В сценарии bash, использующем чтение индикатора выполнения zenity --progress из канала, как безопасно завершить конвейерный процесс?

Я показываю прогресс команды в скрипте bash. Выходные данные команды передаются в zenity --progress и могут работать в течение длительного времени. Я хочу прервать его (убить эту команду), если я отменяю диалог zenity:

( echo 0; command; echo 100 ) | if ! zenity --progress
                                then DO_SOMETHING_TO_KILL_command
                                fi

Все решения, которые я нашел, либо:

  1. в общем случае kill "command" с помощью pgrep, pidof, pkill, killall и т. д. Это не то, что я хочу, так как может быть много таких "запущенных" команд.

  2. создайте 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 $!
Другие вопросы по тегам