Привет, Хабр! Конструкции вроде 2>&1 и &> встречаются повсюду - в мануалах, скриптах, инструкциях. Их используют постоянно, но редко понимают до конца. Почему ошибки продолжают появляться в терминале, хотя, казалось бы, должны уходить в файл? Почему конвейер передаёт только часть вывода?

В статье разберём всё от базового синтаксиса до работы с tee и /dev/null - каждая часть будет разобрана и показана на практических примерах.

Любая программа в Linux фактически ведёт диалог с окружающей её средой. Ей необходимо получать данные (ввод), отдавать результаты (вывод) и сообщать о проблемах (ошибки). Те, кто мало работает и/или только изучает Linux, зачастую сталкиваются с тем, что, к примеру, при запуске команды с > ошибки просто "пролетают" мимо файла и портят вывод. Или возникают проблемы с передачей ошибок через конвейер от одной программы - другой. За всё это отвечают 3 стандартных потока.

1. Дескрипторы

Любой процесс в Linux по умолчанию имеет три стандартных канала связи. У каждого из них есть номер - файловый дескриптор.

  • 0 - STDIN (Standard Input): поток ввода. По умолчанию "привязан" к Вашей клавиатуре. Когда программа читает данные, она читает их из stdin.

  • 1 - STDOUT (Standard Output): поток вывода. Сюда программа пишет свои основные результаты. По умолчанию он попадает в Ваш терминал.

  • 2 - STDERR (Standard Error): поток ошибок. Сюда программа пишет диагностические сообщения, предупреждения и ошибки. По умолчанию он тоже попадает в терминал, но это отдельный, независимый от stdout поток.

Разделение stdout и stderr - решение определенно гениальное. Оно позволяет отделить "полезные данные" от "служебных". То есть, можно перенаправить результаты работы программы/скрипта в файл, а ошибки вывести в терминал.

Посмотрим на практике:

# Команда выводит данные в stdout
echo "Всем привет от STDOUT!"

# Теперь сгенерируем ошибку, обратившись к несуществующему файлу
# Сообщение об ошибке пойдет в stderr
ls /несуществующий_файл

Оба сообщения появились в терминале. Визуально они неотличимы, но для системы это два разных потока.

Что такое файловый дескриптор?

Файловый дескриптор (File Descriptor, FD) - неотрицательное целое число (0, 1, 2, 3, ...), которое операционная система использует для уникальной идентификации открытого файла или другого ресурса ввода-вывода в контексте конкретного процесса.

Проще говоря, это индекс в таблице открытых файлов процесса.

2. Базовое перенаправление (> и 2>)

В этом пункте рассмотрим процесс управления потоками. Синтаксис в принципе прост и понятен: команда [N]> файл, где N - номер файлового дескриптора (то есть 0 - stdin, 1 - stdout, 2 - stderr). Если N не указан, по умолчанию используется 1 (stdout),

# Перенаправляем stdout команды echo в файл
echo "Этот текст уйдет в файл" > output.txt
cat output.txt

# Пытаемся перенаправить stdout команды ls, она создаст ошибку
ls /несуществующий_файл > stdout_only.txt

Мы увидим ошибку в терминале, файл stdout_only.txt будет пустым. Это потому, что мы перенаправили только stdout (дескриптор 1). Ошибка (дескриптор 2) продолжила уходить в терминал.

Исправляем ситуацию, перенаправив stderr:

# Перенаправляем только stderr в файл
ls /несуществующий_файл 2> errors.txt

Теперь ошибки в терминале не будет, она вся записана в файл errors.txt.

3. Объединение потоков

Возможно возник вопрос. А если мы хотим записать и обычный вывод, и вывод ошибки в один файл? Тут "на сцене" появляется конструкция M>&N, которая буквально означает: "сделай так, чтобы поток с дескриптором M перенаправлялся туда же, куда поток N".

Самая распространенная конструкция подобного плана - 2>&1. Она означает: "направь stderr (2) туда же, куда направлен stdout (1)".

# Пробуем снова. Направляем stdout в файл, а stderr - туда же, куда и stdout.
ls /несуществующий_файл > combined.txt 2>&1

На этот раз в терминале ничего не появилось. И stdout (который был пуст), и stderr были записаны в файл combined.txt.

Важно уточнить, что порядок важен. Команда интерпретируется слева направо.

  • > combined.txt - сначала мы говорим: "перенаправь stdout в файл combined.txt".

  • 2>&1 - затем мы говорим: "а теперь перенаправь stderr туда же, куда сейчас смотрит stdout". А stdout в этот момент уже смотрит в файл.

Если бы мы написали наоборот: ls /несуществующий_файл 2>&1 > combined.txt, это сработало бы иначе:

  1. 2>&1 - "отправь stderr туда же, куда stdout". На данный момент stdout смотрит в терминал. Значит, stderr тоже отправляется в терминал.

  2. > combined.txt - "теперь перенаправь stdout в файл". Но stderr продолжает смотреть в терминал...

Именно из-за неправильного порядка команда some_command 2>&1 > file.txt не даст ожидаемого результата. Ошибки будут в терминале.

