# Стандартные потоки


## Введение

Каждая консольная программа снабжена тремя потоками данных: одним входящим и двумя исходящими, называемых *стандартными потоками ввода, вывода и ошибок*.
По умолчанию стандартный поток ввода подключен к клавиатуре, а потоки вывода и ошибок -- к экрану терминала.
Подключение потоков к другим устройствам ввода и вывода называется *перенаправлением*.
Прием активно применяется на практике для автоматизации ввода данных, сохранения выходных данных, последовательной обработки данных несколькими командами.

Почему с программой связаны два исходящих потока?
В стандартный поток вывода передаются результаты работы программы, тогда как в стандартный поток ошибок -- диагностические сообщения, предупреждения, информирование о текущем состоянии процесса.
Если результат программы из стандартного потока вывода может быть использована другой программой, то сообщения из потока ошибок в первую очередь направлены пользователю.

До этого момента мы употребляли выражение, что команда "выводит на экран терминала".
Фактически, команда отправляет данные в стандартный поток вывода, который подключен к терминалу.
Таким же успехом данные могут быть перенаправлены в файл или другую команду.
Это дает неожиданные применения примитивным на первый взгляд командам.
Рассмотрим это на примере команд `echo`, `read` и `cat`.

<!-- Команда `echo` -->
Любой язык программирования содержит функцию для вывода строки на экран.
В интерпретаторе `bash` для этого существует команда `echo`.
Она отправляет данные, переданные ей через аргументы командной строки, в стандартный вывод.
Так как стандартный вывод по умолчанию подключен к терминалу, то команда выведет свои аргументы на экран.
Перевод строки добавляется автоматически к выводимым данным.
Опция `-n` отменяет такое поведение.
Утилиту `echo` часто используют в сценариях, где она печатает диагностические сообщения и значения переменных окружения.
Дополнительно, она умеет разворачивать регулярные выражения до имен файлов:

```
[sppo@machine ~]$ echo main.*
main.c main.cc
```

<!-- Команда `read` -->
Если можно выводить данные, то должна существовать обратная операция ввода данных от пользователя.
Команда `read` считывает одну строку из стандартного ввода и помещает слова из строки в переменные, имена которых переданы через аргументы.
Если переменных недостаточно для вмещения всех слов, то в последнюю из них поместится остаток необработанной строки.

```
[sppo@machine ~]$ read var1 var2 var3
Hello terminal! This is user.
[sppo@machine ~]$ echo $var1
Hello
[sppo@machine ~]$ echo $var2
Terminal!
[sppo@machine ~]$ echo $var3
This is user.
```

<!-- Команда cat -->
Команда `cat` отправляет в стандартный поток вывода содержимое файлов, имена которых переданы через аргументы командной строки.
Она позволяет быстро просматривать небольшие файлы, вмещаемые на экране.

```
[sppo@machine ~]$ cat main.c
int main(int argc, char** argv)
{
    return 0;
}
```


## Перенаправление потоков

<!-- Про перенаправление потоков в файл -->
По умолчанию, два выходных потока привязаны к экрану, а входной поток к клавиатуре терминала.
Эту связь можно изменить, привязав потоки к файлу.
Такое действие называют *перенаправлением потоков*.
При перенаправлении потока ввода данные будут считаны из файла, а при перенаправлении потока вывода -- будут записаны в файл.

<!-- Перенаправление вывода -->
Для перенаправления вывода в файл используют символ `>` и конструкцию вида `command > file`.
В случае с командой `echo`, запись строки `hello terminal` в файл `my.txt` выглядит как `echo "hello terminal" > my.txt`.
Если такого файла не существует, то он будет создан.
В противном случае его предыдущее содержимое заменится новым.
Чтобы добавить строку без затирания существующего содержимого используют символы `>>` и конструкцию `command >> file`.
Благодаря перенаправлению стандартного потока вывода в файл `echo` превращается в текстовый редактор для записи и добавления в файл строки.

<!-- Перенаправление ввода из файла -->
Команда `read` считывает данные из стандартного потока ввода.
Для перенаправления входящего потока командная оболочка `bash` предлагает символ `<` и конструкцию вида `command < file`.
Команда `read var1 var2 var3 < file.txt` считывает из файла `file.txt` первую строку и размещает ее в переменных окружения `var1`, `var2` и `var3`.

<!-- Что можно сделать с `cat`? -->
Утилита `cat` совместно с перенаправлением потоков превращается в инструмент:
* объединения файлов: `cat part-1.txt part-2.txt part-3.txt > file.txt`.
* создания многострочных файлов: `cat > file.txt`.

<!-- Перенаправление сразу двух потоков -->
Перенаправление потоков ввода и вывода можно скомбинировать в одной команде.
Так, команда `cat < part-4.txt >> file.txt` добавит в конец файла `file.txt` содержимое `part-4.txt`.

