Практическое написание сценариев командной оболочки Bash/Bash подстановки: различия между версиями

Содержимое удалено Содержимое добавлено
Строка 276:
 
== Подстановка результата из подоболочки через автоматический файл ==
 
Иногда в сценариях требуется связывать дескрипторы выполняющихся в разных подоболочках команд. Например одна команда производит данные, а другая их потребляет. В основном это происходит по тому, что команды просто так спроектированы.
 
В старых версиях командных оболочек такую проблему решали так:
* Если команда производит данные, то она создает и пишет в некоторый файл, указываемый разработчиком явно. Файл можно создать, если перенаправить стандартный поток вывода команды в файл.
* Если команда потребляет данные из файла, то разработчик связывает стандартный поток ввода этой команды с дескриптором, ведущим на файл производителя.
 
Например
<source lang=bash>
#!/bin/bash
 
PRODUCER_FILE=$(mktemp -p /tmp tmp.XXXXXXXX)
 
# Для удаления временного файла после завершения сценария
trap "rm -f $PRODUCER_FILE" EXIT SIGKILL
 
producer() {
for arg; do
echo $arg
done
} >$PRODUCER_FILE
 
consumer() {
while read; do
echo $FUNCNAME: $REPLY
done
} <$PRODUCER_FILE
 
producer "alpha" "beta" "gamma"
echo "========================="
cat $PRODUCER_FILE
echo "========================="
consumer
# Результат:
# =========================
# alpha
# beta
# gamma
# =========================
# consumer: alpha
# consumer: beta
# consumer: gamma
</source>
Основным неудобством этого способа является необходимость иметь файл-посредник между двумя процессами: этот файл создаете вы и вы же отвечаете за его удаление.
 
Дальнейшее развитие интерпретаторов привело к появлению инструмента, называемого конвейером, который связывает стандартный поток вывода команды-производителя со стандартным потоком ввода команды-потребителя через специальный pipe-файл. Жизненным циклом этого файла уже управляет командная оболочка. Таким образом, наш пример можно переписать следующим образом через конвейер:
<source lang=bash>
#!/bin/bash
 
producer() {
for arg; do
echo $arg
done
}
 
consumer() {
while read; do
echo $FUNCNAME: $REPLY
done
}
 
producer "omicron" "kappa" | consumer
# Результат:
# consumer: omicron
# consumer: kappa
</source>
 
Вроде бы все хорошо, но теперь представьте, что данный код не оформлен через функции: есть только код, производящий данные, и код — потребляющий данные. В этом случае в Bash предусмотрен третий метод — через подстановку.
<source lang=bash>
#!/bin/bash
(
for arg in "alpha" "beta" "gamma"; do
echo $arg
done
)> >(
while read; do
echo Anonimous consumer: $REPLY
done
)
# Результат:
# Anonimous consumer: alpha
# Anonimous consumer: beta
# Anonimous consumer: gamma
</source>
Данная конструкция кажется сложной, но поймете ли вы ее смысл, если мы запишем ее в гибридном варианте:
<source lang=bash>
producer "alpha" "beta" "gamma" > >(
while read; do
echo Anonimous consumer: $REPLY
done
)
# Результат:
# Anonimous consumer: alpha
# Anonimous consumer: beta
# Anonimous consumer: gamma
#
# или так
consumer 2>/dev/null <<< $(
>(
for arg in "alpha" "beta" "gamma"; do
echo $arg
done
)
)
# consumer: alpha
# consumer: beta
# consumer: gamma
</source>
Во всех этих ситуациях мы использовали подстановку, которая называется ''Process Substitution''. Эта возможность позволяет создать своего рода анонимную функцию и связать ее стандартный ввод или вывод через автоматический pipe-файл. Анонимные функции запускаются асинхронно и передают свой ввод/вывод в специальный файл (в заголовке мы называем его автоматическим), назначаемый командной оболочкой. Можно посмотреть что это за файл такой, если написать в командной строке такую конструкцию
<source lang=bash>
$ echo >(true) <(true)
/dev/fd/63 /dev/fd/62
</source>
Собственно преимуществом такого подхода является то, что управлением жизненным циклом файла берет на себя система. Кроме того, мы можем писать код в сценарии на лету. С другой стороны, такие конструкции не переносимы.
 
== Скобочные подстановки ==