При создании сценариев Bash мы можем захотеть перенаправить вывод всех операторов echo в файл журнала без явного указания оператора перенаправления и имени файла журнала после каждого оператора echo. Команда Bash exec - это мощная встроенная утилита, которую можно использовать для этой цели. В этом руководстве мы рассмотрим, как можно использовать команду exec для добавления протоколирования ошибок и вывода в сценарии оболочки. Мы также рассмотрим другие способы использования этой команды в сценариях командного интерпретатора.
Основы
При выполнении любой команды в оболочке Bash по умолчанию создается подоболочка, и порождается новый дочерний процесс (forked) для выполнения команды. Однако при использовании команды exec команда, следующая за exec, заменяет текущую оболочку. Это означает, что под-оболочка не создается, а текущий процесс заменяется этой новой командой.
Давайте проведем эксперимент, чтобы лучше понять это:
1 2 3 4 5 6 7 | pstree -p init(1)─┬─init(52)───bash(53) ├─init(78)───bash(79)───pstree(108) └─{init}(7) echo $$ 79 exec sleep 300 |
Мы проверили PID текущей оболочки с помощью команд echo $$ и pstree, а затем выполнили sleep на 300 секунд с помощью exec. Давайте теперь переключимся на другой терминал, чтобы проверить список процессов:
1 2 3 4 5 | ps -aef | grep $USER user1 53 52 0 23:37 tty2 00:00:00 -bash user1 79 78 0 23:39 tty1 00:00:00 sleep 300 user1 111 53 0 23:40 tty2 00:00:00 ps -aef user1 112 53 0 23:40 tty2 00:00:00 grep --color=auto |
Как мы видим, PID 79, который изначально был назначен на Bash, теперь назначен на команду sleep.
Мы также наблюдаем, что после завершения 300-секундного сна сессия (терминал), имевшая PID 79, вышла, поскольку оболочка была заменена командой exec, и ее выполнение завершилось.
Замена процесса с помощью команды exec
Замена существующего процесса другой командой может быть мощным инструментом. Давайте рассмотрим эту идею на других примерах.
Профиль входа пользователя
Предположим, что Bash не является оболочкой по умолчанию на нашем Linux. Интересно, что с помощью команды exec мы можем заменить оболочку по умолчанию в памяти на оболочку Bash, добавив ее в профиль входа пользователя:
1 | exec bash |
Бывают сценарии, когда мы хотим добавить определенную программу или меню в профиль входа пользователя (.bashrc или .bash_profile), и в таких случаях мы можем предотвратить доступ пользователя к интерпретатору Bash после завершения программы независимо от статуса завершения:
1 | exec menu.sh |
Вызовы программ внутри сценариев
Мы можем вызывать скрипты или другие программы внутри скрипта с помощью exec, чтобы перекрыть существующий процесс в памяти. Это экономит количество создаваемых процессов и, следовательно, системные ресурсы. Такая реализация особенно полезна в случаях, когда мы не хотим возвращаться в основной скрипт после выполнения подскрипта или программы:
1 2 3 4 5 6 7 8 9 10 11 12 | #! /bin/bash while true do echo "1. Статистика диска " echo "2. Отправить отчет " read Input case "$Input" in 1) exec df -kh ;; 2) exec /home/SendReport.sh ;; esac done |
В этом простом сценарии, управляемом пользовательским вводом, мы выполнили команду df и сценарий с помощью exec в различных пунктах меню.
Дескрипторы файлов и протоколирование в сценариях Shell с помощью команды exec
Команда exec - это мощный инструмент для манипулирования дескрипторами файлов (FD), создания вывода и протоколирования ошибок в сценариях с минимальными изменениями. В Linux по умолчанию файловым дескриптором:
- 0 - является stdin (стандартный ввод)
- 1 - stdout (стандартный вывод)
- 2 - stderr (стандартная ошибка).
Ведение журнала в сценариях
Мы можем динамически открывать, закрывать и копировать stdout для выполнения операций протоколирования. Давайте перенаправим stdout (FD 1) в файл журнала:
1 2 3 4 5 | #! /bin/bash script_log="/tmp/log_`date+%F`.log" exec 1>>$script_log echo "Это будет записано в лог-файл, а не в терминал...". echo "Это тоже..." |
Мы проверили, как мы можем записывать стандартный вывод в файлы, теперь давайте проверим, как мы можем также записывать стандартную ошибку в тот же файл:
1 2 3 4 5 6 7 8 | #! /bin/bash script_log="/home/shubh/log_`date+%F`.log" exec 1>>$script_log exec 2>&1 date echo "Вышеприведенная команда неверна, ошибка будет записана в лог-файл" date echo "Вывод правильной команды date также будет записан в лог-файл, включая эти заявления echo" |
Здесь мы скопировали stderr (2) в stdout (1), а stdout уже был изменен для записи в файл журнала.
Изменение Stdin для чтения из файла
Давайте создадим пример входного файла:
1 | cat input.csv |
Теперь запустим пример для чтения из созданного нами файла:
1 2 3 4 5 6 7 8 | #!/bin/bash exec < input.csv read row1 echo -n "Содержимым первой строки является: " echo $row1 echo -n "Содержимым второй строки является: " read row2 echo $row2 |
И вот что мы получаем:
Изменение файловых дескрипторов и восстановление значений по умолчанию
Мы также можем открывать и закрывать новые файловые дескрипторы для чтения и записи из файлов. Для демонстрации этого воспользуемся тем же входным файлом, что и в предыдущем разделе:
1 2 3 4 5 6 7 8 9 | #! /bin/bash exec 3< input.csv read -u 3 row1 echo $row1 exec 3<&- exec 4>&1 exec > out.txt echo "Выходные данные будут записаны в файл out.txt" exec 4>&- |
Здесь мы сначала открыли входной файл на FD 3, прочитали из файла и вывели его содержимое на терминал (по умолчанию на stdout). Наконец, мы использовали &- для закрытия FD 3. Когда этот сценарий выполняется, он выводит следующие данные:
Кроме того, скрипт создает выходной файл out.txt:
1 | cat out.txt |
Вывод будет зарегистрирован в файле out.txt
Обратите внимание, что вывод скрипта содержит только первый оператор echo. Интересно, что после первого оператора echo мы скопировали stdout на FD 4 и перенаправили его в другой файл. Позже, в последней строке, мы восстановили доступ к stdout, закрыв FD 4.
Запуск сценариев в чистой среде
Мы можем сбросить все переменные окружения для чистого запуска с помощью опции -c:
1 | exec -c printenv |
Так как команда printenv перечисляет переменные окружения, то при передаче ее в качестве аргумента команде exec здесь печатается пустой вывод.
Использование с командой Find
Опцию exec можно использовать для выполнения таких операций, как grep, cat, mv, cp, rm и многих других над файлами, найденными командой find. Давайте воспользуемся примером из нашей статьи о команде find, чтобы найти все файлы .java, содержащие слово "interface" в каталоге src:
1 | find src -name "*.java" -type f -exec grep -l interface {} \; |
Здесь мы указали опцию -type f, чтобы найти только обычные файлы. Затем мы использовали опцию -exec для выполнения команды grep в списке файлов, возвращенных командой find. Обратите внимание, что точка с запятой в конце заставляет команду grep выполняться для каждого файла по очереди, поскольку {} заменяется именем текущего файла. Обратите внимание, что обратная косая черта необходима для того, чтобы точка с запятой не интерпретировалась оболочкой.
Заключение
В этом руководстве мы рассмотрели различные варианты использования встроенной в Bash команды exec в сценариях оболочки.
Проще говоря, это мощный инструмент для замены процессов. В частности, ее использование с дескрипторами файлов в сценариях делает ее одним из самых мощных инструментов для гибкого протоколирования сценариев.