Управляем потоками в Linux: от stdin до stderr

Привет, Хабр!
Сегодня рассмотрим работу с потоками в Linux: stdin, stdout, stderr и, конечно, все эти оператора редиректа.
Файловые дескрипторы: 0, 1, 2
Для начала поговорим о том, что такое эти загадочные числа 0, 1 и 2. Если кажется, что это просто архаичные обозначения — вы глубоко заблуждаетесь. В Unix всё — буквально всё — является файлом.
0 — это стандартный ввод
stdin
. Сюда поступают команды, нажатия клавиш и всякое такое.1 — это стандартный вывод
stdout
. Все, что вы видите в терминале (например, результат выполнения команды), идет сюда.2 — это стандартный поток ошибок
stderr
. Любая ошибка, предупреждение или критическая ситуация — всё это тут.
Если провести аналогию с офисом, то 0 — это входящие звонки, 1 — обычная переписка, а 2 — сигнал тревоги, когда что‑то идёт не так.
Редиректы
Эти операторы позволяют направить вывод туда, куда вам нужно, будь то файл, команда или даже другой поток.
Оператор >
Этот оператор — рабочий конвейер, который берет stdout и перенаправляет его в указанный файл.
#!/bin/bash
# Записываем результат выполнения команды в файл output.txt
echo "Привет, stdout!" > output.txt
Здесь, если файл уже существует, он будет перезаписан. А если нужно пришить новый вывод к уже существующему файлу, используйте оператор >>
.
Оператор 2>
Когда дело доходит до ошибок, то помогает оператор 2>
. Он берет stderr
и пишет его в отдельный файл. Может пригодится, если отделить «хорошие» сообщения от «бесполезного» шума ошибок:
#!/bin/bash
# Пытаемся выполнить команду, которая гарантированно завалится
ls /some/nonexistent/dir 2> error.log
В результате получите файл с ошибкой, а основной вывод останется чистым.
Оператор &>
А вот &> — маст‑хэв для тех, кто хочет всё в одном флаконе: и stdout, и stderr вместе.
#!/bin/bash
# Сохраняем и stdout, и stderr в файл combined.log
./some_script.sh &> combined.log
Оператор 1>&2
Если хочется, чтобы обычный вывод шел туда, куда уже направлены ошибки, то оператор 1>&2 решает эту задачу:
#!/bin/bash
# Отправляем stdout в stderr (ну, бывает, что так удобнее для логирования)
echo "Это сообщение пойдет в stderr" 1>&2
Here-strings <<< и Here-documents <
Представьте, что вам надо передать строку или блок текста в качестве ввода для команды. Вот тут на помощь приходят:
Here‑string (<<<): позволяет передать строку напрямую в команду.
# Передаем строку в grep через stdin grep "pattern" <<< "Некоторый текст с pattern внутри"
Here‑document (<
: для многострочного ввода. cat <
Утилиты tee, read и exec
Команда tee
Команда tee позволяет одновременно записывать данные в файл и выводить их на экран.
#!/bin/bash
# Ловим вывод команды и сразу же его записываем в log.txt, не упуская ничего
echo "Запускаем процесс..." | tee log.txt
А если хочется фильтровать вывод, то можно комбинировать с
grep
:
#!/bin/bash
# Логирование и фильтрация ошибок одновременно
./run_process.sh 2>&1 | tee process.log | grep "ERROR"
Команда read
Как часто мы сталкивались с ситуацией, когда скрипт зависает, ожидая ввода? Вот тут на помощь приходит команда
read
с таймаутом.
#!/bin/bash
# Запрашиваем ввод с таймаутом в 5 секунд
echo "Введите что-нибудь (у вас 5 секунд):"
if read -t 5 userInput; then
echo "Вы ввели: $userInput"
else
echo "Время вышло! Давайте продолжим без вашего участия."
fi
Команда exec
Если хотите, чтобы скрипт был настолько чист, что все будущие сообщения автоматически шли в лог‑файл, то тут полезна команда
exec
:
#!/bin/bash
# Перенаправляем весь stdout и stderr в файл с помощью exec и tee
exec &> >(tee -a script.log)
echo "Эта строка уже попадет в лог!"
Кстати, exec можно использовать и для замены текущего процесса на другой — это мощно, но требует осторожности. Когда вы запускаете
exec ls -l
, скрипт умирает и превращается в ls.
Как применить редиректы на практике
Отдельная запись stdout и stderr
Допустим, нужно разделить обычный вывод и ошибки, чтобы потом анализировать их независимо:
#!/bin/bash
# Файл: separate_logs.sh
# Записываем stdout в out.log, а stderr в err.log
./some_command.sh 1> out.log 2> err.log
Логирование пайплайнов для отладки
Иногда пайплайн настолько сложен, что хочется видеть, как данные проходят через каждое звено:
#!/bin/bash
# Файл: debug_pipeline.sh
# Пайплайн с промежуточным логированием
cat input.txt | tee step1.log | grep "filter_pattern" | tee step2.log | sort | tee final.log
Каждый шаг логируется, и если что‑то пойдет не так — вы сразу увидите, на каком этапе данные потерялись или искажались.
Ввод с таймаутом и повторами
Иногда нужно не только запросить ввод, но и дать пользователю несколько шансов, прежде чем скрипт сдастся:
#!/bin/bash
# Файл: read_retry.sh
# Несколько попыток ввода с таймаутом
TIMEOUT=5
TRIES=3
attempt=1
while [ $attempt -le $TRIES ]; do
echo "Попытка $attempt/$TRIES: введите пароль (5 секунд на ответ):"
if read -t $TIMEOUT password; then
if [ "$password" == "secret" ]; then
echo "Доступ разрешен. Добро пожаловать!"
break
else
echo "Неверный пароль. Попробуйте еще раз."
fi
else
echo "Таймаут! Вы слишком медлили."
fi
attempt=$((attempt + 1))
done
if [ $attempt -gt $TRIES ]; then
echo "Слишком много попыток. Доступ запрещен."
fi
Всем начинающим администраторам Linux крайне рекомендую посетить открытые уроки, которые пройдут в Otus в апреле:
2 апреля: Вся правда о рынке труда или как быть востребованным в современных реалиях. Узнать подробнее
17 апреля: Фильтрация трафика в iptables. Узнать подробнее