Как работает 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 Документация по перенаправлению, Объяснение файла Таблицы дескрипторов, Введение в указатели