Комментарии 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? Он ведь работает в уйме других операционных систем.
«специфичные для bash»
В первом случае подразумевается не платформа windows, а командная строка windows (cmd.exe).
command >out.txt 2>&1
Даже если поставить какой-нить git-bash и выполнять шелл-скрипты в нем, эта виртуализация настолько замедляет процесс, что простой скрипт используемый парочку конвейеров, и в линуксе выполняющийся за пару секунд, в винде может выполняться десятки секунд.
И да, — в *nix есть возможность обращаться к различным устройствам и процессам через имя файла на виртуальных файловых системах, типа /dev/null, /dev/random/
и например вывести в чужой STDOUT например так:
echo hello >> /proc/xxx/fd/1
comm <(sort list1.txt) <(sort list2.txt)это не перенаправление, а process substitution: tldp.org/LDP/abs/html/process-sub.html
Там и дальше неверно:
Круглые скобки тут имеют тот же смысл, что и в математике. Оболочка сначала обрабатывает команды в скобках, а затем всё остальное. В нашем примере сначала производится сортировка строк из файлов, а потом то, что получилось, передаётся команде 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
.
Первый — это стандартный поток ввода (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. Также можно было все-таки указать все виды перенаправлений, включая
"<<", "<<<" и конечно главную фичу *никс консоли "|".
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`
И работать с ним в циклах или условиях.
Именно поэтому использование "&>" небезопасно.
Если нужно перенаправить 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
такое писать нельзя.
Я не говорю про «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
% # Здесь пусто
Не могли бы вы написать пример скрипта, в котором демонстрируется описываемая проблема?
Для полного счастья не хватает команды tee, которая дублирует стандартный вывод в файл:
$date |tee tektate.txt
Вт авг 29 15:00:00 MSK 2017
$
Сравните:
$cat tektate.txt
Вт авг 29 15:00:00 MSK 2017
$
например, когда во время работы скрипта пользователь должен видеть вывод, но в то же время этот вывод нужен для дальнейшей работы скрипта
date | tee -a file1.txt file2.txt
и получим вывод и на экран и в два файла.
Linux: перенаправление