Unix2018/Перенаправление ввода-вывода

Перенаправление — это возможность командной оболочки перенаправлять стандартные потоки в определенное пользователем место, например в файл.

Базовые примеры

Перенаправление обычно осуществляется вставкой специального символа > между командами. Обычно синтаксис выглядит так:

команда1 > файл1

выполняет команду1, помещая стандартный вывод в файл1.

команда1 < файл1

выполняет команду1, используя в качестве источника ввода файл1 (вместо клавиатуры). На каждый запрос ввода программы считывается одна строка текста из файла.

команда1 < файл1 > файл2

совмещает два предыдущих варианта. Выполняет команду1, вводя из файла1 и выводя в файл2.

Добавление в конец файла

Можно осуществить перенаправление в файл с добавлением в конец. При этом информация, хранящаяся в файле не будет удалена, а вся новая информация будет добавлена в конец этого файла. Синтаксис:

команда1 >> файл1

Перенаправление разных потоков

Файловый дескриптор (FD) — абстрактный индикатор (дескриптор), используемый для доступа к файлу или другому ресурсу ввода/вывода, например к каналу или сетевому сокету. Файловые дескрипторы являются частью POSIX API. Дескриптор файла является неотрицательным целым числом, обычно представленным на языке программирования C как тип int. Мы это рассмотрим позднее, когда будем программировать на C.

Каждый процесс UNIX (кроме демонов) должен иметь три стандартных файловых дескриптора (0, 1, 2), соответствующих трём стандартным потокам:

  1. stdin — стандартный ввод
  2. stdout — стандартный вывод
  3. stderr — стандартный вывод ошибок

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

К примеру:

команда1 2> файл1

выполняет команду1, направляя стандартный поток ошибок в файл1.


Часто стандартный поток ошибок объединяют со стандартным потоком вывода, чтобы можно было обрабатывать ошибки и обычные результаты работы программы вместе. К примеру:

find / -name .profile > results.txt 2>&1

попытается найти все файлы с именем .profile. Если выполнять эту команду без перенаправлений, она будет направлять результаты поиска в stdout, а сообщения об ошибках (к примеру, о недостаточности прав доступа при попытке поиска в защищённых каталогах) в stderr. По умолчанию обе эти роли выполняет консоль. Если стандартный поток вывода направлен в файл results.txt, то ошибки по-прежнему будут направляться в консоль. Чтобы и ошибки, и результаты поиска направлялись в файл results.txt, стандартные потоки ошибок и вывода были объединены с использованием 2>&1.

Написание 2>&1 перед > не будет работать, так как когда интерпретатор прочитает 2>&1, он ещё не знает, куда перенаправлен стандартный поток вывода, поэтому потоки ошибок и вывода не будут объединены.

Конвейеры

Конвейеры (pipes) — это возможность нескольких программ работать совместно, когда выход одной программы непосредственно идет на вход другой без использования промежуточных временных файлов. Синтаксис:

команда1 | команда2

Выполняет команду1, используя её поток вывода как поток ввода при выполнении команды2.

Аналогичный результат можно было бы получить при помощи двух перенаправлений и временного файла:

команда1 > ВременныйФайл
команда2 < ВременныйФайл
rm ВременныйФайл

Недостатки такого способа очевидны:

  • обработка становится строго последовательной, а не параллельной;
  • создаётся временный файл на диске, который может иметь большой размер;
  • медленнее за счёт дискового I/O.

Именованные каналы

Именованный канал (named pipe) или именованный конвейер — один из методов межпроцессного взаимодействия, расширение понятия конвейера в UNIX-подобных ОС. Именованный канал позволяет различным процессам обмениваться данными, даже если программы, выполняющиеся в этих процессах, изначально не были написаны для взаимодействия с другими программами. Это понятие также существует и в Microsoft Windows, хотя там его семантика существенно отличается.

Традиционный канал — «безымянен», потому что существует анонимно и только во время выполнения процесса. Именованный канал — существует в системе и после завершения процесса. Он должен быть «отсоединён» или удалён, когда уже не используется. Процессы обычно подсоединяются к каналу для осуществления взаимодействия между ними.

