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

b1f5da1cdf6889a1158a0860fc5d7209.jpg

Привет, Хабр!

Сегодня рассмотрим работу с потоками в 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. Узнать подробнее

© Habrahabr.ru