В Bash, да и других командных оболочках (даже в cmd.exe) есть довольно странные
символы для управления потоками ввода/вывода, управления потоком исполнения.
Одни из наиболее часто используемых это: >
, <
, &
, &&
, |
, ||
.
Выглядят они похоже, да и иногда имеют схожий смысл, но, само собой –
всё это разные операции.
Потоки вывода / ввода и файлы
Для начала разберёмся с первыми двумя. Во-первых, >
, <
– это всё
про файлы или про дескрипторы файлов (некоторые их представления).
И это действительно базовые вещи, например, если мы хотим сохранить
вывод команды в файл, то мы пишем:
help > help_info.txt
– где help
– это команда, вывод которой мы хотим сохранить, а help_info.txt
– это файл, куда сохранить. То есть обычный вывод мы можем сохранить в файл
с помощью перенаправления потока стандартного вывода (>
).
Если вы повторите это действие несколько раз, то всё равно увидите в результирующем
файле всего одну "копию" вывода help
. Когда мы перенаправляем поток
вывода с помощью >
, он пишет с начала файла, затирая его. Если же
нужно дописывать, используют его "брата" – >>
– он продолжит записать
с конца файла.
Но если есть вывод, значит можно сделать аналогично на ввод. Например,
команда wc умеет считать слова/строки/байты,
переданные на стандартный поток ввода. Передадим и мы из нашего файла
help_info.txt
:
wc -l < help_info.txt
И узнаем, сколько же строк было в выводе help
. Тут важно понять, что это
не из-за того, что в одном случае "стрелка" была от программы, а в другом –
в программу, хотя так и выглядит. Просто >
всегда пытается записать в файл,
а <
– прочитать из файла. К примеру, мы можем попытаться сразу посчитать
строки, которые выдаёт help
:
help > wc
Но вместо этого мы создадим файл "wc" и запишем в него вывод команды help
.
Потоки вывода / ввода и программы
А вот что реально поможет с переводом стандартного потока вывода на вход
другой программы – так это "труба" – |
, также известная как конструкция "пайп"
(калька с английского "pipe"). По трубе можно слева направо лить данные,
изменяя их как на конвейере:
help | wc -l
– сработает уже ожидаемо. Можно делать более сложные "каскады" из труб:
ifconfig | grep inet | grep -v inet6 | cut -d ' ' -f 2
Работает это следующим образом:
- Получаем вывод команды ifconfig с подробной информацией о сетевых интерфейсах.
- Отфильтровываем строки, оставляя те, что с подстрокой "inet" командой grep.
- Из результата убираем строки с подстрокой "inet6" (не интересуемся IPv6).
- Разделяем строки на столбцы по пробелу и берём 2-ой столбец с помощью cut.
В результате получаем список IP-адресов на наших сетевых интерфейсах. Можно шаг за шагом наращивать эту цепочку из труб и команд, чтобы увидеть, как выход предыдущего используется, модифицируется и отдаётся следующему.
Управляющие конструкции в Bash
Хорошо, поняли, что |
перенаправляет данные из одной программы в другую...
Но есть ещё и ||
– может он делает это как-то лучше? На самом деле, ||
вообще никак не связан с |
, хоть и похож. Он нужен для управления последовательностью
действий.
Как в языках программирования есть условные операторы (в Bash они тоже есть), так и в терминале иногда удобно задать последовательность действий, исходя и того, получилось ли предыдущее. Например, перейти в директорию "asd", а если не удалось (скорее всего её нет) – создать её:
cd asd || mkdir asd
||
проверит код завершения предыдущей команды, и если он не 0 (а именно 0
считается корректным и правильным), то выполнится. Сам код завершения
последней программы можно посмотреть в переменной $?
(в Windows – %errorlevel%
):
$ cd asd
-bash: cd: asd: No such file or directory
$ echo $?
1
– код не 0, значит в примере выше, мы бы попытались создать директорию... И было бы
логично в неё заодно так и перейти. То есть "перейти в директорию "asd",
а если не удалось (скорее всего её нет) – создать её и, если создали, перейти в неё".
Для этого есть противоположность ||
– &&
. Он выполнит следующую команду,
если предыдущая завершилась хорошо. Если ||
называют "логическим или" (исключающим),
то &&
– "логическое и". А наш пример превращается в:
cd asd || mkdir asd && cd asd
Мы можем таким образом описывать условия, как и с if.
Запуск программ в фоне на Bash
Казалось бы, как мы пришли к этому подзаголовку? Ровно также, как и к "управляющим
конструкциям": у &&
есть довольно похожий на него "близнец" – &
. И опять у них
нет ничего общего. Если первый позволяет управлять потоком исполнения команд,
то второй запускает их в фоне!
Например, давайте запустим простую, но долгую программу в фоне, выполним другую программу, а потом вернём из фона первую программу.
$ sleep 100 &
[1] 4925
$ echo "Hello"
Hello
$ jobs
[1]+ Running sleep 100 &
$ fg 1
sleep 100
^C
С помощью команды jobs можно получить список запущенных в сессии терминала задач. Задачи пронумерованы – по этим номерам можно поднимать задачи из фонового режима командой fg.
Это может быть удобно, если нужно запустить несколько задач, а терминал только один, либо же если хочется наплодить кучу независимых процессов (например, для параллельно обработки данных).
Завершение, о чём не рассказал
Как можно было заметить, в Bash довольно много странных символов, имеющих важные значения. Более того – не обязательно похожие комбинации символов означают похожее. Понимание приходит с опытом / привычкой.
У того же амперсанда есть и другие смыслы, в зависимости от контекста:
и "битовое и" и "запись в открытый дескриптор". Да и <<
, и даже <<<
здесь не были упомянуты. В заметке скорее обзор самых первых удивлений,
которые возникают у студента при знакомстве с терминалом / Bash-ем.
Если также часто придётся рассказывать про иные особенности - появится и их описание, как это было с "Как запустить программу в терминале в фоне, без вывода какого либо текста".
Ну и в целом, если заинтересовались – "Трюки Bash" для вас!