Как стать автором
Обновить

Комментарии 26

Linux: перенаправление

А почему такое краткое, ни о чём не говорящее, название?


И, если не ошибаюсь, всё вышеизложенное справедливо не только для оболочек Linux, BSD и т.п., но даже и для командной строки Windows.

Ошибаетесь. В статье приведены примеры, использующие конструкции, специфичные для bash:


comm <(sort list1.txt) <(sort list2.txt)
find / -name wireless &> results.txt

Кстати, ru_vds, стоит обратить внимание возле этих примеров на башеспецифичность. А то бывает люди напарываются, когда пишут в shebang #!/bin/sh, проверяют на системе, в которой /bin/sh -> bash, а потом скрипт внезапно не работает на другой системе, где /bin/sh не bash.

Эм… А разве bash специфичен для Linux? Он ведь работает в уйме других операционных систем.

Ваш ответ/замечание не противоречит тому, что написал Self_Perfection: баш-специфичность и платформозависимость/специфичность — понятия абсолютно ортогональные, можно сидеть как под линуксом в csh, так и под макосью в bash, как примитивный пример.
«для командной строки Windows.»
«специфичные для bash»

В первом случае подразумевается не платформа windows, а командная строка windows (cmd.exe).
Специфичен. bash всего лишь скомпилирован под многие ОС, в т.ч. имеющих другую философию и совершенно другое окружение. Вы можете запустить bash на Plan 9, а дальше у Вас будет только один вопрос: и что теперь? Возможность, конечно, имеется. Как заметил, Self_Perfection, проблемы начинаются уже на симлинке /bin/sh в Linux.
Для Windows перенаправление stderr в stdout имеет вид:
command >out.txt 2>&1
Да, зря выкинули каламбур из названия исходной статьи. Тем более, что перевести его очень просто (что-то типа «Двигаемся (или „двигаясь“, или „идём“) в правильном (пере)направлении»).
В варианте > и < — да, но в *nix перенаправлений гораздо больше и работают они лучше, чем в статье.
Даже если поставить какой-нить git-bash и выполнять шелл-скрипты в нем, эта виртуализация настолько замедляет процесс, что простой скрипт используемый парочку конвейеров, и в линуксе выполняющийся за пару секунд, в винде может выполняться десятки секунд.

И да, — в *nix есть возможность обращаться к различным устройствам и процессам через имя файла на виртуальных файловых системах, типа /dev/null, /dev/random/
и например вывести в чужой STDOUT например так:
echo hello >> /proc/xxx/fd/1

Там и дальше неверно:


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

никакого «оболочка сначала обрабатывает …» нет, в случае с process substitution она просто создаст подоболочку, соединит stdout (или stdin при >()) подоболочки с некоторым дескриптором основной оболочки, заменит <()/>() на «путь» к этому самому дескриптору и продолжит выполнение.


Главное тут то, что выполняться все три команды из примера будут параллельно. Можно проверить так:


  1. time bash -c 'echo <(sleep 1) <(sleep 1)' завершится немедленно, выведя эти самые пути*. Если бы оболочка сначала обрабатывала что‐либо в скобках, то time показал бы как минимум одну секунду.
  2. 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.

Первый — это стандартный поток ввода (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. Также можно было все-таки указать все виды перенаправлений, включая
"<<", "<<<" и конечно главную фичу *никс консоли "|".
Подскажите, пожалуйста, как нагуглить про эту главную фичу? Я видел её в скриптах, но ни черта не понял. Быстрый поиск не помог, я даже не в курсе, как этот символ в никсах называется, я в основном с вендой работаю.
(anonymous) pipe / pipeline / конвейер
Это называется «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`

И работать с ним в циклах или условиях.

cat здесь не нужен, sort сам умеет считывать данные из файлов.

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

Если отталкиваться от результата, тут sort вообще не делает ничего полезного, и если все оптимизировать, то достаточно только grep и wc
Именно поэтому использование "&>" небезопасно.
Если нужно перенаправить 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 такое писать нельзя.

Если вы пишете скрипт, в котором вы открыли еще какой-то файл (еще один дескриптор), то &> будет перенаправлять не только STDOUT и STDERR, но еще и этот дескриптор.
Я не говорю про «2>&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
% # Здесь пусто

Не могли бы вы написать пример скрипта, в котором демонстрируется описываемая проблема?

Спасибо за пинок. 30 минут сделали меня мудрее. Действительно нет никакой проблемы с дескрипторами.

Для полного счастья не хватает команды tee, которая дублирует стандартный вывод в файл:


$date |tee tektate.txt
Вт авг 29 15:00:00 MSK 2017
$

Сравните:


$cat tektate.txt
Вт авг 29 15:00:00 MSK 2017
$
не совсем так — tee позволяет отобразить вывод в файл И в терминал
например, когда во время работы скрипта пользователь должен видеть вывод, но в то же время этот вывод нужен для дальнейшей работы скрипта
tee позволяет размножить стандартный вывод в несколько потоков. По умолчанию размножается в указанный файл и STDOUT, но можно
date | tee -a file1.txt file2.txt
и получим вывод и на экран и в два файла.

Все так!

Я бы еще добавил в статью именование потоков ввода/вывода. Ипользуется, например, чтобы из подпроцесса вытащить stderr наружу.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий