Sessions, Workspaces and Startup
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
Checked against R 4.6.1 (2026-06-24) on 2026-10-20; R 4.6.2 is scheduled for 2026-10-29. Everything on this page is base R, so no package versions apply. Every [source,r] block that has #> lines was run with Rscript on R 4.6.1 and the output is pasted as it was printed, except that paths and platform strings are cut down to a generic form (the examples print basename() of a folder, not the folder). The examples that start R again do so with system2(), so they need R on the PATH, as Rscript is.
A running R process carries more state than the objects you can see: a working directory, a workspace, a search path of attached packages, a table of options, a set of environment variables and, in a terminal, a command history. Most "it works on my machine" problems with R scripts come from that hidden state, so this page is about where it comes from at startup, how to read and change it, and how to write code that does not depend on it. The language itself starts on Syntax and Lexical Structure; IDEs and the R Console covers the consoles that start sessions, and Scripts and the Command Line covers Rscript.
The examples use the library scenario of the section: books.csv and loans.csv in a folder called shelf, with shelf_today and loan_days fixed so results do not change. Blocks that read books.csv assume the working directory is that folder.
The working directory
Every relative path in R is resolved against the working directory of the process, so read.csv("books.csv") works only while the working directory is the folder that holds the file. getwd() reports it, setwd() changes it, and setwd() returns the previous directory invisibly so that it can be put back. A directory that does not exist is an error and leaves the working directory alone.
# Where am I, and what is here?
basename(getwd())
#> [1] "shelf"
file.exists("books.csv")
#> [1] TRUE
list.files(pattern = "\\.csv$")
#> [1] "books.csv" "loans.csv"
# Building and taking apart paths
file.path("data", "raw", "books.csv")
#> [1] "data/raw/books.csv"
basename("data/raw/books.csv")
#> [1] "books.csv"
dirname("data/raw/books.csv")
#> [1] "data/raw"
basename(dirname(normalizePath("books.csv")))
#> [1] "shelf"
# setwd() returns the previous directory, invisibly
scratch <- file.path(tempdir(), "scratch")
dir.create(scratch)
identical(dirname(scratch), tempdir())
#> [1] TRUE
old <- setwd(scratch)
basename(getwd())
#> [1] "scratch"
setwd(old)
basename(getwd())
#> [1] "shelf"
# Restore the directory even when the code in between fails
count_csv <- function(dir) {
old <- setwd(dir)
on.exit(setwd(old))
length(list.files(pattern = "\\.csv$"))
}
count_csv(".")
#> [1] 2
basename(getwd())
#> [1] "shelf"
# A missing directory is an error, and the directory is unchanged
tryCatch(setwd("nope"), error = \(e) conditionMessage(e))
#> [1] "cannot change working directory"
basename(getwd())
#> [1] "shelf"
Source: R documentation: getwd and setwd
The pieces used above:
-
getwd()andsetwd(dir)report and change the directory;setwd()returns the old one invisibly. -
file.path(...)joins path parts with/, which works on every platform, Windows included (R FAQ 7.8).basename()anddirname()take a path apart without touching the file system. -
normalizePath(path)gives the absolute, canonical form of an existing path; for a path that does not exist it warns, for examplepath[1]="nope.csv": No such file or directory, unlessmustWork = FALSE. -
list.files(path, pattern),file.exists(),dir.exists()anddir.create()list a folder, test for files and folders, and create a folder. -
tempdir()is the per-session temporary folder, removed when the session ends (taken fromTMPDIR,TMPorTEMP, consulted in that order), andtempfile()returns a new unique name inside it without creating the file.
Three habits keep the working directory from causing trouble:
-
Use
setwd()interactively, never as the first line of a script. A line such assetwd("C:/Users/me/analysis")works on one machine only, and it changes the directory for everything that runs afterwards in the session. -
When a function must change directory, restore it with
on.exit(setwd(old))ascount_csv()does above, so a failure half way does not leave the session somewhere else. -
Build paths with
file.path()instead of pasting separators, and on Windows write/or\\: a single backslash starts an escape sequence (R FAQ 7.8, and the'\U' used without hex digitserror in Common errors and warnings).
Projects are the better answer to "where am I?". An RStudio project (a folder with an .Rproj file) is described by the Posit documentation as a context with its own working directory, workspace, history and source documents: opening it sets the working directory to the project folder and loads that folder’s .RData (if the options say so) and .Rhistory. The community package here goes one step further by building paths from the project root, wherever the script is run from. The R manual gives the same advice without a tool: use a separate working directory for each analysis, because objects called x and y in one analysis are hard to recognise later (R-intro, Data permanency and removing objects).
The workspace
The workspace is the global environment, .GlobalEnv: the place where the objects you create at the top level live, and the first entry of the search path. ls() lists its names without a leading dot, ls(all.names = TRUE) lists those too, exists(), get() and assign() read and create objects by name, and rm() removes them.
books <- read.csv("books.csv")
loans <- read.csv("loans.csv", colClasses = "character")
loan_days <- 21L
# The workspace is the global environment; ls() lists its objects
environmentName(globalenv())
#> [1] "R_GlobalEnv"
ls()
#> [1] "books" "loan_days" "loans"
# A leading dot hides a name from ls()
.draft <- "scratch notes"
ls()
#> [1] "books" "loan_days" "loans"
ls(all.names = TRUE)
#> [1] ".draft" "books" "loan_days" "loans"
# Looking up, reading and creating objects by name
exists("books")
#> [1] TRUE
nrow(get("books"))
#> [1] 16
assign("branch", "Main Library")
branch
#> [1] "Main Library"
rm(.draft, branch)
# rm(list = ls()) empties the workspace and nothing else
options(digits = 4)
library(tools)
rm(list = ls())
ls()
#> character(0)
getOption("digits")
#> [1] 4
"package:tools" %in% search()
#> [1] TRUE
Source: R documentation: ls
rm(list = ls()) is the line most older tutorials put at the top of a script. It removes the objects whose names do not start with a dot and nothing else: the example shows that the option set earlier and the attached package are still there, and so are hidden objects, loaded namespaces, environment variables, the working directory, the random-number state and any open connections. The R FAQ’s own recipe, rm(list = ls(all.names = TRUE)), also removes the dot names (R FAQ 7.3), but it is no more of a reset. A script that needs a clean start gets it from a new process (Rscript, or restarting the session in an IDE), and a script that is correct from a clean start does not need the line at all.
Saving and loading
save() writes named objects to a file that load() restores under the same names; saveRDS() writes one object that readRDS() returns, so the caller picks the name. save.image() is save() of the whole workspace, and its default file is .RData in the working directory, which is why a stray save.image() litters a folder. The examples write to tempfile() instead.
books <- read.csv("books.csv")
loans <- read.csv("loans.csv", colClasses = "character")
# save() and load(): several objects, and the names come back with them
rdata <- tempfile(fileext = ".RData")
save(books, loans, file = rdata)
rm(books, loans)
load(rdata)
ls()
#> [1] "books" "loans" "rdata"
# load() into a separate environment instead of the global one
shelf_env <- new.env()
load(rdata, envir = shelf_env)
ls(shelf_env)
#> [1] "books" "loans"
# saveRDS() and readRDS(): one object, and you choose the name
rds <- tempfile(fileext = ".rds")
saveRDS(books, rds)
shelf_books <- readRDS(rds)
identical(shelf_books, books)
#> [1] TRUE
# save.image() saves the whole workspace, helper variables included
workspace_file <- tempfile(fileext = ".RData")
save.image(file = workspace_file)
sort(load(workspace_file, envir = new.env()))
#> [1] "books" "loans" "rdata" "rds"
#> [5] "shelf_books" "shelf_env" "workspace_file"
load() brings the objects in under their saved names and silently overwrites objects of the same name in the target environment, so load(file, envir = new.env()) is safer when the file is not yours. readRDS() cannot overwrite anything. For a single data frame, saveRDS() and readRDS() are the better pair, and the formats are covered in Serialization: RDS, RData and JSON. Both are R-only formats; the text formats are in Reading and Writing Text Data.
The .RData file and ending a session
When a session ends R can write the workspace to .RData in the working directory, and the next session started in that directory restores it (R-intro, Data permanency and removing objects). q(save = "yes") saves, q(save = "no") does not, and the default, "default", asks in interactive use and is controlled by the command-line flags in non-interactive use. The flags --save and --no-save choose whether to save at the end, and --restore and --no-restore (or the narrower --no-restore-data and --no-restore-history) whether to restore at the start. --vanilla combines --no-save, --no-restore, --no-environ, --no-site-file and --no-init-file. The examples start new R processes with system2(), through a small helper, r_run(), defined in the first block below; every example that starts R reuses it, and each checked script defines it at its top.
r_run <- function(..., flags = character(), env = character()) {
args <- c("--no-echo", flags, as.vector(rbind("-e", shQuote(c(...)))))
out <- suppressWarnings(
system2("R", args, stdout = TRUE, stderr = TRUE, env = env)
)
invisible(cat(out[nzchar(trimws(out))], sep = "\n"))
}
project <- file.path(tempdir(), "shelf-session")
dir.create(project)
old <- setwd(project)
# Session 1 creates an object and quits with q(save = "yes")
r_run("shelf_count <- 16L", 'q(save = "yes")')
list.files(all.files = TRUE, no.. = TRUE)
#> [1] ".RData"
# Session 2 restores .RData when asked to (--restore); --no-restore skips it
r_run("print(ls())", flags = "--restore")
#> [1] "shelf_count"
r_run("print(ls())", flags = "--no-restore")
#> character(0)
# q(save = "no") ends a session and leaves .RData as it was
r_run("scratch <- 1", 'q(save = "no")')
r_run("print(ls())", flags = "--restore")
#> [1] "shelf_count"
setwd(old)
unlink(project, recursive = TRUE)
The manual says that restoring is the default. The examples above pass --restore explicitly, because the R used to check them starts every session as R --no-save --no-restore: the R front-end script of that build adds both flags before the ones you give. Rscript always adds --no-restore (Rscript --help says "in addition to --no-echo --no-restore"), so a script run with Rscript never restores a stale .RData. Run R with no flags in your own project folder and see whether ls() is empty before relying on either behaviour.
The widely repeated advice is to never save the workspace and never restore it. A saved .RData hides how the objects were made, so an analysis that works only because of an object created last month cannot be repeated by anyone else, or by you on a new machine; objects from an old session also mask objects the script defines, and a large .RData slows every start. The recipe is to keep the code in scripts, keep the data in files that you can read again, and use saveRDS() for an intermediate result that is costly to recompute. In RStudio the same two switches, "Restore .RData into workspace at startup" and "Save workspace to .RData on exit" (ask, always or never), are listed under General in the project options of the Posit documentation, which says that these project options override the global ones; unchecking the first and choosing "Never" for the second gives the recommended behaviour. The R FAQ’s entry on saving the workspace says only what save.image() does (R FAQ 7.2).
Startup: what R reads, and in what order
R reads two kinds of startup file. Environment files (Renviron, .Renviron) are lists of NAME=value lines that set environment variables; they are not R code. Profile files (Rprofile.site, .Rprofile) are R code that is run. The Startup help page gives the order, which the manual R-intro (section 10.8, Customizing the environment) summarises as "Rprofile.site, the user profile, .RData and then .First()":
Source: R documentation: Startup
Two details differ from many summaries. The default packages are attached after .First() runs, by .First.sys(); only base and methods (attached early by .OptRequireMethods()) exist while the profiles and .First() run, and the example below prints exactly that. And on Unix there is a first file, R_HOME/etc/Renviron, written when R was configured; it is read before the others, they can override its values, and it is not the same file as Renviron.site.
The files and the variables that name them
| File | Chosen by | Notes |
|---|---|---|
|
always (Unix) |
Written at configure time. Do not edit; override in the site or user file. |
|
|
Site-wide variables. The file does not exist in a factory-fresh installation. Skipped by |
|
|
The first one found is used, not both. Skipped by |
|
|
Site-wide R code, run with only |
|
|
The first one found is used, not both. Skipped by |
|
the working directory |
Restored unless |
The consequence of "working directory, then home directory" is that a project folder with its own .Rprofile replaces the one in your home directory rather than adding to it. Setting R_ENVIRON_USER or R_PROFILE_USER to a file that does not exist makes child R processes (such as those of R CMD check) ignore your personal files. The example builds a throw-away "home", a project folder with an .Renviron and an .Rprofile, and a site Renviron.site and Rprofile.site named by R_ENVIRON and R_PROFILE. Each file appends its name to an environment variable called TRACE, .First() prints it, and every run is a fresh R:
# A throw-away "home" with a project folder, a site Renviron and a site profile
home <- file.path(tempdir(), "home")
project <- file.path(home, "shelf-project")
dir.create(project, recursive = TRUE)
site_environ <- file.path(home, "Renviron.site")
site_profile <- file.path(home, "Rprofile.site")
writeLines("TRACE=site.Renviron", site_environ)
writeLines('TRACE="${TRACE} > user.Renviron"', file.path(project, ".Renviron"))
writeLines(
'Sys.setenv(TRACE = paste(Sys.getenv("TRACE"), "> Rprofile.site"))',
site_profile
)
writeLines(c(
'Sys.setenv(TRACE = paste(Sys.getenv("TRACE"), "> .Rprofile"))',
".First <- function() {",
' cat("First :", Sys.getenv("TRACE"), "\\n")',
' cat("RData :", exists("from_rdata"), "\\n")',
' pkgs <- grep("^package:", search(), value = TRUE)',
' cat("Attached:", sub("^package:", "", pkgs), "\\n")',
"}",
'.Last <- function() cat("Last : done\\n")'
), file.path(project, ".Rprofile"))
# Leave an .RData behind, as an earlier session would have
old <- setwd(project)
Sys.unsetenv("R_PROFILE_USER")
r_run("from_rdata <- 1", 'q(save = "yes")', flags = "--vanilla")
# Start R in the project. R_ENVIRON and R_PROFILE name the site files; the
# user files are found by location (HOME is the throw-away home)
env <- c(
paste0("HOME=", home),
paste0("R_ENVIRON=", site_environ),
paste0("R_PROFILE=", site_profile)
)
ready <- 'cat("Session :", length(grep("^package:", search())), "packages\\n")'
r_run(ready, flags = "--restore", env = env)
#> First : site.Renviron > user.Renviron > Rprofile.site > .Rprofile
#> RData : TRUE
#> Attached: methods base
#> Session : 7 packages
#> Last : done
# Each switch removes one step; --vanilla removes all of them
switches <- c(
"--no-environ", "--no-init-file", "--no-restore-data", "--vanilla"
)
for (flag in switches) {
cat("---", flag, "\n")
r_run(ready, flags = c("--restore", flag), env = env)
}
#> --- --no-environ
#> First : > Rprofile.site > .Rprofile
#> RData : TRUE
#> Attached: methods base
#> Session : 7 packages
#> Last : done
#> --- --no-init-file
#> Session : 7 packages
#> --- --no-restore-data
#> First : site.Renviron > user.Renviron > Rprofile.site > .Rprofile
#> RData : FALSE
#> Attached: methods base
#> Session : 7 packages
#> Last : done
#> --- --vanilla
#> Session : 7 packages
setwd(old)
unlink(home, recursive = TRUE)
Source: R documentation: Startup
The output shows the order (site Renviron, user Renviron, site profile, user profile), that .RData is already restored when .First() runs, that only methods and base are attached at that point, and that each --no-* flag removes exactly its own step. The rows with --no-init-file and --vanilla print only the session line because .First() and .Last() live in the skipped .Rprofile. --vanilla also skips the restore. The --no-environ row shows that R_PROFILE is still honoured: the flags are independent.
Environment files and variables
A line in an environment file is a comment starting with # or NAME=value. The value is processed like a Unix shell word: quotes are removed, ${foo-bar} is the value of foo if set and otherwise bar, ${foo:-bar} also falls back when foo is empty, and the braces are essential, because $HOME is not expanded. Use forward slashes in paths, even on Windows, and quote values that contain spaces. The lines are not R code, so SHELF_BRANCH = "Main" with spaces around the = is wrong.
# ~/.Renviron or a project .Renviron (illustrative: not run)
SHELF_BRANCH='Main Library'
SHELF_LOAN_DAYS=21
R_LIBS_USER="${HOME}/R/library"
# a secret belongs here, not in a script that is committed to version control:
# SHELF_API_TOKEN=...
Source: R documentation: Startup
In a session, Sys.getenv() reads variables (with unset = as the fallback, and NA to tell "unset" from "empty"), Sys.setenv() sets them for the session and for the processes it starts, Sys.unsetenv() removes them, and readRenviron(path) reads an environment file into the running session, as startup would. A missing file gives a warning and FALSE.
# Reading, defaulting, setting and removing environment variables
Sys.getenv("SHELF_BRANCH")
#> [1] ""
Sys.getenv("SHELF_BRANCH", unset = "Main Library")
#> [1] "Main Library"
Sys.setenv(SHELF_BRANCH = "Riverside", SHELF_LOAN_DAYS = "28")
Sys.getenv(c("SHELF_BRANCH", "SHELF_LOAN_DAYS"))
#> SHELF_BRANCH SHELF_LOAN_DAYS
#> "Riverside" "28"
loan_days <- as.integer(Sys.getenv("SHELF_LOAN_DAYS", unset = "21"))
loan_days
#> [1] 28
Sys.unsetenv("SHELF_BRANCH")
Sys.getenv("SHELF_BRANCH", unset = NA)
#> [1] NA
# A variable set with Sys.setenv() reaches the processes R starts
Sys.setenv(SHELF_BRANCH = "Riverside")
show_branch <- shQuote('print(Sys.getenv("SHELF_BRANCH"))')
child_args <- c("--vanilla", "--no-echo", "-e", show_branch)
cat(system2("R", child_args, stdout = TRUE), sep = "\n")
#> [1] "Riverside"
# readRenviron() reads a file of NAME=value lines into the running session
renviron <- tempfile(fileext = ".Renviron")
writeLines(c(
"# the shelf settings",
"SHELF_BRANCH='North Branch'",
"SHELF_LOAN_DAYS=14"
), renviron)
readRenviron(renviron)
Sys.getenv(c("SHELF_BRANCH", "SHELF_LOAN_DAYS"))
#> SHELF_BRANCH SHELF_LOAN_DAYS
#> "North Branch" "14"
The variables that matter most for a session are in the EnvVar help page. HOME is the home directory (on Windows R sets R_USER to it, and HOME too if unset); R_HOME is set by R and is what R.home() reports; TMPDIR, TMP and TEMP choose the temporary folder; R_LIBS, R_LIBS_USER and R_LIBS_SITE seed .libPaths(); R_DEFAULT_PACKAGES names the packages attached at startup; R_HISTFILE and R_HISTSIZE control the command history; TZ and LC_ALL set the time zone and the locale. Variables that set the default of an option, such as R_DEFAULT_INTERNET_TIMEOUT for options("timeout"), are read once at startup. Anything set in an .Renviron is visible to every R process started from that session, including Rscript children, which is the point and the risk.
Library paths: R_LIBS, R_LIBS_USER and R_LIBS_SITE
The library search path that library() uses is .libPaths(). At startup R builds it from R_LIBS and R_LIBS_USER (in that order), then the site libraries from R_LIBS_SITE, then the system library, and keeps only directories that exist. If R_LIBS_USER is unset or empty R sets it to a version-specific folder in your home directory: R/<platform>-library/<x.y> on Unix-alikes, R/win-library/<x.y> under LOCALAPPDATA on Windows, and Library/R/<m>/<x.y>/library for CRAN builds on macOS. Set a variable to NULL to mean "no directories". Since R 4.2.0 both R_LIBS_USER and R_LIBS_SITE are set by R’s startup code when they are unset or empty, so Sys.getenv("R_LIBS_USER") shows the default (before 4.2.0 only R_LIBS_USER was), and the default user library is only used if it exists (R-admin, Managing libraries).
# The default user library on a Unix-alike ends in the R version: x.y
basename(Sys.getenv("R_LIBS_USER"))
#> [1] "4.6"
# Two extra libraries, set the way a .Renviron would set them
libs <- file.path(tempdir(), c("project-library", "user-library"))
dir.create(libs[[1L]])
dir.create(libs[[2L]])
env <- c(paste0("R_LIBS=", libs[[1L]]), paste0("R_LIBS_USER=", libs[[2L]]))
r_run("print(basename(.libPaths()[1:2]))", env = env)
#> [1] "project-library" "user-library"
# A directory that does not exist is silently dropped
env <- paste0("R_LIBS_USER=", file.path(tempdir(), "not-there"))
r_run("print(any(grepl('not-there', .libPaths())))", env = env)
#> [1] FALSE
# Inside a session, .libPaths(new) puts new first
old_paths <- .libPaths()
.libPaths(libs[[1L]])
basename(.libPaths())[[1L]]
#> [1] "project-library"
.libPaths(old_paths)
# A package that is in no library on the path
missing_pkg <- "shelfdata"
tryCatch(
library(missing_pkg, character.only = TRUE),
error = \(e) conditionMessage(e)
)
#> [1] "there is no package called ‘shelfdata’"
Source: R documentation: .libPaths
Changing R_LIBS_USER therefore changes which packages are visible. Packages installed into the old user library seem to vanish, and the symptom is the error there is no package called followed by the name; the cure is to put the old directory back in R_LIBS, or to reinstall (or use a per-project library, see Reproducible Environments with renv). The distinction between a package and a library, and why library() is the function that attaches a package, is R FAQ 7.29; the case "I installed a package but the functions are not there" is R FAQ 7.30. How packages are installed, loaded and attached is the subject of Packages, Libraries and Namespaces.
What belongs in .Rprofile
An .Rprofile is sourced into the global environment with only base loaded. Three rules follow from that:
-
Qualify every function that is not in
base(utils::head(),grDevices::ps.options()). Code such asps.options(horizontal = FALSE)that worked while the package was already attached stops working after an update, which is R FAQ 7.25. -
Wrap statements in
local({ ... })so that the profile creates no objects in your workspace. -
Keep out anything that changes what a script computes. Anything that only changes how R talks to you (the prompt, the CRAN mirror, whether quotes are fancy) is safe.
# ~/.Rprofile (a fragment for a file of its own: not a script)
local({
options(
repos = c(CRAN = "https://cloud.r-project.org"),
useFancyQuotes = FALSE,
warnPartialMatchDollar = TRUE
)
if (interactive()) {
options(prompt = "shelf> ")
}
})
Source: R documentation: Startup
Starting R with R_PROFILE_USER pointing at that file gives the settings, an empty workspace and the default packages (to run the next block, save the fragment to a temporary file and store its path in profile). The prompt stays at its default, "> ", because the session is not interactive.
r_run(
"print(getOption('repos'))",
"print(getOption('prompt'))",
"print(ls(all.names = TRUE))",
"print(length(grep('^package:', search())))",
env = paste0("R_PROFILE_USER=", profile)
)
#> CRAN
#> "https://cloud.r-project.org"
#> [1] "> "
#> character(0)
#> [1] 7
Source: R documentation: options
The opposite mistake is putting library() calls in .Rprofile. Scripts then work on your machine because the profile attached the package, and fail everywhere else, including under Rscript --vanilla, because Rscript reads the profile unless told not to. The error is could not find function, and the same error with head in place of file_ext is what a profile gets for an unqualified utils function, since only base and methods are attached while it runs (the startup example above shows exactly that):
profile <- tempfile(fileext = ".R")
env <- paste0("R_PROFILE_USER=", profile)
# A script that only works because a profile attached a package
writeLines("library(tools)", profile)
script <- tempfile(fileext = ".R")
writeLines('cat(file_ext("books.csv"), "\\n")', script)
run_script <- function(flags = character()) {
args <- c(flags, script)
out <- suppressWarnings(
system2("Rscript", args, stdout = TRUE, stderr = TRUE, env = env)
)
invisible(cat(out, sep = "\n"))
}
run_script()
#> csv
run_script("--vanilla")
#> Error in file_ext("books.csv") : could not find function "file_ext"
#> Calls: cat
#> Execution halted
The failing child ends with a non-zero status, which system2() reports as a warning that the helper suppresses. A profile that fails at startup stops R as a failing script does, so test an edited profile with R --no-echo -e "1" before leaving it in place. For head the checked startup message was Error: could not find function "head" followed by Execution halted.
.First(), .Last() and .First.sys()
A function called .First found in a profile or in .RData runs at the beginning of the session, after the profiles and the restore and before the default packages are attached; .Last runs at the end of the session (q() with runLast = TRUE, the default). .First.sys() in base attaches the packages in options("defaultPackages") and .Last.sys() is its counterpart at quit. R-intro’s example .First() sets the prompt, calls library(MASS) and opens a graphics device; modern practice keeps .First() for cheap, harmless actions, and uses reg.finalizer(onexit = TRUE) instead of .Last() in code that others run, because .Last belongs to the end user and can be replaced during the session (quit help page). The Startup help page warns that startup code must not interact with the user, which can crash the process.
Source: R documentation: quit and .Last
options()
options() is the table of global settings that functions consult. options() with no argument returns all of them, getOption(name, default) reads one (use it, not options("name"), which is a list of length one), and options(name = value) sets one and returns the old values invisibly as a list, which options(old) puts back. The same pattern inside a function, op <- options(digits = 3); on.exit(options(op)), is what R FAQ 7.7 recommends; assigning to .Options does not work. An option set in a profile applies to every session; an option set in a script applies to that session, so a package or a script that changes one for its own purposes undoes the change on exit.
# Read an option, with a fallback for one that is not set
getOption("digits")
#> [1] 7
getOption("shelf.branch", default = "Main Library")
#> [1] "Main Library"
# options() returns the old values, invisibly, so they can be put back
old <- options(digits = 3)
old
#> $digits
#> [1] 7
#>
pi
#> [1] 3.14
options(old)
pi
#> [1] 3.141593
# The same pattern inside a function: the option is restored on exit,
# so the value is formatted inside (printing happens after the restore)
show_ratio <- function(x, y) {
op <- options(digits = 3)
on.exit(options(op))
format(x / y)
}
show_ratio(22, 7)
#> [1] "3.14"
# scipen: fixed or scientific notation; width: columns per line
1e5
#> [1] 1e+05
old <- options(scipen = 999, width = 40)
1e5
#> [1] 100000
seq_len(30)
#> [1] 1 2 3 4 5 6 7 8 9 10 11 12
#> [13] 13 14 15 16 17 18 19 20 21 22 23 24
#> [25] 25 26 27 28 29 30
options(old)
# Options that are not what an old tutorial says
getOption("stringsAsFactors")
#> NULL
tryCatch(options(stringsAsFactors = TRUE), warning = \(w) conditionMessage(w))
#> [1] "'options(stringsAsFactors = TRUE)' is deprecated and will be disabled"
getOption("defaultPackages")
#> [1] "datasets" "utils" "grDevices" "graphics" "stats" "methods"
Source: R documentation: options
The options most often met:
| Option | Effect |
|---|---|
|
Significant digits when printing numbers (1 to 22, default 7). A suggestion only, and it changes the display, not the value. |
|
A penalty applied when choosing between fixed and scientific notation: positive favours fixed, negative scientific (default 0). |
|
Maximum columns per line of output (10 to 10000), also used when |
|
Roughly the maximum number of entries a print shows (default 99999). |
|
The decimal mark used when printing, formatting and |
|
How warnings are handled: negative ignores them, 0 (default) stores them until the top-level call returns, 1 prints them as they occur, 2 or larger turns them into errors. |
|
A function or expression run when an error is not caught, for example |
|
The package repositories used by |
|
Seconds allowed for some internet operations (default 60); the default can be set with |
|
The encoding assumed for input connections (default |
|
The packages attached at startup. Initially from |
|
Defaults for |
|
Gone. R 4.0.0 changed the default of |
Source: R documentation: options
The four warn levels differ in when the message appears, and -1 and 2 change what the program does. The function below is run in a fresh R for each level. At level 0 the warning appears after the top-level call that caused it has finished, so it is printed before the next -e expression:
# One function that warns, run in a fresh R for each value of the warn option
define <- paste(
"check_loan <- function(days) {",
'if (days > 21) warning("loan is overdue")',
"days",
"}",
sep = "\n"
)
for (w in c(-1, 0, 1, 2)) {
cat("--- warn =", w, "\n")
r_run(
define, sprintf("options(warn = %d)", w),
"x <- check_loan(30)", 'cat("after the warning\\n")',
flags = "--vanilla"
)
}
#> --- warn = -1
#> after the warning
#> --- warn = 0
#> Warning message:
#> In check_loan(30) : loan is overdue
#> after the warning
#> --- warn = 1
#> Warning in check_loan(30) : loan is overdue
#> after the warning
#> --- warn = 2
#> Error in check_loan(30) : (converted from warning) loan is overdue
#> Execution halted
# warn = 2 turns a warning into an error that tryCatch() can see
op <- options(warn = 2)
tryCatch(as.integer("x"), error = \(e) conditionMessage(e))
#> [1] "(converted from warning) NAs introduced by coercion"
options(op)
Source: R documentation: options (warn)
The warn help text says that in the future the value will be checked to be one of -1, 0, 1 or 2; setting R_WARN_BOUNDS_OPT to "yes" activates the check now (R 4.6.0 NEWS). Level 2 is useful for debugging because a warning then stops the code where it happened, and risky for unattended jobs: the help page notes that turning warnings into errors can trigger bugs and resource leaks that would not otherwise occur.
The command history
The history is the list of commands typed at the console. history(max.show = 25, pattern) shows the last lines, savehistory(file = ".Rhistory") writes them and loadhistory(file = ".Rhistory") reads them back; timestamp() writes a time-stamp line into the history. They work with the readline interface on Unix-alikes and with Rgui and interactive Rterm on Windows, and not in batch use: under Rscript, savehistory() stops with no history available to save and loadhistory() with no history mechanism available (both checked with tryCatch() on R 4.6.1). At a console the calls look like the fragment below, which is why it is not a script.
# fragment: type these at an interactive console
history(max.show = 10)
history(pattern = "read.csv")
savehistory("shelf-session.Rhistory")
loadhistory("shelf-session.Rhistory")
R_HISTFILE (default .Rhistory in the working directory) names the file used when the history is restored at startup, unless --no-restore-history or --no-restore is given, and saved at the end, and R_HISTSIZE is the maximum number of lines saved (default 512 on the readline interface). The IDEs have their own history pane and files: R.app on macOS keeps .Rapp.history, and RStudio’s project options include "Always save history (even when not saving .RData)". The history records what was typed, not what happened, so it is a diary, not a substitute for a script.
The search path at startup
search() lists the environments R looks through when it evaluates a name, from the global environment outwards; searchpaths() adds the file system paths. A new session has the workspace first, then the default packages in the reverse order of attaching, then Autoloads (the environment for objects set up with autoload(), R FAQ 7.6) and finally package:base. library() attaches a package at position 2, immediately after .GlobalEnv, so a package attached later masks the ones attached earlier, and anything in the workspace masks all of them.
# The search path of a new session: the workspace first, base last
search()
#> [1] ".GlobalEnv" "package:stats" "package:graphics"
#> [4] "package:grDevices" "package:utils" "package:datasets"
#> [7] "package:methods" "Autoloads" "package:base"
# library() attaches a package at position 2, just after the workspace
library(tools)
search()[1:3]
#> [1] ".GlobalEnv" "package:tools" "package:stats"
detach("package:tools")
# Changing the default packages with R_DEFAULT_PACKAGES
r_run("print(search())", flags = "--vanilla", env = "R_DEFAULT_PACKAGES=NULL")
#> [1] ".GlobalEnv" "Autoloads" "package:base"
r_run(
"print(search())",
flags = "--vanilla", env = "R_DEFAULT_PACKAGES=utils,stats"
)
#> [1] ".GlobalEnv" "package:stats" "package:utils" "Autoloads"
#> [5] "package:base"
Source: R documentation: search
R-intro’s Managing the search path shows attach() putting a data frame at position 2. A restored .RData is not attached: it goes into the global environment itself, which is why its objects mask package functions. attach() is best avoided: its documentation says that it copies the elements into a new environment, so assigning to them changes only the copy, that it "can lead to confusion", and that with() is usually preferable in interactive use. Packages, namespaces and library() versus require() are covered in Packages, Libraries and Namespaces; the lookup rules behind masking are in Scoping, Environments and Closures.
R_DEFAULT_PACKAGES and options(defaultPackages = ) change the attached packages: R_DEFAULT_PACKAGES=NULL attaches none but base, a comma-separated list attaches exactly those, and Rscript --default-packages=LIST does the same for one command (Rscript --help). The packages load in the order given, so they appear on the search path in reverse. A fast, minimal Rscript run that uses only base and utils:: is a good way to find hidden dependencies on attached packages.
Asking the session about itself
# Which R, and in what mode?
R.version.string
#> [1] "R version 4.6.1 (2026-06-24)"
interactive()
#> [1] FALSE
# What the platform reports (the values depend on your machine)
names(Sys.info())
#> [1] "sysname" "release" "version" "nodename"
#> [5] "machine" "login" "user" "effective_user"
sessionInfo()$basePkgs
#> [1] "stats" "graphics" "grDevices" "utils" "datasets" "methods"
#> [7] "base"
# The clock, the calendar and the time zone
class(Sys.time())
#> [1] "POSIXct" "POSIXt"
class(Sys.Date())
#> [1] "Date"
is.character(Sys.timezone())
#> [1] TRUE
# Locale categories are set, and read back, one at a time
Sys.setlocale("LC_COLLATE", "C")
#> [1] "C"
sort(c("b", "A", "a", "B"))
#> [1] "A" "B" "a" "b"
Source: R documentation: sessionInfo
-
R.version.stringandR.versiondescribe the R build;sessionInfo()adds the platform, locale, time zone, BLAS and LAPACK libraries, attached and loaded-only packages with versions, which is what to paste into a bug report or a question (the values are platform-specific, so only the shape is shown above). -
Sys.info()names the operating system and machine;R.home()is the installation folder (R.home("etc")is whereRenvironandRprofile.sitelive). -
interactive()isTRUEat a console andFALSEunderRscript,R -eandR -f, and is the test that guards console-only startup code.commandArgs(trailingOnly = TRUE)returns the arguments after the script name (character(0)when there are none);commandArgs()also lists the flags R was started with. -
Sys.time(),Sys.Date()andSys.timezone()read the clock, the date and the time zone. Examples in this section useshelf_todayinstead ofSys.Date()so output is reproducible; dates are in Dates and Times. -
Sys.setlocale(category, locale)sets one locale category and returns the new value,Sys.getlocale()reads it, and collation (the result ofsort()) followsLC_COLLATE. Locales and encodings are in Encodings, Locales and Internationalization.
Common errors and warnings
| Message | Cause | Fix |
|---|---|---|
|
The folder does not exist, or the path was typed for another machine. |
Check |
|
A relative path and a working directory without the file; the warning comes first, the error follows. |
|
|
The package is not in any library on |
Check |
|
A profile uses a function whose package is not attached yet (R FAQ 7.25). |
|
|
A script relied on a package that a profile attached. |
Call |
|
|
Install the package, or re-create the object. |
|
A Windows path pasted with single backslashes. |
Write |
The file messages were produced by calling read.csv("books.csv") and load("shelf.RData") from a folder that does not hold the files, readRenviron("no-such-file") gives the warning file 'no-such-file' cannot be opened for reading and returns FALSE, and the Windows message comes from parsing setwd("C:\\Users\\me"), with a position that depends on where the text is typed. The stringsAsFactors warning is in the options example above.
The namespace warning needs a package, so the example builds a one-function package shelfdemo in a temporary library (the setup is not shown; it is a DESCRIPTION, a NAMESPACE with export(loan_label) and one file of R code), saves a function from it in a session that can see the library, and loads the file in one that cannot. The function’s environment was the package namespace, which cannot be recreated, so R substitutes the global environment and warns. The transcript below is the two checked runs, shown as a fragment:
# Session 1 sees the package, so a function from it is saved with a reference
# to the package namespace
saved <- tempfile(fileext = ".RData")
r_run(
"library(shelfdemo)", "label <- loan_label",
sprintf("save(label, file = '%s')", saved),
env = paste0("R_LIBS=", lib)
)
# Session 2 has no such package
r_run(sprintf("load('%s')", saved))
#> Warning: namespace ‘shelfdemo’ is not available and has been replaced
#> by .GlobalEnv when processing object ‘label’
r_run(
sprintf("load('%s')", saved),
"print(environmentName(environment(label)))"
)
#> Warning: namespace ‘shelfdemo’ is not available and has been replaced
#> by .GlobalEnv when processing object ‘label’
#> [1] "R_GlobalEnv"
Source: R documentation: load
Pitfalls
-
setwd()at the top of a script. It fixes the script to one machine. Open a project, or runRscriptfrom the project folder, and use relative paths (R FAQ 7.8 for the Windows path rules). -
Relying on a restored workspace. An object that exists only because
.RDatawas restored breaks the script on any other machine. Restart with--no-restoreand run the script from the top. -
Packages attached by a profile. The script then depends on the profile (see
.Rprofileabove). Run important scripts withRscript --vanillato check that they stand alone. -
library()versusrequire()in a startup file.library()stops with an error if the package is missing,require()(designed for use inside functions) returnsFALSEwith a warning and carries on, so a missing package in a profile shows up later and further away. -
options()set and not restored. A function that setsdigitsorwarnand does not restore it changes every later printout; useon.exit(options(op))(R FAQ 7.7). -
A second
.Rprofilethat silently wins. The working directory’s file replaces the home one, so a project that gains an.Rprofileloses the personal settings in~/.Rprofile.
Where older tutorials differ
-
setwd()as the first line of a script, andrm(list = ls())as the second. Current practice is a project (orRscriptrun from the right folder), relative paths and a fresh process for a clean start. -
Answering "Save workspace image?" with yes. The advice is to never save and never restore
.RData, and to save results explicitly withsaveRDS(). -
attach(df)to avoid typingdf$. Usewith()or$. -
options(stringsAsFactors = FALSE)in.Rprofile. Since R 4.0.0 that is the default, anddefault.stringsAsFactors()was deprecated in R 4.2.0 and removed (defunct) in R 4.3.0, so the line is no longer needed and the opposite,TRUE, warns. -
library(MASS)orlibrary(ggplot2)in.Rprofile, or in.First(), so that every script "just works". Load packages in the script that uses them.
The wider list of differences between old and current R is on What’s New and Migrating Older Code, and the idioms that follow from this page are on Idioms and Common Pitfalls.