В сценарии bash, использующем чтение индикатора выполнения zenity --progress из канала, как безопасно завершить конвейерный процесс?
Я показываю прогресс команды в скрипте bash. Выходные данные команды передаются в zenity --progress и могут работать в течение длительного времени. Я хочу прервать его (убить эту команду), если я отменяю диалог zenity:
( echo 0; command; echo 100 ) | if ! zenity --progress
then DO_SOMETHING_TO_KILL_command
fi
Все решения, которые я нашел, либо:
в общем случае kill "command" с помощью pgrep, pidof, pkill, killall и т. д. Это не то, что я хочу, так как может быть много таких "запущенных" команд.
создайте fifo для вывода PID "команды" (command & echo $! >some_fifo; wait), а затем прочитайте ее после канала.
Решение 2. делает то, что я хочу, но слишком сложно (см., Например, пример здесь (на французском языке)). Я хочу по возможности избегать fifo или временных файлов. Кажется, что это можно сделать с перенаправлением вывода на дескриптор файла, но я не могу понять, как именно.
Примечание: команда подстановки для всей группы с перенаправлением, например, $( ( ... command& echo >&3 ... | zenity --progress) 3>&1) - что является общим вариантом в случаях этого типа - делает здесь не работает, потому что $(...) ожидает завершения всей подоболочки.
3 ответа
Простая версия POSIX:
( # the pipe creates an implicit subshell; marking it explicit
( sleep 10; echo 100 )& echo $!
) | (
read PIPED_PID; zenity --progress || kill $PIPED_PID
)
который работает также, если zenity
выходит из строя. Даже сняв первый command
(sleep 10
в этом случае) echo $!
всегда будет выводить сначала в канал, поэтому мы читаем его непосредственно перед запуском индикатора выполнения.
Еще более простой вариант этого, когда длинный command
(sleep 10
выше) не выводит номера прогресса:
sleep 10 & PIPED_PID=$!
tail -f /dev/null --pid $PIPED_PID | ( zenity --progress || kill $PIPED_PID )
wait
не работает здесь, потому что труба делает подоболочку, которая является родным из &
подпроцесс. Это работает на других оболочках, кроме bash, но tail --pid
не является стандартом POSIX. POSIX версия этого:
sleep 10 & PIPED_PID=$!
while kill -s 0 $PIPED_PID; do sleep .1; done | ( zenity --progress || kill $PIPED_PID )
Одна из самых надежных реализаций:
#!/bin/bash
( # the pipe creates an implicit subshell; marking it explicit
(
sleep 10 & echo $! >&3
wait
echo 100 # to stdout, first pipe
) | (
if ! zenity --progress
then echo zen_progress_aborted >&3
fi
)
) 3>&1 | (
read FD3_FIRST
read FD3_SECOND
# the PID comes first and the zenity output second almost always
#+but we cannot be sure so:
if [ "$FD3_SECOND" = "zen_progress_aborted" ]
then kill $FD3_FIRST
elif [ "$FD3_FIRST" = "zen_progress_aborted" ]
then kill $FD3_SECOND
fi
)
# 'echo 100' after 'wait' means zenity does not exit after the command ends
#+so no need to kill it in this case
exit 0
Он значительно сложнее, чем использование "coproc", но он переносим POSIX и надежен в редком случае, когда второй "echo" обгоняет первый (например, сбой zenity из-за ошибки, а подпроцесс запускается позже)
Это должно работать:
coproc { echo 0; command; echo 100; }
zenity --progress <&${COPROC[0]} || kill $!