Большинство оболочек позволяют изменять способ ввода и вывода данных приложения. Это может направлять вывод за пределы терминала в файлы или другие приложения, а также считывать ввод из файлов, а не с терминала.
Стандартный ввод и вывод
Прежде чем мы поймем, как работает перенаправление в оболочках, нам сначала нужно понять процесс стандартного ввода и вывода.
Все приложения имеют три уникальных потока, которые соединяют их с внешним миром. Они называются стандартный ввод, или stdin, стандартный вывод, или stdout, и стандартная ошибка, или stderr.
Стандартный ввод - это механизм по умолчанию для ввода данных в интерактивную программу. Обычно это прямая связь с клавиатурой при запуске непосредственно в терминале, и ни с чем другим он не связан.
Стандартный вывод - это механизм по умолчанию для записи вывода из программы. Обычно это ссылка на терминал вывода, но часто буферизируется по соображениям производительности. Это может быть особенно важно, когда программа работает по медленному сетевому каналу.
Стандартная ошибка - это альтернативный механизм вывода данных из программы. Обычно это другая ссылка на тот же терминал вывода, но без буферизации, так что мы можем записывать ошибки немедленно, в то время как обычный вывод может быть записан по одной строке за раз.
Перенаправление в файлы
При запуске приложений часто требуется направить вывод в файл, а не в терминал. Точнее, мы заменяем стандартный поток вывода на тот, который идет туда, куда нам нужно: В данном случае - в файл.
Обычно это делается с помощью оператора > между запускаемым приложением и файлом для записи вывода. Например, мы можем отправить вывод команды ls в файл под названием files следующим образом:
1 | ls > files |
Мы можем сделать это после любой команды, включая все необходимые аргументы:
1 | ls -1 *.txt > text-files |
Добавление в файлы
Когда мы используем >, то записываем вывод в файл, заменяя все его содержимое. При необходимости мы также можем добавить в файл, используя >>:
1 | ls -1 *.txt > files |
1 2 | ls -1 *.text >> files ls -1 *.log >> files |
При необходимости мы можем использовать это для создания файлов с помощью любого вида вывода, включая просто использование echo для точного вывода строк:
1 2 3 4 | echo "Текстовые файлы" > files ls -1 *.txt >> files echo "Файлы журналов" >> files ls -1 *.log >> files |
Перенаправление ошибки
Иногда нам нужно перенаправить стандартную ошибку вместо стандартного вывода. Это работает точно так же, но нам нужно указать точный поток.
Все три стандартных потока имеют значения ID, как определено в спецификации POSIX и используется в языке C. Стандартный ввод - это 0, стандартный вывод - это 1, а стандартная ошибка - это 2.
Когда мы используем операторы перенаправления, по умолчанию это относится к стандартному выводу. Однако мы можем явно указать поток для перенаправления, добавив к нему идентификатор потока.
Например, чтобы перенаправить стандартную ошибку, из команды cat мы используем 2>:
1 | cat not_exist 2> log |
Мы можем быть последовательными и использовать 1> для перенаправления стандартного вывода, если хотим, хотя это идентично тому, что мы видели ранее.
Мы также можем использовать &> для перенаправления стандартного вывода и стандартной ошибки одновременно:
1 | ls -1 &> log |
Это отправляет весь вывод команды, независимо от того, в каком потоке он находится, в один и тот же файл. Однако это работает только в определенных оболочках - например, мы можем использовать это в bash или zsh, но не в sh.
Перенаправление в потоки
Иногда мы хотим перенаправить не в файл, а в поток. Этого можно добиться, используя вместо имени файла идентификатор потока с префиксом &. Например, мы можем перенаправить в стандартную ошибку, используя >&2:
1 2 | echo Стандартный выход >&1 echo Стандартная ошибка >&2 |
Мы можем объединить это с вышеописанным для объединения потоков, перенаправляя из одного потока в другой. Распространенной конструкцией является объединение стандартной ошибки в стандартный вывод, чтобы оба потока можно было использовать вместе. Мы достигаем этого с помощью 2>&1 - буквально перенаправляя поток 2 в поток 1:
1 | ls -1 2>&1 |
Иногда мы можем использовать это для создания новых потоков, просто используя новые идентификаторы. Однако этот поток должен быть уже использован в другом месте, иначе это будет ошибкой. Чаще всего он используется в качестве источника потока.
Например, мы можем поменять местами стандартный вывод и стандартную ошибку, пройдя через третий поток, используя 3>&2 2>&1 1>&3:
1 | ls -1 3>&2 2>&1 1>&3 |
Эта конструкция работает правильно не во всех оболочках. В bash конечным результатом является то, что стандартный вывод и стандартная ошибка напрямую меняются местами. В zsh результатом будет то, что и стандартный вывод, и стандартная ошибка окажутся на стандартном выводе.
Перенаправление нескольких потоков
Мы можем легко объединить вышеописанное, чтобы перенаправить стандартный вывод и стандартную ошибку одновременно. В основном это работает именно так, как мы и ожидали - мы просто объединяем два разных перенаправления в одной команде:
1 | ls -1 > stdout.log 2> stderr.log |
Это не будет работать так, как хотелось бы, если мы попытаемся перенаправить оба потока в один и тот же файл. Здесь происходит то, что оба потока перенаправляются по отдельности, и побеждает тот, который придет вторым, вместо того, чтобы объединить оба в один файл.
Если мы хотим перенаправить оба потока в один файл, то мы можем использовать &>, как мы видели выше, или же мы можем использовать операторы объединения потоков. Если мы хотим использовать операторы объединения потоков, то мы должны сделать это после перенаправления в файл, иначе будет перенаправлен только стандартный вывод:
1 | ls -1 > log 2>&1 |
Чтение из файлов
Иногда мы хотим добиться и обратного - перенаправить файл в приложение. Для этого мы используем оператор <, и содержимое файла заменит стандартный ввод приложения:
1 | wc < /usr/share/dict/words |
Когда мы это сделаем, единственный входной сигнал, который может получить приложение, будет поступать из этого источника, и все это будет происходить немедленно. Это фактически то же самое, как если бы пользователь набирал все содержимое файла в самом начале работы приложения.
Однако конец файла также сигнализируется приложению, что многие приложения могут использовать для остановки обработки.
Передача данных между приложениями
Последнее действие, которое мы можем выполнить, - это направить вывод одного приложения в другое. Это обычно называется конвейеризацией, и вместо него используется оператор |:
1 | ls | wc |
Это напрямую соединяет стандартный вывод нашего первого приложения со стандартным вводом второго приложения, а затем позволяет данным напрямую перетекать между ними.
Работа со стандартной ошибкой
Стандартная ошибка по умолчанию не подключена, поэтому все, что записывается в этот поток, будет отображаться в консоли. Это сделано специально, поскольку стандартная ошибка предназначена для сообщения об ошибках, а не для обычного вывода приложения.
Если мы хотим перенаправить стандартную ошибку, мы можем использовать технику, описанную выше, чтобы сначала перенаправить ее в стандартный вывод, а затем передать в следующее приложение:
1 | ls dont-exist | wc |
1 | ls dont-exist 2>&1 | wc |
Если мы хотим передавать только стандартную ошибку, то нам нужно поменять местами потоки стандартного вывода и стандартной ошибки, как мы видели ранее.
1 | ls 3>&2 2>&1 1>&3 | wc -l |
1 | ls dont-exist 3>&2 2>&1 1>&3 | wc -l |
Объединение конвейеров
При передаче данных между приложениями мы также можем строить произвольные цепочки, в которых мы передаем данные между многими приложениями для достижения результата:
1 | docker images | cut -d' ' -f1 | tail -n +2 | sort -u | wc -l |
Эта команда выглядит пугающе, но мы можем разбить ее на отдельные части:
- docker images - Получить список всех образов Docker
- cut -d' ' -f1 - Обрезать вывод, чтобы вернуть только первый столбец, где столбцы разделены пробелами.
- tail -n +2 - Ограничить вывод, чтобы он начинался со строки 2
- sort -u - Сортировать этот список, возвращая только уникальные записи.
- wc -l - Подсчитать количество строк.
Итак, у нас есть команда для получения количества уникальных образов docker, игнорируя версию образа.
Многие консольные приложения предназначены именно для такого использования, поэтому они часто могут принимать входные данные из стандартного ввода и записывать их в стандартный вывод.
Некоторые приложения также имеют специальные режимы, которые позволяют это делать - например, в git есть так называемые фарфоровые и сантехнические команды, где сантехнические команды специально разработаны для комбинирования таким образом, а фарфоровые команды предназначены для человеческого потребления.
Заключение
Здесь мы рассмотрели несколько методов перенаправления ввода и вывода приложений между файлами или другими приложениями. Это методы, которые могут привести к сложным результатам на основе простых входных данных.