Как избежать эхо-закрытия именованных каналов FIFO? - Забавное поведение Unix FIFO

Я хочу вывести некоторые данные в канал, а другой процесс построчно что-то сделает с данными. Вот игрушечный пример:

mkfifo pipe
cat pipe&
cat >pipe

Теперь я могу вводить все, что хочу, и после нажатия клавиши ввода сразу же вижу ту же строку. Но если заменить вторую трубу на echo:

mkfifo pipe
cat pipe&
echo "some data" >pipe

Канал закрывается после завершения echo и cat pipe&, поэтому я не могу больше передавать данные по каналу. Есть ли способ избежать закрытия канала и процесса, который получает данные, чтобы я мог передавать много строк данных через канал из сценария bash и обрабатывать их по мере поступления?


person user1084871    schedule 07.12.2011    source источник


Ответы (6)


Когда FIFO открывается для чтения, он блокирует вызывающий процесс (обычно). Когда процесс открывает FIFO для записи, читатель разблокируется. Когда писатель закрывает FIFO, процесс чтения получает EOF (0 байтов для чтения), и больше ничего не может быть сделано, кроме как закрыть FIFO и снова открыть. Таким образом, вам нужно использовать цикл:

mkfifo pipe
(while cat pipe; do : Nothing; done &)
echo "some data" > pipe
echo "more data" > pipe

Альтернативой является сохранение некоторого процесса с открытым FIFO.

mkfifo pipe
sleep 10000 > pipe &
cat pipe &
echo "some data" > pipe
echo "more data" > pipe
person Jonathan Leffler    schedule 07.12.2011
comment
Вторая версия отлично справляется со своей задачей! Первый у меня не работает, потому что я не хочу перезапускать процесс, получающий данные. - person user1084871; 07.12.2011
comment
Вы можете обмануть и заставить cat держать канал открытым для записи, используя: cat pipe 3>pipe. Команда cat не будет использовать файловый дескриптор 3, но будет открывать для записи FIFO, называемый конвейером (хотя он будет читать его в другом файловом дескрипторе - вероятно, номер 4). - person Jonathan Leffler; 07.12.2011
comment
Разве exec >6 pipe не достигает того же? Обычно присваивает pipe файловому дескриптору 6 и оставляет его открытым для записи. Вместо того, чтобы писать непосредственно в pipe, вы, вероятно, захотите записать в этот дескриптор, используя >&6, но в противном случае он должен держать его открытым iirc - person Haravikk; 17.02.2014
comment
@Haravikk: Нет, использование exec >6 pipe не сработает, даже если синтаксис исправлен на exec 6> pipe. Проблема в том, что процесс зависает, ожидая, пока какой-то другой процесс откроет канал для чтения, и единственный процесс, который планировал это сделать, - это тот, который заблокирован. - person Jonathan Leffler; 21.07.2014
comment
Совет: используйте tail -f вместо версии с sleep. - person danr; 29.12.2016
comment
@danr Престижность за более простую версию, которая просто делает то, что для этого предусмотрено! - person mitsos1os; 18.12.2020

Поместите все операторы, которые вы хотите вывести в FIFO, в одной подоболочке:

# Create pipe and start reader.
mkfifo pipe
cat pipe &
# Write to pipe.
(
  echo one
  echo two
) >pipe

Если у вас есть дополнительные сложности, вы можете открыть конвейер для записи:

# Create pipe and start reader.
mkfifo pipe
cat pipe &
# Open pipe for writing.
exec 3>pipe
echo one >&3
echo two >&3
# Close pipe.
exec 3>&-
person Mark Edgar    schedule 08.12.2011
comment
Отличный ответ, и на самом деле ему удается удерживать канал открытым для произвольно сложной последовательности команд. - person voetsjoeba; 08.10.2013
comment
Не могли бы вы вкратце объяснить, как это работает? Особенно последняя строка: exec 3>&- - person Nick Chammas; 29.07.2014
comment
@NickChammas: во втором примере exec 3>pipe открывает файловый дескриптор 3 для записи в pipe; две echo команды записывают в канал посредством перенаправления вывода; последний exec 3>&- - это то, как вы закрываете дескриптор открытого файла - в данном случае дескриптор 3. В этот момент cat, работающий в фоновом режиме, получает EOF и завершает работу. - person Jonathan Leffler; 20.03.2015

Вы можете решить эту проблему очень легко, открыв сторону чтения канала в режиме чтения-записи. Читатель получает EOF только после закрытия последнего писателя. Таким образом, открытие его в режиме чтения-записи гарантирует, что всегда есть хотя бы один писатель.

Так что измените свой второй пример на:

mkfifo pipe
cat <>pipe &
echo "some data" >pipe
person phemmer    schedule 02.11.2016
comment
С помощью этого метода я не могу понять, как закрыть канал, я могу только убить процесс cat, который может вести себя иначе. Например, если cat на самом деле была программой awk с блоком END, блок END не будет выполняться, когда он получит SIGTERM. - person pix; 06.07.2017
comment
@pix ваш вариант использования не совсем такой, как в исходном вопросе. Но, как упоминалось в ответе, читатель получает EOF только после закрытия последнего писателя, поэтому убедитесь, что всегда есть писатель. Например, exec 3>pipe, чтобы оболочка удерживала его в открытом состоянии. Или sleep inf >pipe &, чтобы запустить отдельный процесс, если вы хотите, чтобы он сохранялся после выхода из оболочки. - person phemmer; 01.06.2018
comment
Читатель получает EOF только после закрытия последнего писателя. - но поскольку ваш читатель также является писателем, последний закрывающийся писатель - это условие, которого вы никогда не достигнете, поскольку ваш читатель не выходит, пока не получит EOF, который произведет только его выход. - person Dev Null; 10.10.2018

Честно говоря, лучший способ заставить это работать - это использовать socat, который в основном соединяет два сокета.

mkfifo foo
socat $PWD/foo /dev/tty

Теперь в новом семестре вы можете:

echo "I am in your term!" > foo
# also (surprisingly) this works
clear > foo

Обратной стороной является то, что вам нужен socat, который доступен далеко не всем. Плюс в том, что я не могу найти что-то, что не работает ... Я могу печатать цвета, tee в FIFO, очищать экран и т. Д. Это как будто вы подчиняете весь терминал.

person Jordan    schedule 20.08.2018
comment
Спасибо! Это самое простое, эффективное и наименее неприятное решение, которое я когда-либо видел для этой проблемы! - person Jeffrey Cash; 04.12.2019

Я улучшил вторую версию ответа Джонатана Леффлера для поддержки закрытия трубы:

dir=`mktemp -d /tmp/temp.XXX`
keep_pipe_open=$dir/keep_pipe_open
pipe=$dir/pipe

mkfifo $pipe
touch $keep_pipe_open

# Read from pipe:
cat < $pipe &

# Keep the pipe open:
while [ -f $keep_pipe_open ]; do sleep 1; done > $pipe &

# Write to pipe:
for i in {1..10}; do
  echo $i > $pipe
done

# close the pipe:
rm $keep_pipe_open
wait

rm -rf $dir
person silyevsk    schedule 19.01.2015

В качестве альтернативы другим решениям здесь вы можете вызвать cat в цикле в качестве входных данных для вашей команды:

mkfifo pipe
(while true ; do cat pipe ; done) | bash

Теперь вы можете кормить его командами по одной, и он не закроется:

echo "'echo hi'" > pipe
echo "'echo bye'" > pipe

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

person Adam Dingle    schedule 14.09.2014