поведение coproc и именованного канала при подстановке команд

Глупый скрипт сделал это с моим bashrc, поэтому я запустил следующее, чтобы получить большую часть моего файла.

# Retrieve functions definitions
$ declare >.bashrc.new
# Retrieve aliases
$ alias >>.bashrc.new
# Retrieve env
$ echo "export $(env | grep ^PATH=)" >>.bashrc.new

Здесь большая часть вашего .bashrcпрактически реконструирована. Теперь вам просто нужно внести небольшие коррективы, чтобы убедиться, что он соответствует вашим лучшим практикам. ;)

Классная команда для поиска определения ваших пользовательских функций:type my_function >>.bashrc.new.

Кроме того, не забудьте просмотреть возвращаемое значение env, чтобы включить во вновь созданный файл то, что вы считаете важным.

5
08.03.2021, 20:07
2 ответа

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

mkfifo /tmp/foo.bar
echo 'Hello World!' > /tmp/foo.bar &

call_me_from_cmd_subst() {
    echo 'Hello Response!' > /tmp/foo.bar &
    echo 'Response Sent!'
}

echo "$(call_me_from_cmd_subst)"
cat /tmp/foo.bar

Подстановка команды $(call_me_from_cmd_subst)создает анонимный канал, соединяющий выходные данные подоболочки, выполняющей функцию, с исходным процессом оболочки. Исходный процесс читает из этого канала. Дочерний процесс создает для запуска дочерний процесс echo 'Hello Response!' > /tmp/foo.bar. Оба процесса начинаются с одних и тех же открытых файлов, включая анонимный канал. Внук выполняет перенаправление > /tmp/foo.bar. Это блокирует, потому что ничего не читается из именованного канала /tmp/foo.bar.

Перенаправление — это двухэтапный -процесс (на самом деле трехэтапный -но третий здесь не имеет значения ), потому что когда вы открываете файл, вы не можете выбрать его файл дескриптор. Оператор >хочет перенаправить стандартный вывод, т. е. он хочет связать определенный файл с файловым дескриптором 1. Это занимает три системных вызова:

  1. Позвоните fd = open("/tmp/foo.bar", O_RDWR), чтобы открыть файл. Файл будет открыт с некоторым файловым дескриптором fd, который процесс в данный момент не использует. Это шаг, который блокируется до тех пор, пока что-то не начнет читать из именованного канала /tmp/foo.bar:, открывая блоки именованного канала, если никто не слушает.
  2. Вызовите dup2(fd, 1), чтобы открыть файл с желаемым файловым дескриптором в дополнение к тому, который выбрал ядро. Если есть что-то открытое в новом дескрипторе (1 ), который есть (анонимный канал, используемый для подстановки команд ), он закрыт в этот момент.
  3. Вызовите close(fd), сохраняя цель перенаправления только на желаемом файловом дескрипторе.

Тем временем дочерний процесс печатает Reponse Sent!и завершает работу. Исходный процесс оболочки все еще читает из канала. Поскольку канал все еще открыт для записи во внучке, исходный процесс оболочки продолжает ждать.

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

call_me_from_cmd_subst() {
    { exec >&-; /bin/echo 'Hello Response!' > /tmp/foo.bar; } &
    echo 'Response Sent!'
}

или

call_me_from_cmd_subst() {
    { echo 'Hello Response!' > /tmp/foo.bar; } >/dev/null &
    echo 'Response Sent!'
}

или любое количество вариаций на эту тему.

У вас нет этой проблемы с сопроцессом, потому что он не использует именованный канал, поэтому одна из половин взаимоблокировки не блокируется:>/tmp/foo.barблоками при открытии именованного канала, но >&pне блокируется, так как просто перенаправляет уже -открытый файловый дескриптор.

5
18.03.2021, 22:26

Очень небольшая модификация вашего кода работает как положено:

#!/usr/bin/env zsh

rm -rf /tmp/foo.bar
mkfifo /tmp/foo.bar
print 'Hello World!' > /tmp/foo.bar &

call_me_from_cmd_subst() {
    get_contents=$(cat /tmp/foo.bar)
    print "Retrieved: $get_contents"
    (print 'Hello Response!' > /tmp/foo.bar &!) >/dev/null
    print 'Response Sent!'
}

# Run this first
#call_me_from_cmd_subst

# Then comment out the above call
# And run this instead
print "$(call_me_from_cmd_subst)"

# Hello Response!
cat /tmp/foo.bar

Выполнение этого приводит к ожидаемому:

Retrieved: Hello World!
Response Sent!
Hello Response!

Я не совсем уверен, почему это так — похоже, zsh считает, что печать в fifo может привести к какому-то выводу на стандартный вывод?

3
18.03.2021, 22:26

Теги

Похожие вопросы