Bash: подстановка процесса

03 Jul 2018

2018-07-03-22:14:15_267x133Рассмотрим достаточно полезную штуку в консоли линукса (bash) как подстановка процесса.

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

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

Избавляемся от пайпов

Имеем две команды, которые формируют некоторый список значений и мы хотим сделать diff или любую другую операцию, которая принимает на вход в качестве аргументов имена файлов.

Начнем с простого примера: хотим сравнить выводы двух команд.

$ sleep $((RANDOM%2)) && curl -v --silent https://google.com | grep date

На месте этой абсурдной команды может быть что угодно: обработка логов, данные телеметрии с сервера и т.п.

Как сделать diff? Два варианта: перенаправить выводы обеих команд в пайпы и использовать подстановку процессов в форме чтения вывода команды.

\<(command)
% mkfifo f1  
$ mkfifo f2  
$ sleep $((RANDOM%2)) && curl -v --silent https://google.com | grep date \> f1 &  
$ sleep $((RANDOM%2)) && curl -v --silent https://google.com | grep date \> f2 &  
$ diff f1 f2  
$ rm f1 f2

Либо более лаконично.

$ diff \<(sleep $((RANDOM%2)) && curl -v --silent https://google.com | grep date) \<(sleep $((RANDOM%2)) && curl -v --silent https://google.com | grep date)

Согласитесь, что второй вариант проще в создании. так как оболочка сама позаботилась о создании и удалении каналов. Другая форма использования перенаправить некоторый stdin на вход другой команды или цепочки команд.

\>(command)

Допустим мы хотим записывать логи подключения разных групп устройств из udevadm в разные файлы. Можно запустить два процесса, но зачем?

$ udevadm monitor | tee \>(grep card \> card.log) \>(grep usb \> usb.log)

Тут не стоит забывать, что множественное перенаправление данных позволительно сделать при помощи tee, который берез stdin и пишет его во все указанные в качестве аргументов файлы.

Сохранение контекста

Подпроцесс - это одно из ключевых понятий bash и других оболочек.

 $ export foo=baz  
$ perl -e 'system "echo foo is \$foo"; $ENV{"foo"}="bar"; system "echo foo is \$foo"'  
$ echo "$foo"

Perl запускается как дочерний процесс, родителем которого выступает bash (в нашем случае). Для данного процесса создается новый контекст, в который копируются переменные окружения и модификация контекста процесса не приводит к изменению контекста родителя.

Это приводит к тому, что модификация контекста внутри цепочки команд не отражается на глобальных переменных.

#!/usr/bin/env bash  
i=0  
sort test | while read line; do  
 i=$(($i+1))  
 echo $i  
done  
echo $i

В данном скрипте цикл while запускается как дочерний процесс, а следовательно у нас всегда будет выводиться 0. Хотя внутри самого цикла мы видим инкремент переменной.

Исправить ситуацию можно при помощи подстановки команд.

#!/usr/bin/env bash  
i=0  
while read line; do  
 i=$(($i+1))  
 echo $i  
done \< \<(sort test)  
echo $i

Литература