<!-- Перенаправление потока ошибок -->
До этого момента перенаправление происходило только стандартных потоков ввода и вывода, но не ошибок.
Хотя он и связана с терминалом, как и поток вывода, но он перенаправляется отдельной инструкцией.
Чтобы изучить перенаправление ошибок, познакомимся с утилитой `find` для поиска файлов.
Команда `find ~ -name "file.txt"` ищет файл по имени `file.txt` в домашнем каталоге.
Если по ходу поиска не получилось зайти в каталог, то это будет указано в сообщении, отправляемом в стандартный поток ошибок.
Чтобы отключить эти сообщения, перенаправим поток ошибок в файл конструкцией вида `command 2> file`: `find / -name "file.txt" 2> out.txt`.

<!-- Перенаправление ошибок в системную корзину -->
Отправляя сообщение об ошибке в файл, мы нагружаем ФС и расходуем дисковое пространство, сохраняя бесполезную информацию.
Когда хотят отключить исходящий поток, его перенаправляют в специальный файл `/dev/null`, называемой *системной корзиной*.
Направленные в нее данные бесследно исчезают.
В случае с примером выше, измененная команда выглядит как: `find / -name "file.txt" 2> /dev/null`.

<!-- Перенаправление вывода в системную корзину -->
Перенаправление стандартному потоку вывода в системную корзину применяют, когда пользовтелю интересует не результат команды, а ошибки в ходе выполнения.
Классическим примером такого явления относится использование утилиты `make` для сборки программ в среде UNIX.
Утилита указывает ход процесса сборки сообщениями в стандартный поток вывода, а ошибки от компилятора и компоновщика отправляет в стандартный поток ошибок.
Сообщения об ошибках теряются среди остальных.
Команда `make > /dev/null` перенаправляет стандартный вывод в системную корзиную и оставляет на экране только сообщения об ошибке.

<!-- Перенаправление потоков вывода и ошибок -->
Чтобы отключить оба исходящих потока программы, их перенаправляют в системную корзину командой `make > /dev/null 2>&1`.
Здесь указано, что поток вывода перенаправляется в системную корзину, а поток ошибок туда же, куда был перенаправлен поток вывода до этого.
Здесь важна последовательность.
Если поменять местами переправляющие части, то нужный эффект не достигается.
Более упрощенные записи `command >& file` или `command &> file` также позволяет достичь этого, но они не соответствуют стандарту POSIX.

<!-- Передача строк в стандартный поток ввода -->
До этого момента нам известны два способа отправки данных в поток ввода -- из клавиатуры и файла.
Файл освобождает пользователя от рутины каждый раз вбивать данные с клавиатуры.
Связка файла с данными и сценария, где эти данные употребляются, в ходе переноса может разрушиться.
Самодостаточные сценарии данные добывают самостоятельно или хранят у себя в виде строки.
Для отправки в поток ввода строки применяют команду `command <<< "string"`.
Многострочный текст в поток ввода передают посредством символов `<<` и следующих за ним разделителя:

```
command << STOP
$ Hello!
$ This is text
$ for command utility
$ STOP
```

Этот прием называется "document here" и применим в сценариях и командной строке.
Разделителем может быть любое слово, а не только `STOP`.
С помощью него в сценарии можно представить многострочный файл.


## Конвейер команд

<!-- Конвейер -->
Перенаправление потоков позволяет нам сохранить результат выполнения команды в файл, чтобы воспользоваться им позднее, например передав его в другую команду.
Здесь файл играет роль промежуточного буфера.
Но можно отказаться от него, если исходящий поток первой команды соединить с входящим потоком второй.
Эту цепочку можно продолжить дальшее, добавив другие команды.
В командной оболочке `bash` потоки двух команд объединяются посредством символа `|`: `command1 | command2`.
Получившуюся конструкцию называют *конвейером*.

<!-- Подсчет сессий пользователей в машине -->
Один пользователь может войти в систему несколько раз из разных терминалов.
Сформирует отчет, в котором будут указаны имя пользователя и количество входов.
Утилита `who` выводит таблицу с информацией о зашедших в машину пользователей.
В первом столбце стоит имя пользователя, затем идут последовательно имя терминала, дата и время входа и IP-адрес удаленной машины.
Из каждой строки таблицы вычленим первый столбец, имя пользователя, командой `awk '{print $1}'`.
Получившийся список имен отсортируем командой `sort` и исключим повторяющиеся имена, подсчитав повторения командой `uniq --count`.
В результате получим следующую составную команду или конвейер:

```bash
who | awk ‘{print $1}’ | sort | uniq --count
```

<!-- Автоматическое соглашение и `yes` -->
Установка программ из менеджера пакетов `apt-get` требует подтверждение действий вводом символа `y`.
Существует команда `yes`, единственная задача которой соглашаться, отправив символ `y` в стандартный поток вывода.
Объединим ее с командой установки компилятора `gcc`:

```bash
yes | apt-get install gcc
```

