Що таке stdin, stdout і stderr у Linux?

stdin, stdout та stderr – це три канали даних, що створюються при запуску будь-якої команди в Linux. Вони дозволяють визначити, як ваші скрипти отримують вхідні дані, а також куди вони відправляють результати. Розглянемо, як це працює.

Потоки даних: джерело та призначення

При вивченні Linux або інших Unix-подібних систем ви неминуче зіткнетесь з поняттями stdin, stdout та stderr. Це три стандартні потоки, які ініціалізуються під час виконання команди Linux. У світі обчислень, потік – це механізм передачі даних. У цьому контексті, ці дані зазвичай представлені у текстовій формі.

Потоки даних, подібно до річкових, мають два кінці: витік та гирло. Кожна команда Linux забезпечує один кінець кожного з цих потоків. Інший кінець визначається оболонкою, з якої була запущена команда. Цей кінець може бути підключений до вашого терміналу, до іншого процесу через канал, або ж перенаправлений до файлу чи іншої команди, залежно від командного рядка, з якого команда була викликана.

Стандартні потоки Linux

У Linux, stdin (стандартний вхід) – це потік, який отримує текстові дані як вхід. Вихідні дані команди, що надсилаються назад до оболонки, передаються через stdout (стандартний вихід). Повідомлення про помилки, що генеруються командою, надсилаються через stderr (стандартна помилка).

Таким чином, існують два вихідних потоки, stdout і stderr, та один вхідний – stdin. Розділення стандартного виводу та повідомлень про помилки на два різні канали дозволяє обробляти їх незалежно один від одного.

Потоки як файли

В Linux потоки, як і більшість інших елементів системи, розглядаються як файли. З файлу можна читати текст, а також записувати текст у файл. Обидві ці операції включають в себе потік даних. Отже, концепція обробки потоків даних як файлів є досить природною.

Кожному файлу, пов’язаному з процесом, присвоюється унікальний номер, який ідентифікує його. Це називається дескриптором файлу. Коли потрібно виконати дію з файлом, для ідентифікації використовується саме дескриптор файлу.

Для stdin, stdout та stderr використовуються такі постійні ідентифікатори:

0: стандартний вхід
1: стандартний вихід
2: стандартна помилка

Реакція на канали та перенаправлення

Для кращого розуміння складних концепцій, часто використовують спрощені пояснення. Наприклад, у граматиці часто подають правило: “і” перед “е”, крім “с”. Але насправді винятків з цього правила набагато більше, ніж відповідностей йому.

Подібно до цього, при обговоренні stdin, stdout і stderr, часто кажуть, що процесу не важливо, куди надходять або виходять його дані. Чи має процес знати, чи виводиться його результат на екран, чи перенаправляється в файл? Чи може він розрізнити, чи надходить вхід з клавіатури, чи передається з іншого процесу?

Насправді, процес може дізнатися про це, якщо розробник передбачив таку можливість. Він навіть може змінити свою поведінку, залежно від того, куди перенаправлено його потоки.

Ці зміни поведінки можна легко спостерігати. Спробуйте виконати наступні команди:

ls

ls | cat

Команда ls виводить результати по-іншому, коли її вихід (stdout) передається іншій команді. Це команда ls перемикається на одностовпцевий вивід, а не команда cat проводить якесь перетворення. ls також поводиться так само, коли її вихід перенаправляється:

ls > capture.txt

cat capture.txt

Перенаправлення stdout та stderr

Розділення повідомлень про помилки в окремий потік має переваги. Це дозволяє перенаправляти вихід команди (stdout) у файл, при цьому спостерігаючи повідомлення про помилки (stderr) на терміналі. Це дає можливість реагувати на помилки в реальному часі. Також це запобігає потраплянню повідомлень про помилки до файлу, куди перенаправляється стандартний вихід.

Введіть наступний текст у текстовий редактор і збережіть його у файлі з назвою error.sh:

#!/bin/bash
echo "About to try to access a file that doesn't exist"
cat bad-filename.txt

Зробіть цей скрипт виконуваним, виконавши команду:

chmod +x error.sh

Перший рядок скрипта виводить текст на термінал через stdout. Другий рядок намагається отримати доступ до файлу, якого не існує. Це призведе до повідомлення про помилку, яке надійде через stderr.

Запустіть скрипт наступною командою:

./error.sh

Обидва потоки виводу, stdout і stderr, виводяться на термінал.

Спробуємо перенаправити вивід у файл:

./error.sh > capture.txt

Повідомлення про помилку, яке надходить через stderr, все ще виводиться на термінал. Можна перевірити файл і побачити, що вивід stdout було перенаправлено у файл.

cat capture.txt

Стандартний вивід було перенаправлено у файл, як і очікувалось.

Символ перенаправлення > за замовчуванням працює зі стандартним виводом. Для перенаправлення певного потоку, можна використовувати його числовий дескриптор.

Для явного перенаправлення stdout використовуйте конструкцію перенаправлення:

1>

Для явного перенаправлення stderr використовуйте конструкцію перенаправлення:

2>

Спробуємо наш тест знову, цього разу використовуючи 2>:

./error.sh 2> capture.txt

