Как работает cmd>/dev/null 2> & 1?
Я читаю о перенаправлении данных на /dev/null
, и поэтому я попробовал простой тест:
ping a.b.c # which results in an address not found
Если я попробую это:
ping a.b.c > /dev/null # prints the same error message as the one above
Однако, если я это сделаю:
ping a.b.c > /dev/null 2>&1 # The error message is gone
Последнее решение - это желаемое решение, но что происходит с этим 2>&1
? Мои исследования пока показывают, что 2
представляет stderr
и 1
представляет stdout
. Итак, если я прочитал его таким образом, похоже, что я создаю файл stderr
и перенаправляю stdout
к нему?
Если это так, что делает &
в этой команде?
Ответы
Ответ 1
Вы правы, 2
- STDERR
, 1
- STDOUT
. Когда вы делаете 2>&1
, вы говорите: "напечатайте на STDOUT
(1
) вещи, которые перейдут на STDERR
(2
)". И до этого вы сказали, что ваш STDOUT
перейдет к /dev/null
. Поэтому ничего не видно. В примерах 1 и 2 вы получаете выходное сообщение, потому что оно печатается на STDERR
, поскольку регулярное перенаправление перенаправляет только STDOUT
.
И когда вы выполняете перенаправление, вы не создаете STDERR
, процессы всегда имеют STDERR
и STDOUT
, когда они создаются.
Ответ 2
Рассмотрим следующий код, который печатает слово "stdout" в stdout и слово "stderror" на stderror.
$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror
Обратите внимание, что '&' оператор сообщает bash, что 2 является файловым дескриптором (который указывает на stderr), а не именем файла. Если мы оставим "&", эта команда напечатает stdout
в stdout и создаст файл с именем "2" и напишет stderror
там.
Экспериментируя с приведенным выше кодом, вы можете сами убедиться, как работают операторы перенаправления. Например, путем изменения файла, который из двух дескрипторов 1,2, перенаправлен на /dev/null
, следующие две строки кода удаляют все из stdout и все из stderror соответственно (печать остается).
$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout
Теперь мы подходим к сути вопроса (подставляя мой пример для вашего), почему
(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1
нет выхода? Чтобы действительно понять это, я настоятельно рекомендую вам прочитать эту веб-страницу в таблицах дескрипторов файлов. Предполагая, что вы сделали это чтение, мы можем продолжить. Обратите внимание, что bash обрабатывает слева направо; таким образом, bash сначала видит >/dev/null
(что то же самое, что и 1>/dev/null
), и устанавливает дескриптор файла 1 для указания на /dev/null вместо stdout. Сделав это, bash затем движется вправо и видит 2>&1
. Это устанавливает дескриптор файла 2 для указания того же файла как дескриптор файла 1 (а не для самого дескриптора 1)!!! (см. этот ресурс на указателях). Поскольку дескриптор файла 1 указывает на /dev/null, а дескриптор файла 2 указывает на тот же файл, что и дескриптор файла 1, файловый дескриптор 2 теперь также указывает на /dev/null. Таким образом, оба дескриптора файла указывают на /dev/null, и поэтому результат не выводится.
Чтобы проверить, действительно ли вы поняли концепцию, постарайтесь угадать результат при переключении порядка перенаправления:
(echo "stdout"; echo "stderror" >&2) 2>&1 >/dev/null
stderror
Здесь рассуждение состоит в том, что при оценке слева направо bash видит 2 > & 1 и, таким образом, устанавливает дескриптор файла 2 в то же место, что и дескриптор файла 1, то есть stdout. Затем он устанавливает дескриптор файла 1 (помните, что > /dev/null = 1 > /dev/null), чтобы указать нa > /dev/null, тем самым удалив все, что обычно будет отправлено на стандартный выход. Таким образом, все, что нам осталось, это то, что не было отправлено на stdout в подоболочку (код в круглых скобках), то есть "stderror". Интересно отметить, что хотя 1 - это просто указатель на stdout, перенаправление указателя 2 на 1 через 2>&1
НЕ формирует цепочку указателей 2 → 1 → stdout. Если бы это произошло, в результате перенаправления 1 на /dev/null код 2>&1 >/dev/null
дал бы цепочку указателей 2 → 1 → /dev/null, и, следовательно, код ничего не создавал бы, в отличие от того, что мы видел выше.
Наконец, я хотел бы отметить, что есть более простой способ сделать это:
Из раздела 3.6.4 здесь, мы видим, что мы можем использовать оператор &>
для перенаправления как stdout, так и stderr. Таким образом, чтобы перенаправить вывод stderr и stdout любой команды на \dev\null
(который удаляет вывод), мы просто вводим
$ command &> /dev/null
или в случае моего примера:
$ (echo "stdout"; echo "stderror" >&2) &>/dev/null
Ключевые вынос:
- Файловые дескрипторы ведут себя как указатели (хотя дескрипторы файлов не совпадают с указателями файлов)
- Перенаправление дескриптора файла "a" в дескриптор файла "b", который указывает на файл "f", заставляет дескриптор файла "a" указывать на то же место, что и файловый дескриптор b - файл "f". Он НЕ образует цепочку указателей a → b → f
- Из-за вышеизложенного, порядок имеет значение,
2>&1 >/dev/null
is!= >/dev/null 2>&1
. Один генерирует вывод, а другой - нет.
Наконец, взгляните на эти большие ресурсы:
Bash Документация по перенаправлению, Объяснение файла Таблицы дескрипторов, Введение в указатели