```{note}
Если вывод команды объемный и не обозревается на одном экране, то для следует вывод перехватить утилитой `less` и пролистывать ее командами.
Пример такого конвейера: `ls -l /usr/bin | less`.
```

При составлении конвейера новичку следует учитывать, что не все команды принимают данные из потока и выдают данные в поток.
Существуют команды, которые:
* принимают данные из потока и выдают результат в поток (`sort`);
* принимают данные из потока, но не выдают результат (`read`);
* не принимают данных из потока, но выдают результат (`ls`, `echo`);
* не принимают данные из потока и не выдают результат (`cd`, `rm`, `mv`).

Графически, это выглядит как показано ниже:
```{figure} ./images/command-in-out.png
```

Некоторые команды могут как принимать данные из стандартного потока, так и брать их из файла, в зависимости от опций и аргументов.
Так, `sort` отсортирует данные из файла, если его имя передано через свободный аргумент: `sort file.txt`.
Если ей передать имя файла через опцию `--output`, то выходные данные сохранятся в этот файл: `sort --output==out.txt file.txt`.
В последнем примере команды `sort` не будет обращаться ни к потоку ввода, ни к потоку вывода.

<!-- Нужны еще примеры -->
<!--
Источники данных:
* список всех процессов в системе: `ps -e`;
* содержимое выбранных файлов: `cat file1 file2`;
* список файлов: `ls -al /usr/bin`;

Финальное преобразование:
* подсчет элементов в тексте: `wc`;
* первые четыре строки: `head -4`;
* последние три строки: `tail -3`;
* сортировка: `sort -n -k5`;
* выбор уникальных строк `uniq`;
* фильтрация строк: `grep`;

Подсчитать количество файлов в проекте по расширениям: `find . -type f | awk -F. '{print $NF}' | sort | uniq -c | sort -g --reverse`.
Подсчитать количество строк в исходных кодах: `find -name ".cpp" -or -name "*.h" -or -name "*.hpp" | xargs cat | wc -l`.
Подсчитать количество функций в исходных кодах.
Подсчитать количество файлов в каталоге.
Вычислить распределение файлов по размерам: `find . -type f | xargs ls -l | awk '{print $5}' | sort -g | ...`.

Введенный в терминале текст появляется во вкладке VSCode: `cat | code -`
-->


## Дополнительные источники

1. [Redirections (Bash Reference Manual)](https://www.gnu.org/software/bash/manual/html_node/Redirections.html)


## Вопросы для самоконтроля

1. Какими стандартными потоками обладает консольная программа?
1. Куда можно перенаправить данные из стандартных потоков?
1. Чем отличаются данные из стандартных потоков вывода и ошибок?
1. Что делают команды `echo`, `read` и `cat`?
   Приведите примеры их использования (без перенаправления потоков).
1. Что такое системная корзина и как перенаправить в нее потоки?
1. Что такое конвейер?


## Упражнения

1. Создайте файл `main.c` со строкой `int main(int argc, char** argv)`.
   В этот же файл добавьте дополнительно строку `{ return 0; }`.
   Проверьте, получилось ли у вас сделать это.
1. Считайте слова из файла `message.txt` (с одной строкой `Hello terminal!`).
   Проконтролируйте, что у вас получилось это сделать.
1. Создайте и объедините два файла в один.
   Создайте третий файл и добавьте его в конец предыдущего файла.
1. Покажите пример перенаправления в файл потока ошибок.
1. Команда `wc` подсчитывает байты, слова и строки.
   Напишите команду, которая выведет на экран количество файлов и каталогов в каталоге `/usr/bin`.
1. Команда `sort` сортирует строки.
   Если ей на вход не передан файл, то данные для сортировки берет из стандартного потока ввода.
   Каждый сортируемый элемент должен начинаться с новой строки.
   Команда `echo -e` выведет текст с новой строки, если перед ним стоят управляющий символ `\n`.
   Опция `-e` интерпретирует обратный слеш и следующий за ним символ как управляющий символ.
   Воспользуйтесь этими командами, чтобы отсортировать слова "Полина", "Кристина", "Динара", "Артем", "Елена", "Алина", "Никита", "Егор", "Денис", "Андрей".
1. Команда `ps -e` выведет список всех процессов в системе.
   Команда `grep string` выведет в стандартный поток вывода только те строки из стандартного потока ввода, в которых содержится слово "string".
   Команда `awk '{print $4}'` выведет только четвертое слово.
   Воспользуйтесь этими командами, чтобы вывести на экран идентификаторы тех процессов, в названиях которых есть слово "kworker".
1. Подсчитайте количество процессов в системе.
1. Отсортируйте файлы из `/usr/bin` по размеру утилитой `sort`.
   Для этого обратите внимание на ее опции `-n` и `-k`.
1. Команда `xclip` взаимодействует с буфером обмена.
   Передайте с помощью него содержимое файла в графическое приложение. <!-- cat example.sh | xclip -sel c -->
1. Командой `xclip` сохраните в файл скопированный из веб-браузера текст.
