Для оболочки, интерпретатора команд Linux, эти дополнительные символы — не пустая трата места на экране. Они — мощные команды, которые могут связывать воедино различные фрагменты информации, разделять то, что было до этого цельным, и делать ещё много всего. Одна из самых простых, и, в то же время, мощных и широко используемых возможностей оболочки — это перенаправление стандартных потоков ввода/вывода.
Три стандартных потока ввода/вывода
Для того, чтобы понять то, о чём мы будем тут говорить, важно знать, откуда берутся данные, которые можно перенаправлять, и куда они идут. В Linux существует три стандартных потока ввода/вывода данных.
Первый — это стандартный поток ввода (standard input). В системе это — поток №0 (так как в компьютерах счёт обычно начинается с нуля). Номера потоков ещё называют дескрипторами. Этот поток представляет собой некую информацию, передаваемую в терминал, в частности — инструкции, переданные в оболочку для выполнения. Обычно данные в этот поток попадают в ходе ввода их пользователем с клавиатуры.
Второй поток — это стандартный поток вывода (standard output), ему присвоен номер 1. Это поток данных, которые оболочка выводит после выполнения каких-то действий. Обычно эти данные попадают в то же окно терминала, где была введена команда, вызвавшая их появление.
И, наконец, третий поток — это стандартный поток ошибок (standard error), он имеет дескриптор 2. Этот поток похож на стандартный поток вывода, так как обычно то, что в него попадает, оказывается на экране терминала. Однако, он, по своей сути, отличается от стандартного вывода, как результат, этими потоками, при желании, можно управлять раздельно. Это полезно, например, в следующей ситуации. Есть команда, которая обрабатывает большой объём данных, выполняя сложную и подверженную ошибкам операцию. Нужно, чтобы полезные данные, которые генерирует эта команда, не смешивались с сообщениями об ошибках. Реализуется это благодаря раздельному перенаправлению потоков вывода и ошибок.
Как вы, вероятно, уже догадались, перенаправление ввода/вывода означает работу с вышеописанными потоками и перенаправление данных туда, куда нужно программисту. Делается это с использованием символов
>
и <
в различных комбинациях, применение которых зависит от того, куда, в итоге, должны попасть перенаправляемые данные.Перенаправление стандартного потока вывода
Предположим, вы хотите создать файл, в который будут записаны текущие дата и время. Дело упрощает то, что имеется команда, удачно названная
date
, которая возвращает то, что нам нужно. Обычно команды выводят данные в стандартный поток вывода. Для того, чтобы эти данные оказались в файле, нужно добавить символ >
после команды, перед именем целевого файла. До и после >
надо поставить пробел.При использовании перенаправления любой файл, указанный после
>
будет перезаписан. Если в файле нет ничего ценного и его содержимое можно потерять, в нашей конструкции допустимо использовать уже существующий файл. Обычно же лучше использовать в подобном случае имя файла, которого пока не существует. Этот файл будет создан после выполнения команды. Назовём его date.txt
. Расширение файла после точки обычно особой роли не играет, но расширения помогают поддерживать порядок. Итак, вот наша команда:$ date > date.txt
Нельзя сказать, что сама по себе эта команда невероятно полезна, однако, основываясь на ней, мы уже можем сделать что-то более интересное. Скажем, вы хотите узнать, как меняются маршруты вашего трафика, идущего через интернет к некоей конечной точке, ежедневно записывая соответствующие данные. В решении этой задачи поможет команда
traceroute
, которая сообщает подробности о маршруте трафика между нашим компьютером и конечной точкой, задаваемой при вызове команды в виде URL. Данные включают в себя сведения обо всех маршрутизаторах, через которые проходит трафик.Так как файл с датой у нас уже есть, будет вполне оправдано просто присоединить к этому файлу данные, полученные от
traceroute
. Для того, чтобы это сделать, надо использовать два символа >
, поставленные один за другим. В результате новая команда, перенаправляющая вывод в файл, но не перезаписывающая его, а добавляющая новые данные после старых, будет выглядеть так:$ traceroute google.com >> date.txt
Теперь нам осталось лишь изменить имя файла на что-нибудь более осмысленное, используя команду
mv
, которой, в качестве первого аргумента, передаётся исходное имя файла, а в качестве второго — новое:$ mv date.txt trace1.txt
Перенаправление стандартного потока ввода
Используя знак
<
вместо >
мы можем перенаправить стандартный ввод, заменив его содержимым файла.Предположим, имеется два файла:
list1.txt
и list2.txt
, каждый из которых содержит неотсортированный список строк. В каждом из списков имеются уникальные для него элементы, но некоторые из элементов список совпадают. Мы можем найти строки, которые имеются и в первом, и во втором списках, применив команду comm
, но прежде чем её использовать, списки надо отсортировать.Существует команда
sort
, которая возвращает отсортированный список в терминал, не сохраняя отсортированные данные в файл, из которого они были взяты. Можно отправить отсортированную версию каждого списка в новый файл, используя команду >
, а затем воспользоваться командой comm
. Однако, такой подход потребует как минимум двух команд, хотя то же самое можно сделать в одной строке, не создавая при этом ненужных файлов.Итак, мы можем воспользоваться командой
<
для перенаправления отсортированной версии каждого файла команде comm
. Вот что у нас получилось:$ comm <(sort list1.txt) <(sort list2.txt)
Круглые скобки тут имеют тот же смысл, что и в математике. Оболочка сначала обрабатывает команды в скобках, а затем всё остальное. В нашем примере сначала производится сортировка строк из файлов, а потом то, что получилось, передаётся команде
comm
, которая затем выводит результат сравнения списков.Перенаправление стандартного потока ошибок
И, наконец, поговорим о перенаправлении стандартного потока ошибок. Это может понадобиться, например, для создания лог-файлов с ошибками или объединения в одном файле сообщений об ошибках и возвращённых некоей командой данных.
Например, что если надо провести поиск во всей системе сведений о беспроводных интерфейсах, которые доступны пользователям, у которых нет прав суперпользователя? Для того, чтобы это сделать, можно воспользоваться мощной командой
find
.Обычно, когда обычный пользователь запускает команду
find
по всей системе, она выводит в терминал и полезные данные и ошибки. При этом, последних обычно больше, чем первых, что усложняет нахождение в выводе команды того, что нужно. Решить эту проблему довольно просто: достаточно перенаправить стандартный поток ошибок в файл, используя команду 2> (напомним, 2 — это дескриптор стандартного потока ошибок). В результате на экран попадёт только то, что команда отправляет в стандартный вывод:$ find / -name wireless 2> denied.txt
Как быть, если нужно сохранить результаты работы команды в отдельный файл, не смешивая эти данные со сведениями об ошибках? Так как потоки можно перенаправлять независимо друг от друга, в конец нашей конструкции можно добавить команду перенаправления стандартного потока вывода в файл:
$ find / -name wireless 2> denied.txt > found.txt
Обратите внимание на то, что первая угловая скобка идёт с номером —
2>
, а вторая без него. Это так из-за того, что стандартный вывод имеет дескриптор 1, и команда >
подразумевает перенаправление стандартного вывода, если номер дескриптора не указан.И, наконец, если нужно, чтобы всё, что выведет команда, попало в один файл, можно перенаправить оба потока в одно и то же место, воспользовавшись командой
&>
:$ find / -name wireless &> results.txt
Итоги
Тут мы разобрали лишь основы механизма перенаправления потоков в интерпретаторе командной строки Linux, однако даже то немногое, что вы сегодня узнали, даёт вам практически неограниченные возможности. И, кстати, как и всё остальное, что касается работы в терминале, освоение перенаправления потоков требует практики. Поэтому рекомендуем вам приступить к собственным экспериментам с
>
и <
.Уважаемые читатели! Знаете ли вы интересные примеры использования перенаправления потоков в Linux, которые помогут новичкам лучше освоиться с этим приёмом работы в терминале?
Комментарии (24)
kt97679
28.08.2017 17:09+2comm <(sort list1.txt) <(sort list2.txt)
это не перенаправление, а process substitution: tldp.org/LDP/abs/html/process-sub.htmlZyXI
28.08.2017 19:06Там и дальше неверно:
Круглые скобки тут имеют тот же смысл, что и в математике. Оболочка сначала обрабатывает команды в скобках, а затем всё остальное. В нашем примере сначала производится сортировка строк из файлов, а потом то, что получилось, передаётся команде comm, которая затем выводит результат сравнения списков.
никакого «оболочка сначала обрабатывает …» нет, в случае с process substitution она просто создаст подоболочку, соединит stdout (или stdin при
>()
) подоболочки с некоторым дескриптором основной оболочки, заменит<()
/>()
на «путь» к этому самому дескриптору и продолжит выполнение.
Главное тут то, что выполняться все три команды из примера будут параллельно. Можно проверить так:
time bash -c 'echo <(sleep 1) <(sleep 1)'
завершится немедленно, выведя эти самые пути*. Если бы оболочка сначала обрабатывала что?либо в скобках, тоtime
показал бы как минимум одну секунду.time bash -c 'cat <(sleep 1) <(sleep 1)'
покажет одну секунду, а не две: команды обрабатываются параллельно.
* У меня почему?то
/dev/fd/63 /dev/fd/62
: аналогичная команда zsh, во?первых, выведет пути по порядку, во?вторых, возьмёт дескрипторы поменьше и, в третьих, использует/proc/self/fd
(хотя я не знаю, что менее вероятно: отсутствие/dev/fd
или/proc/self/fd
, так что это не принципиально):/proc/self/fd/12 /proc/self/fd/13
. «Почему?то» относится в первую очередь к «выведет пути по порядку»:strace -e execve -f $shell -c 'echo <(sleep 1) <(sleep 2)'
явно показывает, что zsh сначала пуститsleep 1
, а bash сначала пуститsleep 2
.
saboteur_kiev
28.08.2017 17:31+2Первый — это стандартный поток ввода (standard input). В системе это — поток №0
Номера потоков ещё называют дескрипторами
Второй поток — это стандартный поток вывода (standard output), ему присвоен номер 1.
Это поток данных, которые оболочка выводит после выполнения каких-то действий.
И, наконец, третий поток — это стандартный поток ошибок (standard error), он имеет дескриптор 2.
Давайте все же грамотно формулизируем.
1. Для каждой запускаемой CLI оболочки, по умолчанию открывается три дескриптора #0 — STDIN, #1 — STDOUT, #2 — STDERR, связанные с файлами-устройств, где STDOUT и STDERR изначально выводят на «экран»
Потоки не называются дескрипторами, они ими являются. Если я в моем баш скрипте открою какой-либо файл или еще один поток, он будет #3, #4 и так далее.
Именно поэтому использование "&>" небезопасно.
Если нужно перенаправить stdout и stderr, обычно пишут ">file.log 2>&1"
2. Можно также уточнить, что STDOUT и STDERR это не тот поток данных, которые «оболочка выводит после выполнения каких-либо действий.»
Это программист, который написал свою программу/утилиту должен выводить информацию пользователю используя print(STDOUT, «text»), либо print(STDERR,«error text»). Или не писать никуда, тогда программа/команда ничего и не выведет на экран. Или писать сразу в лог-файл (можно также помнить, что многие команды и скрипты могут запускать без оболочки, например через cron, где вместо stdout будет локальная почта. Или через rsh, где stdout вообще не будет и будет ошибка.
3. Также можно было все-таки указать все виды перенаправлений, включая
"<<", "<<<" и конечно главную фичу *никс консоли "|".evvlasov
29.08.2017 18:00Подскажите, пожалуйста, как нагуглить про эту главную фичу? Я видел её в скриптах, но ни черта не понял. Быстрый поиск не помог, я даже не в курсе, как этот символ в никсах называется, я в основном с вендой работаю.
saboteur_kiev
30.08.2017 12:18Это называется «pipe», или «конвейер», когда вы перенаправляете вывод на ввод между программами. И так можно много раз. Например
cat file.txt | sort -r | cut -d ":" -f 1,3 | grep «line1» | wc -l
* выводим file.txt и перенаправляем его в
* сортировку наоборот, отсортированный текст перенаправляем в
* команду cut, которая берет 1 и 3 столбец и выводит дальше в
* grep, который отсекает все, кроме строк где есть «line1» и перенаправляет в
* wc который просто считает количество строк и полученное число перенаправляет в
* stdout, который уже без дальнейших перенаправлений выводится пользователю.
А вот так можно это число записать в числовую переменную:
typedef -i LINE1_LINES=`cat file.txt | sort -r | cut -d ":" -f 1,3 | grep «line1» | wc -l`
И работать с ним в циклах или условиях.Apcel
30.08.2017 16:52cat здесь не нужен, sort сам умеет считывать данные из файлов.
saboteur_kiev
30.08.2017 18:26Это искусственный пример множественных перенаправлений, а не рабочая команда.
Если отталкиваться от результата, тут sort вообще не делает ничего полезного, и если все оптимизировать, то достаточно только grep и wc
ZyXI
29.08.2017 23:16Именно поэтому использование "&>" небезопасно.
Если нужно перенаправить stdout и stderr, обычно пишут ">file.log 2>&1"Чем оно «небезопасно»? Сравните
ls -lAh /proc/self/fd
в случаях&>/tmp/1.la
и>/tmp/2.la 2>&1
:
итого 0 lrwx------ 1 zyx 64 авг 29 23:04 0 -> /dev/pts/6 l-wx------ 1 zyx 64 авг 29 23:04 1 -> /tmp/1.la l-wx------ 1 zyx 64 авг 29 23:04 2 -> /tmp/1.la lr-x------ 1 zyx 64 авг 29 23:04 3 -> /proc/6017/fd/ l-wx------ 1 zyx 64 авг 29 23:04 4 -> pipe:[21043] итого 0 lrwx------ 1 zyx 64 авг 29 23:04 0 -> /dev/pts/6 l-wx------ 1 zyx 64 авг 29 23:04 1 -> /tmp/2.la l-wx------ 1 zyx 64 авг 29 23:04 2 -> /tmp/2.la lr-x------ 1 zyx 64 авг 29 23:04 3 -> /proc/5833/fd/ l-wx------ 1 zyx 64 авг 29 23:04 4 -> pipe:[21043]
Абсолютно никакой разницы. Про буферизацию точно не скажу, но ей вроде libc занимается и куда ведут дескрипторы ей глобально всё равно: для stdout буферизацию она всё равно сделает, а для stderr нет, пока не попросите обратного, тогда как с
write()
буферизации не будет вообще, какой бы дескриптор вы не взяли. Какая ещё разница возможна между данными конструкциями я не представляю.
&>
писать проще, быстрее и понятнее (я длительное время не понимал, почему2>&1
нужно писать после: нужно просто дескрипторы воспринимать как ссылки, а перенаправление2>&1
как присваивание по ссылке 2 значения по ссылке 1). Есть только одна проблема: это bash’изм, с#!/bin/sh
такое писать нельзя.saboteur_kiev
30.08.2017 12:19Если вы пишете скрипт, в котором вы открыли еще какой-то файл (еще один дескриптор), то &> будет перенаправлять не только STDOUT и STDERR, но еще и этот дескриптор.
Я не говорю про «2>&1», я говорю именно про "&>"ZyXI
31.08.2017 18:44+1Как?то такого не замечаю:
% bash -c 'exec 5>/tmp/test3fd ; ls -lAh /proc/self/fd &>/tmp/test3fdredir' % cat /tmp/test3fdredir итого 0 lrwx------ 1 zyx zyx 64 авг 31 18:41 0 -> /dev/pts/6 l-wx------ 1 zyx zyx 64 авг 31 18:41 1 -> /tmp/test3fdredir l-wx------ 1 zyx zyx 64 авг 31 18:41 2 -> /tmp/test3fdredir lr-x------ 1 zyx zyx 64 авг 31 18:41 3 -> /proc/8502/fd l-wx------ 1 zyx zyx 64 авг 31 18:41 4 -> pipe:[21043] l-wx------ 1 zyx zyx 64 авг 31 18:41 5 -> /tmp/test3fd % cat /tmp/test3fd % # Здесь пусто
Не могли бы вы написать пример скрипта, в котором демонстрируется описываемая проблема?
saboteur_kiev
01.09.2017 14:37Спасибо за пинок. 30 минут сделали меня мудрее. Действительно нет никакой проблемы с дескрипторами.
saipr
29.08.2017 15:05Для полного счастья не хватает команды tee, которая дублирует стандартный вывод в файл:
$date |tee tektate.txt Вт авг 29 15:00:00 MSK 2017 $
Сравните:
$cat tektate.txt Вт авг 29 15:00:00 MSK 2017 $
Lennonenko
30.08.2017 02:15не совсем так — tee позволяет отобразить вывод в файл И в терминал
например, когда во время работы скрипта пользователь должен видеть вывод, но в то же время этот вывод нужен для дальнейшей работы скрипта
saboteur_kiev
30.08.2017 12:21tee позволяет размножить стандартный вывод в несколько потоков. По умолчанию размножается в указанный файл и STDOUT, но можно
date | tee -a file1.txt file2.txt
и получим вывод и на экран и в два файла.
Akon32
А почему такое краткое, ни о чём не говорящее, название?
И, если не ошибаюсь, всё вышеизложенное справедливо не только для оболочек Linux, BSD и т.п., но даже и для командной строки Windows.
Self_Perfection
Ошибаетесь. В статье приведены примеры, использующие конструкции, специфичные для bash:
Кстати, ru_vds, стоит обратить внимание возле этих примеров на башеспецифичность. А то бывает люди напарываются, когда пишут в shebang
#!/bin/sh
, проверяют на системе, в которой/bin/sh -> bash
, а потом скрипт внезапно не работает на другой системе, где /bin/sh не bash.asergrisa
Эм… А разве bash специфичен для Linux? Он ведь работает в уйме других операционных систем.
siziyman
Ваш ответ/замечание не противоречит тому, что написал Self_Perfection: баш-специфичность и платформозависимость/специфичность — понятия абсолютно ортогональные, можно сидеть как под линуксом в csh, так и под макосью в bash, как примитивный пример.
saboteur_kiev
«для командной строки Windows.»
«специфичные для bash»
В первом случае подразумевается не платформа windows, а командная строка windows (cmd.exe).
hdfan2
Для Windows перенаправление stderr в stdout имеет вид:
hdfan2
Да, зря выкинули каламбур из названия исходной статьи. Тем более, что перевести его очень просто (что-то типа «Двигаемся (или „двигаясь“, или „идём“) в правильном (пере)направлении»).
saboteur_kiev
В варианте > и < — да, но в *nix перенаправлений гораздо больше и работают они лучше, чем в статье.
Даже если поставить какой-нить git-bash и выполнять шелл-скрипты в нем, эта виртуализация настолько замедляет процесс, что простой скрипт используемый парочку конвейеров, и в линуксе выполняющийся за пару секунд, в винде может выполняться десятки секунд.
И да, — в *nix есть возможность обращаться к различным устройствам и процессам через имя файла на виртуальных файловых системах, типа /dev/null, /dev/random/
и например вывести в чужой STDOUT например так:
echo hello >> /proc/xxx/fd/1