Повідомлення про помилку перенаправлене, а echo-повідомлення stdout виводиться на термінал:

Повідомлення stderr знаходиться в файлі capture.txt, як і очікувалось.

Перенаправлення stdout і stderr

Звісно, якщо ми можемо перенаправляти stdout або stderr у файли окремо, ми також можемо перенаправити їх обидва одночасно у два різні файли?

Так, це можливо. Наступна команда перенаправляє stdout у файл capture.txt, а stderr у файл error.txt:

./error.sh 1> capture.txt 2> error.txt

Оскільки обидва вихідні потоки – stdout і stderr – перенаправлені у файли, то виводу на терміналі немає. Ми просто повертаємося до командного рядка.

Перевіримо вміст кожного файлу:

cat capture.txt
cat error.txt

Перенаправлення stdout і stderr в один файл

Ми вже розібрались, як перенаправляти stdout та stderr в окремі файли. Єдиною іншою комбінацією є перенаправлення їх обох в один файл.

Цього можна досягти за допомогою такої команди:

./error.sh > capture.txt 2>&1

Розберемо її по частинах:

./error.sh: запускає файл скрипта error.sh.
> capture.txt: перенаправляє stdout в файл capture.txt. > є скороченням від 1>.
2>&1: це конструкція перенаправлення &>. Вона дозволяє вказати оболонці, що один потік має відправлятися в те саме місце, що й інший. В цьому випадку ми говоримо “перенаправити потік 2 (stderr) в те саме місце, куди перенаправляється потік 1 (stdout)”.

Виводу на терміналі немає, що є цілком очікувано.

Давайте перевіримо файл capture.txt і подивимось, що в ньому.

cat capture.txt

Обидва потоки, stdout і stderr, були перенаправлені в один файл.

Щоб перенаправити вивід і відкинути його, перенаправте вихід у /dev/null.

Виявлення перенаправлення всередині скрипта

Ми обговорювали, що команда може визначати, чи перенаправлений якийсь із потоків, і може відповідно змінювати свою поведінку. Чи можна зробити те саме у власних скриптах? Так, і це досить просто.

Введіть наступний текст у редактор і збережіть його як input.sh:

#!/bin/bash
if [ -t 0 ]; then
  echo stdin coming from keyboard
else
  echo stdin coming from a pipe or a file
fi

Зробіть скрипт виконуваним командою:

chmod +x input.sh

Ключовим є тест у квадратних дужках. Параметр -t (термінал) повертає істину (0), якщо файл, пов’язаний з файловим дескриптором, пов’язаний з терміналом. Ми використовуємо дескриптор файлу 0, який представляє stdin, як аргумент для тесту.

Якщо stdin підключено до терміналу, то тест буде успішним. Якщо stdin підключено до файлу або каналу, тест буде не успішним.

Ми можемо використовувати будь-який текстовий файл для надання вхідних даних для скрипту. В даному випадку ми використовуємо файл dummy.txt.

./input.sh < dummy.txt

Вивід показує, що скрипт розпізнає, що вхід не надходить з клавіатури, а надходить з файлу. При бажанні ви можете відповідним чином змінити поведінку вашого скрипта.

Спробуємо тепер з використанням каналу:

cat dummy.txt | ./input.sh

Скрипт розпізнає, що його вхідні дані передаються через канал. Тобто, він знову розпізнає, що потік stdin не підключений до терміналу.

Запустимо скрипт без каналів і перенаправлень.

./input.sh

Потік stdin підключений до терміналу, і скрипт повідомляє про це.

Щоб перевірити те ж саме з вихідним потоком, потрібен новий скрипт. Введіть наступне у редактор і збережіть як output.sh:

#!/bin/bash
if [ -t 1 ]; then
  echo stdout is going to the terminal window
else
  echo stdout is being redirected or piped
fi

Зробіть його виконуваним за допомогою команди:

chmod +x input.sh

Єдина відмінність цього скрипта від попереднього – це тест у квадратних дужках. Ми використовуємо цифру 1 для представлення файлового дескриптора для stdout.

Спробуємо. Ми передаємо вихід через cat.

./output.sh | cat

Скрипт розпізнає, що його вихід не направляється безпосередньо в термінал.

Можна також перевірити скрипт, перенаправивши вихід у файл.

./output.sh > capture.txt

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

Перевіримо файл capture.txt, щоб переконатись, що вивід було збережено. Виконайте наступну команду:

cat capture.txt

Знову ж таки, простий тест у скрипті показує, що вивід stdout не надсилається безпосередньо на термінал.

Якщо запустити скрипт без каналів та перенаправлень, він повинен виявити, що stdout надсилається безпосередньо на термінал.

./output.sh

І це саме те, що ми бачимо.

Розуміння Потоків

Знання про те, як визначити, чи підключений ваш скрипт до термінала, каналу або перенаправлення, дозволяє налаштувати його поведінку. Залежно від того, чи виводиться інформація на екран або у файл, ви можете зробити вивід більш деталізованим. Повідомлення про помилки можна виводити в інший файл, ніж стандартний вивід програми.

Як завжди, більше знань дає більше можливостей.