Взаимодействие с ОС

Работа с файлами

Пути и названия файлов и директорий

Если предполагается использовать код в разных операционных системах, то имеет смысл учитывать различие в формировании путей, в частности, прямые и обратные слэши в адресах файлов. Особенно это важно, когда при выполнении скрипта предполагается сохранение его или его результатов по определенному адресу. Также стоит помнить, что простое копирование путей из проводника Windows может не работать, так как R/RStudio предполагают использование прямых слэшей в путях.

Самый простой способ создания адреса обычно основан на использовании paste() + разделитель (\ или '/). Корректнее воспользоваться функцией file.path():

my_path <- file.path('.', 'dir_name', 'file_name.csv')
my_path
## [1] "./dir_name/file_name.csv"

Иногда полезно использовать file.path() вместе с функцией normalizePath() и с аргументом mustWork = FALSE, если файл ещё не создан. В частности, это повышает надёжность пути. Также с помощью normalizePath() можно узнать полный адрес файла:

normalizePath('./data/spss_example.sav')
## [1] "/home/konhis/Documents/Rprojects/r_textbook/data/spss_example.sav"

Название и расширения файла

Достаточно часто возникает необходимость получить название файла или его расширение или же просто удалить из пути всё, кроме собственно названия файла. Например, нередко в названии файла может быть дата его создания или же идентификатор респондента.

Конечно, подобные задачи легко решаются регулярными выражениями, однако можно воспользоваться и базовыми функциями, в частности basename(), которая возвращает название файла и его расширение.

basename('./data/spss_example.sav')
## [1] "spss_example.sav"

В пакете tools, который идёт в базовом наборе пакетов, имеется целый ряд полезных функций для работы с названиями файлов, в частности, file_path_sans_ext(), которая возвращает адрес файла, без расширения:

library(tools)
file_path_sans_ext('./data/spss_example.sav')
## [1] "./data/spss_example"
file_path_sans_ext(basename('../data/spss_example.sav'))
## [1] "spss_example"

Извлечение названия директории файла проще, и для него достаточно воспользоваться функцией dirname():

dirname('./data/spss_example.sav')
## [1] "./data"

Создание файлов и директорий

При сохранении файлов (записи файлов на диск) приходится учитывать, что нельзя одновременно создать директорию и записать в неё файл: необходимо сначала создать директорию с помощью функции dir.create() и только после этого записать файл.

# создаём директорию
my_dir_path <- './data/new_dir'
dir.create(path = my_dir_path)

Если попытаться создать директорию, когда директория с таким именем уже существует, то R выдаст предупреждение. Поэтому перед созданием директории стоит сделать проверку:

# повторяем создание
dir.create(path = my_dir_path)
## Warning in dir.create(path = my_dir_path): './data/new_dir' already exists
# проверяем наличие
dir.exists(paths = my_dir_path)
## [1] TRUE

В работе с файлами прослеживается такая же логика: можно создать файл с помощью функции file.create() и потом в него записать какой-то вектор.

# создаём путь 
my_file_path <- file.path(my_dir_path, 'vec.txt')
print(my_file_path)
## [1] "./data/new_dir/vec.txt"
# создаём файл по этому пути
file.create(my_file_path)
## [1] TRUE
write('записываем строку в файл', file = my_file_path)

# проверяем, что записалось
readLines(my_file_path)
## [1] "записываем строку в файл"

Как правило, необходимость в такой логике возникает очень редко, всё же для сохранения объектов есть другие, более подходящие и удобные функции. Тем не менее, инструменты для низкоуровневой работы с файлами могут быть полезн, например, проверка на наличие файла, получение его метаинформации или просто смена прав доступа (для *nix-систем).

# проверяем на наличие
file.exists(my_file_path)
## [1] TRUE
# смотрим даты создания, mode и проч.
file.info(my_file_path)
##                        size isdir mode               mtime               ctime
## ./data/new_dir/vec.txt   46 FALSE  664 2022-02-04 18:55:22 2022-02-04 18:55:22
##                                      atime  uid  gid  uname grname
## ./data/new_dir/vec.txt 2022-02-04 18:55:22 1000 1000 konhis konhis

Листинг файлов и директорий

Для создания списка файлов определённой директории можно воспользоваться функцией list.files(). Притом можно не просто просматривать файлы директории, но и выбирать конкретные файлы по паттерну (например, удобно читать потоком xlsx-файлы и игнорировать временные файлы). Функция может работать рекурсивно по вложенным директориям, а также возвращать полные пути и адреса файлов.

sav_files <- list.files(path = './data', pattern = '\\.sav$', full.names = TRUE)
sav_files
## [1] "./data/spss_example.sav"  "./data/spss_example2.sav"

Аналогично можно анализировать структуру папок. Например, ранее мы создавали new_dir в директории /data, можно выбрать её и все поддиректории:

list.dirs(path = './data')
## [1] "./data"         "./data/new_dir"

Временные файлы и директории

Эпизодически возникают ситуации, когда необходимо промежуточный результат операции куда-то сохранить, чтобы потом дополнительно прочитать и обработать. Например, когда в файле данных много ошибок — лишних разделителей колонок или строк, можно его построчно импортировать, почистить строки от лишних знаков, так же построчно сохранить и потом уже прочитать функцией для импорта таблиц со всеми необходимыми аргументами.

В таких случаях, чтобы не усложнять себе жизнь придумыванием уникальных имен и адресов файлов, можно воспользоваться временными файлами. Временные файлы записываются во временную директорию, которая создаётся при запуске R-сессии (то есть каждая сессия имеет собственную временную директорию).

# создаём название и путь временного файла
my_temp_file <- tempfile()
my_temp_file
## [1] "/tmp/RtmpG1FaJn/file8481e3b6367f7"
# сохраняем iris во временный файл
write.csv(iris, my_temp_file, row.names = FALSE)

# импортируем первые 3 строки временного файла
read.table(my_temp_file, nrows = 3, header = TRUE, sep = ',')
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa

Работа с системными командами

Для того чтобы из R вызвать какой-нибудь bash-скрипт, исполняемый файл или какую-либо команду, используют одну из двух функций — system() или system2().

Первая реализация механизма — system(), в *nix-системах system() передаёт команду в командную строку, где она и выполняется. В Windows для вызова команд из командной строки есть функция shell(), а system() напрямую обращается к исполняемому файлу. Соответственно, результаты одной и той же функции в разных операционных системах могут не совпадать. Также в разных системах может различаться обработка ошибок (stderr).

Первый аргумент функции system() — строка с текстом команды и её аргументами, аргумент intern = TRUE используется в тех случаях, когда результат необходимо вернуть в рабочее окружение R-сессии.

Вызовем R в командной строке с аргументом -e (expression), то есть что необходимо выполнить указанное выражение '2 + 2':

system("Rscript -e '2 + 2'")

Повторим операцию, только результат выполнения команды запишем в объект from_cl:

from_cl <- system("Rscript -e '2 + 2'", intern = TRUE)
str(from_cl)
##  chr "[1] 4"

Функция system2() в определённой мере представляет собой развитие system(), однако обе функции нельзя признать взаимозаменяемыми. В частности, system2() не зависит от платформы, аргументы команды передаются в отдельном строковом векторе. Также отличается и обработка потоков вывода/ошибок (stdout/stderr).

Вызовем R в командной строке и выполним выражение '2 + 2':

system2("Rscript", args = "-e '2 + 2'")

Повторим и запишем результат в from_cl2:

from_cl2 <- system2("Rscript", args = "-e '2 + 2'", stdout = TRUE)
str(from_cl2)
##  chr "[1] 4"