4. Сокращения и особые случаи

4.1. Сокращение &>
Для удобства в bash есть сокращённая запись для одновременного перенаправления и stdout, и stderr: &>.

# Эта запись эквивалента "> file.txt 2>&1"
ls /несуществующий_файл &> combined_v2.txt

4.2. Как работает конвейер (|)
Конвейер по умолчанию передаёт только stdout следующей команде. Stderr при этом идёт прямиком в терминал.

# Сгенерируем ошибку и попробуем передать её через конвейер grep
ls /несуществующий_файл /etc/hosts | grep "hosts"

Мы увидим сообщение об ошибке от ls (это stderr), а также результат успешного выполнения (файл /etc/hosts), отфильтрованный grep (это stdout, который прошел по конвейеру).

Возник вопрос, как передать stderr через конвейер? Необходимо перенаправить его в stdout перед передачей:

# Теперь и ошибка, и вывод пойдут в grep
ls /несуществующий_файл /etc/hosts 2>&1 | grep "несуществующий"

Подведение итогов

Попробуем собрать всё описанное в одной практической команде. Поставим абстрактную цель: выполнить команду, которая и найдёт, и не найдёт файлы. Записать весь её вывод (и ошибки, и обычный вывод) в один файл, а также посчитать общее количество строк в этом выводе.

Итоговая команда:

ls /etc/hosts /несуществующий_файл /etc/passwd > final_output.txt 2>&1
wc -l final_output.txt

Результат следующий: в файле final_output.txt будут находиться как список найденных файлов (/etc/hosts, /etc/passwd), так и сообщение об ошибке для несуществующего файла. Команда wc -l покажет число 3 (два найденных файла + одна строка с ошибкой).

Тут можно подумать, что статья подходит к логическому завершению, но ведь в данной теме есть замечательная команда tee и файл /dev/null, некая "черная дыра". Давайте разберём...

Команда tee: "разветвитель" потоков

tee - утилита командной строки, которая читает данные из стандартного ввода (stdin) и записывает их одновременно в стандартный вывод (stdout), и в один или несколько файлов.

Если упростить, то tee - тройник: один вход (stdin) и два выхода (stdout) - один идёт дальше, а второй направляется в файл.

Синтаксис:

команда | tee [ОПЦИИ] ФАЙЛ1 [ФАЙЛ2 ...]
  • -a или --append - дописывать в конец файла вместо перезаписи

  • -i или --ignore-interrupts - игнорировать сигналы прерывания

Рассмотрим практические примеры для лучшего понимания:

1. Базовое использование: логирование с видимым выводом

# Мы видим вывод в терминале и одновременно сохраняем его в файл
ls -la | tee directory_listing.txt

2. Многоуровневое логирование сложных операций

# Сохраняем вывод в несколько файлов одновременно
find /home -name "*.txt" 2>&1 | tee find_errors.log find_results.log | wc -l

Результаты поиска сохраняются в два файла, а также передаются в wc -l для подсчета строк.

3. Мониторинг выполнения длительной команды

# Видим прогресс в реальном времени, но всё сохраняем для последующего анализа
dd if=/dev/zero of=testfile bs=1M count=100 2>&1 | tee dd_progress.log

/dev/null или "черная дыра"

/dev/null - это специальное виртуальное устройство в Unix-подобных системах, которое немедленно отбрасывает все записанные в него данные. При попытке чтения из него возвращается конец файла.

Практические примеры использования:

1. Подавление нежелательного вывода

# Полное подавление всех сообщений (и вывод, и ошибки)
some_noisy_command &> /dev/null

2. Подавление только ошибок

# Полезно когда важен только успешный вывод
curl --silent example.com 2> /dev/null

3. Создание пустых файлов или очистка существующих

# Более эффективно чем удаление и создание заново
cat /dev/null > logfile.txt  # Полная очистка файла

Комбинирование tee и /dev/null

# Сохраняем детальный лог в файл, но в терминал показываем только краткую сводку
complex_script.sh 2>&1 | tee detailed.log | grep -E "(ERROR|SUCCESS)" > /dev/null

Что тут происходит:

  • complex_script.sh выполняется, все его сообщения (stdout + stderr) собираются

  • tee записывает полный лог в detailed.log

  • grep фильтрует поток, оставляя только строки с ERROR или SUCCESS

  • Результат grep отправляется в /dev/null (если нам не нужно видеть его в терминале)

  • Но полная информация сохраняется в файле для последующего анализа

Заключение

В статье мы разобрали перенаправление потоков, подавление лишнего вывода через /dev/null и разветвление данных с помощью tee. Я постарался собрать все основные аспекты воедино, если что-то упустил - сообщите, пожалуйста. Внесу правки, дополню.

P. S. В моей группе в Телеграмм разбираем практические кейсы: скрипты (Python/Bash/PowerShell), тонкости ОС и инструменты для эффективной работы.

Комментарии (1)


  1. AVX
    28.10.2025 17:02

    Полная очистка файла может быть проще:

    > logfile.txt