Вместо традиционного, безымянного конвейера оболочки (shell pipeline), именованный канал создаётся явно с помощью mknod или mkfifo, и два различных процесса могут обратиться к нему по имени, как к файлу.

Например, можно создать канал и настроить gzip на сжатие того, что туда попадает:

mkfifo pipe
gzip -9 -c < pipe > out

Параллельно в другом процессе можно выполнить:

cat file > pipe

Это приведёт к сжатию передаваемых данных gzip-ом.

Цепочка конвейеров

Команды перенаправления и конвейеризации могут быть объединены в цепочки для получения более сложных команд.

К примеру:

ls | grep '.sh' | sort > shlist

Эта команда получает список содержимого текущего каталога, который фильтруется, оставляя только строки, содержащие '.sh', затем этот отфильтрованный список лексически сортируется, и окончательный результат помещается в файл shlist.

# Merges and sorts all ".txt" files, then deletes duplicate lines.
cat *.txt | sort | uniq

Конструкции подобного типа часто встречаются в скриптах.

Bash-specific

Упрощённая форма записи команды

команда > файл 2>&1

выглядит так:

команда &> файл

или

команда >& файл

Также есть специальный синтаксис для объединения stdout и stderr в конвейере. Вместо

команда1 2>&1 | команда2

можно писать

команда1 |& команда2

Перенаправление в несколько выводов

tee (от англ. "тройник" для соединения труб) — команда, выводит на экран, или же перенаправляет выходной материал команды и копирует его в файл или в переменную.

ls -l | tee file.txt | less

220px-Tee.svg.png

  • Чтобы одновременно увидеть и сохранить выходные данные:
lint program.c | tee program.lint

Эта команда покажет стандартные выходящие данные команды lint program.c на средстве вывода, в то же самое время она сохранит полученные данные в файл program.lint. Если такой файл уже существует, он будет переписан.

  • Чтобы получить данные от команды и дописать их в существующий файл:
lint program.c | tee -a program.lint

Как и в предыдущем примере, на экран будут выведены данные от команды lint program.c, после чего эти данные будут дописаны в конец файла program.lint. Если такой файл не существует, он будет создан.

  • Использование совместно с sudo. Такой способ не работает (от имени суперпользователя выполняется echo):
sudo echo "Body of file..." > root_owned_file

Можно делать так:

echo "Body of file..." | sudo tee root_owned_file > /dev/null

В этом примере демонстрируется, как при помощи tee обойти ограничения команды sudo, за счёт которых она не может перенаправлять вывод в файл. А дальнейшее перенаправление в /dev/null позволит избежать вывода текста на консоль.

Перенаправление в себя

Частая ошибка новичков: команда

sed 's/foo/bar/' file >file

или

cat file | sort > file

не работает, получается пустой файл.

Мы хотим изменить файл, используя что-то, что читает из файла и записывает результат в stdout. Для этого мы перенаправляем stdout в файл, который мы хотим изменить. Проблема здесь в том, что перенаправления настраиваются до того, как команда фактически выполняется.

Таким образом, до начала запуска sed стандартный вывод уже перенаправлен. При перенаправлении через > файл очищается (truncate). Когда sed начинает читать файл, он уже пуст.

Пример

Перенаправление stdout и stderr

Напишем программу main.c:

#include <stdio.h>
 
int main() {
    fprintf(stdout, "Hello to stdout\n");
    fprintf(stderr, "Hello to stderr\n");
    return 0;
}

Далее попробуем перенаправления:

$ gcc main.c
$ ./a.out
Hello to stdout
Hello to stderr
$ ./a.out >&2
Hello to stdout
Hello to stderr
$ (./a.out >&2) 2> err.txt
$ cat err.txt 
Hello to stderr
Hello to stdout

Создание пустого файла

$ :> newfile
$ > newfile

Создание файла с текстом

Запись данных в файл со стандартного ввода (завершение ввода по Ctrl+D):

$ cat > newfile
blah blah

Специальные устройства

$ echo "Hello" > /dev/null
$ echo "Hello" > /dev/full
bash: echo: write error: No space left on device

Литература