Несколько «путей» исполнения кода в рамках одного процесса называют потоками — именно они являются базовой сущностью, загружает центральный процессор. То есть сам процесс — это своего рода абстракция, оборачивающая потоки.
Каждый поток имеет набор системных параметров: идентификатор, счетчик, регистры, стек и т.д.
В рамках одно процесса потоки имеют общие участки кода, области памяти и разнообразные ресурсы, связанные с внешними устройствами.
Таким образом каждый исполняемый процесс имеет как минимум один поток. Во многих программах это стандартный «entry point» — функция «main».
Однако, операционные системы на базе Linux (и не только) имеют дополнительные потоки ввода и вывода с зарезервированными идентификаторами.
Именно о них и пойдет речь в этой статье. Мы узнаем, в чем особенности каждого из стандартных потоков и как организовать перенаправление данных из одного процесса в другой.
В этом руководстве используется операционная система Ubuntu 22.04, запущенная на облачном сервере Timeweb Cloud. Для выполнения приведенных команд вам потребуется пользователь с правами sudo либо пользователь root.
Стандартные потоки: stdin, stdout, stderr
Программы в UNIX-подобных операционных системах способны обрабатывать данные последовательно, соединяясь в так называемые конвейеры обработки данных (linux pipeline). Кстати, иногда их называют каналами.
То есть множество процессов могут выполнять перенаправление данных таким образом, что результат выполнения одной программы станет вводными для выполнения другой программы.
Такое перенаправление возможно благодаря стандартным потокам ввода и вывода — то есть потокам процесса, номер (дескриптор) которых зарезервирован для выполнения некоторых «стандартных» функций операционной системы.
Существует 3 зарезервированных стандартных потока:
- stdin — читает входные данные — команды пользователя, введенные через клавиатуру, либо выходные данные других программ.
- stdout — Выводит выходные данные (чаще всего в формате текста) — результат выполнения программы.
- stderr — Выводит диагностические или отладочные сообщения, либо информацию об ошибках в работе программы.
Читать данные может только стандартный поток ввода (stdin), а два других (stdout и stderr) способны данные только записывать.
По умолчанию в стандартный поток ввода поступают данные с клавиатуры, а стандартным потоком вывода является монитор. То есть все, что вводится в консольный терминал, попадает в поток ввода. Все, что выводится из консольного терминала, попадает в поток вывода — на монитор.
Перенаправление потоков
Потоки можно подключать к произвольным источникам данных — файлам, программам, сетевым сокетам и внешним устройствам. Такая операция подключения называется перенаправлением и выполняется в консольном терминале с помощью специальных операторов:
- Оператор > (знак больше). Перенаправляет стандартный поток вывода в указанный объект с данными. Данные внутри полностью перезаписываются.
- Оператор >> (два знака больше). Перенаправляет стандартный поток вывода в конец указанного объекта с данными. Данные внутри дополняются.
- Оператор < (знак меньше). Перенаправляет стандартный поток ввода из указанного объекта с данными.
Также важно отметить, что вместе со знаком перенаправления можно в явном виде указывать специальный дескриптор потока, помогая системе понять, какой именно поток нужно перенаправить:
- Запись 1> (номер и знак больше). Перенаправляет стандартный поток вывода с помощью явно указанного дескриптора.
- Запись 1>> (номер и два знака больше). Перенаправляет стандартный поток вывода с помощью явно указанного дескриптора, но не перезаписывает указанный объект с данными.
- Запись 2> (номер и знак больше). Перенаправляет стандартный поток ошибок с помощью явно указанного дескриптора.
Перенаправление потока вывода
В качестве примера давайте попробуем перенаправить вывод команд, сообщающей текущую дату, из консоли в отдельный файл.
Для наглядности сперва выполним консольный вывод актуальной даты:
sudo date
В терминале появится похожее сообщение:
Wed Sep 11 05:21:03 PM MSK 2024
Этот вывод можно сразу записать в файл с помощью оператора перенаправления вывода:
sudo date > date_info
Проверим состояние файловой системы:
ls
Можно заметить, что в корневом каталоге появился (помимо системных) ранее указанный файл:
date_info resize.log snap
Откроем его:
sudo nano date_info
Содержимое файла будет примерно таким:
Wed Sep 11 05:22:08 PM MSK 2024
Перенаправление потока вывода (в конец)
Теперь выполним аналогичное перенаправление вывода, но в конец содержимого файла. Для этого используем не один знак «больше», а два:
sudo date >> date_info
Откроем обновленный файл:
sudo nano date_info
Как можно заметить, к старой дате внутри была дописана новая:
Wed Sep 11 05:22:08 PM MSK 2024
Wed Sep 11 06:06:52 PM MSK 2024
Для наглядности можно попробовать снова сделать перенаправление вывода с одним знаком «больше». Однако в этот раз мы перенаправим вывод команды, предназначенной для просмотра файловой системы:
ls > date_info
Снова открываем содержимое:
sudo nano date_info
Как видно, файл был полностью перезаписан:
date_info
resize.log
snap
То есть главное отличие перенаправления вывода в конец объекта с данными — содержимое не ПЕРЕзаписывается, а ДОписывается.
Еще один важный момент — рядом со знаком перенаправления можно в явном виде использовать дескриптор потока. Например, показанную выше команду можно записать в таком виде:
ls 1> date_info
В любом случае результат будет аналогичный.
Перенаправление потока ввода
Информацию, которая хранится внутри произвольного объекта данных, можно перенаправить в поток ввода конкретной программы.
Самый простой способ продемонстрировать перенаправления ввода — использовать команду, выводящую содержимое файла:
cat < date_info
Консольный вывод будет таким:
date_info
resize.log
snap
В данном случае мы указали файл с текстовым содержимым и передали в поток ввода команды.
Разумеется, можно использовать и более классический способ вывода текстового файла — через передачу аргумента в команду:
cat date_info
Результат будет аналогичным:
date_info
resize.log
snap
Однако механизм обработки команды в обоих вариантах несколько отличается:
- В первом случае мы передали содержимое файла в поток ввода процесса, обрабатывающего команду, после чего он вывел полученные данные в консоль.
- Во втором случае мы просто вызвали команду с аргументом, который представлял собой название файла.
Для более чистого примера мы могли бы использовать команду, которая принимает во входном потоке содержимое файла и выполняет его преобразование по описанным правилам:
tr a-z A-Z < date_info
В консольном терминале появится следующий вывод:
DATE_INFO
RESIZE.LOG
SNAP
В этом примере мы использовали команду, преобразующую символы нижнего регистра в символы верхнего регистра.
Важно отметить, что описанным способом нельзя перенаправить вывод одной команды в качестве ввода другой:
tr a-z A-Z < date
В терминале появится сообщение об ошибке:
-bash: date: No such file or directory
Это связано с тем, что после знака перенаправления консоль ожидает имя конкретного файла.
Перенаправление потока ошибок
По умолчанию, если команда выполняется неверно, запущенный ею процесс выводит соответствующее сообщение в поток ошибок. Как и предполагается, поток ошибок можно перенаправить подобно потоку вывода.
Чтобы продемонстрировать эту возможно, нужно выполнить какую нибудь команду с ошибкой. Например, попытаться перейти в несуществующую директорию:
cd /folder
В консоли появится сообщение о некорректности запрашиваемого адреса:
-bash: cd: /folder: No such file or directory
Попробуем перенаправить стандартный поток вывода ошибок в файл:
cd /folder 2> error_info
Обратите внимание, что теперь мы явно указываем дескриптор потока перед знаком «больше».
Проверим текущий каталог:
ls
Закономерно появился новый файл:
date_info error_info resize.log snap
Просмотрим его содержимое:
sudo nano error_info
Внутри окажется уже знакомое сообщение об ошибке:
-bash: cd: /folder: No such file or directory
Аналогично стандартному потоку вывода, мы можем перенаправлять данные об ошибке без перезаписи файла:
cd /folder 2>> error_info
Снова проверим файл:
sudo nano error_info
Теперь внутри записано два одинаковых консольных вывода:
-bash: cd: /folder: No such file or directory
-bash: cd: /folder: No such file or directory
Перенаправление всего вывода
Два потока стандартного вывода (результат команды и ее ошибки) можно одновременно перенаправить в один и тот же объект с данными. Для этого вместо дескриптора используется амперсанд:
ls ./folder &> full_info
Теперь информация о несуществующем каталоге оказалась в отдельном файле:
cat full_info
Его содержимое будет таким:
ls: cannot access './folder': No such file or directory
А теперь сделаем перенаправление без перезаписи, указав существующий каталог:
ls . &>> full_info
Проверим содержимое файла:
cat full_info
Видно, что внутри находится комбинированный вывод обоих вызовов команды:
ls: cannot access './folder': No such file or directory
date_info
error_info
full_info
resize.log
snap
Перенаправление одновременно ввода и вывода
Перенаправление можно выполнять сразу для нескольких потоков. Это полезно, когда нужно взять откуда-то данные, переработать их какой-то командой, после чего также куда-то их записать.
Например, можно выполнить обратную сортировку определенного файла, после чего записать результат в еще один файл:
sort -r < date_info > date_info_sorted
Теперь выведем содержимое нового файла:
cat date_info_sorted
Вывод будет содержать ранее сохраненный список объектов файловой системы, но в обратном порядке:
snap
resize.log
error_info
date_info
Конвейеры команд
Последовательность команд можно объединить в конвейер (pipeline) обработки данных — вывод одной команды будет автоматически становиться вводом для другой. И так далее по цепочке.
Таким образом можно строить длинные и сложные последовательности команд.
Например, мы можем одной командной строкой взять содержимое нашего файла со «слепком» файловой системы, отсортировать его в обратном порядке, удалить из результата все строки кроме первых 2-х, после чего добавить дополнительный текст и записать конечный результат в файл:
cat date_info | sort -r | head -n 2 | sed 's/$/. конец строки :)/' | cat > pipe_info
Для объединения команды использовался знак «|». В данном примере мы:
- Взяли содержимое файла с именами объектов файловой системы
- Отсортировали содержимое в обратном порядке
- Взяли первые 2 строки из результата сортировки
- Добавили в каждую строку дополнительный текст с помощью утилиты SED
- Записали итоговый текст в новый файл
Проверим, что внутри созданного файла:
snap. конец строки :)
resize.log. конец строки :)
Утилита SED — это потоковый редактор текста (stream editor), который принимает данные через поток ввода, обрабатывает их и передает далее через поток вывода.
С помощью него можно редактировать файлы, не открывая их в явном виде, а также изменять выводы других команд прямо в конвейере обработки данных.
Фильтрация вывода
Часто в момент перенаправления данных выводы одних команд перед тем, как они станут вводом других команд, необходимо привести к более структурному виду.
В этом помогают промежуточные (в рамках конвейера обработки данных) команды, преобразующие текст тем или иным способом. Иными словами, вывод команд можно отфильтровать.
В этом руководстве будут упомянуты лишь самые основные способы фильтрации данных.
tee
Эта команда перенаправляет входной поток в отдельный файл.
Например, описанный выше конвейер можно можно переписать из такого вида:
cat date_info | sort -r | head -n 2 | sed 's/$/. конец строки :)/' | cat > pipe_info
В такой:
cat date_info | sort -r | head -n 2 | sed 's/$/. конец строки :)/' | tee pipe_info
Результат его работы не изменится, однако запись станет лаконичнее — на последней команде нет явного перенаправления потока вывода.
grep
Эта команда позволяет находить определенные строки внутри файлов по заданному регулярному выражению. В блоге Timeweb Cloud есть отдельная статья про использовании команды grep.
Например, в ранее созданном файле с содержимым файловой системы можно найти только файлы с результатами выводов по их характерному нижнему подчеркиванию в названии:
cat date_info | grep '.*_.*'
Консольный вывод будет таким:
date_info
error_info
Либо мы могли бы найти только те названия файлов, у которых указано расширение:
cat date_info | grep '.*\..*'
В данном случае перед специальным символом точки ставится обратный слеш для отключения его специальности.
Вывод будет следующим:
resize.log
tr
Эта команда выполняет различные преобразования текста. Например удаляет перечисленные символы или заменяет указанную последовательность на другую.
Например, можно изменить регистр символов в потоке вывода:
cat date_info | tr a-z A-Z
Результат выполнения будет таким:
DATE_INFO
ERROR_INFO
RESIZE.LOG
SNAP
Либо можно заменить нижнее подчеркивание на пробел:
cat date_info | tr _ " "
Вывод будет уже следующим:
date info
error info
resize.log
snap
head
Эта команда выводит ограниченное количество строк от начала текста.
Например, можно вывести только 2 первые строки:
cat date_info | head -n 2
Вывод будет таким:
date_info
error_info
tail
Эта команда выводит ограниченное количество строк от конца текста.
Например, можно вывести только 2 последние строки:
cat date_info | tail -n 2
Вывод будет таким:
resize.log
snap
Заключение
В этом небольшом руководстве были рассмотрены стандартные потоки ввода и вывода, применяемые в операционных системах Linux:
- stdin
- stdout
- stderr
Также были показаны способы перенаправления данных через консольный терминал за счет объединения программ в конвейеры (pipelines) обработки данных.
Для этого использовались специальные знаки с которыми можно использовать дескриптор потока:
- > или 1> или 2>
- >> или 1>> или 2>>
- <
Перенаправление потоков ввода и вывода — одна из главных особенностей Unix-подобных операционных систем, которая позволяет гибко и эффективно обрабатывать как пользовательские данные, введенные с клавиатуры или полученные через сетевое соединение, так и данные, являющиеся результатом выполнения других программ.