【问题标题】:in a bash script that uses zenity --progress progress bar reading from a pipe, how to safely kill the piped process?在使用 zenity --progress 从管道读取进度条的 bash 脚本中,如何安全地终止管道进程?
【发布时间】:2013-03-07 08:25:28
【问题描述】:

我正在 bash 脚本中显示命令进度。命令输出通过管道传送到 zenity --progress 并最终可以运行很长时间。如果我取消 zenity 对话框,我想中止它(杀死该命令):

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

我找到的所有解决方案:

  1. 一般用 pgrep、pidof、pkill、killall 等杀死“命令”。这不是我想要的,因为可能有很多这样的“命令”在运行。

  2. 创建一个fifo来输出“command”的PID(command & echo $!>some_fifo;wait),然后在管道之后从中读取。

解决方案 2. 做我想要的,但是以一种过于复杂的方式(参见例如一个例子 here(法语))。如果可能,我想避免使用 fifos 或临时文件。似乎最多可以通过将输出重定向到文件描述符来完成,但我不知道具体如何。

注意:使用重定向替换整个组的命令,例如 $( ( ... command& echo >&3 ... | zenity --progress ) 3>&1 ) - 这是这种类型的通用解决方案-- 在这里不起作用,因为 $(...) 会等到整个子 shell 完成。

【问题讨论】:

  • 只有在我们运行 'zenity --progress --pulsate' 的情况下才需要'echo 0';当 'command' 不输出数字时,通常这样看起来更好。

标签: bash progress fifo zenity


【解决方案1】:

最强大的实现之一:

#!/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 由于错误而失败并且子进程碰巧稍后开始)

【讨论】:

    【解决方案2】:

    一个简单的 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 在这里不起作用,因为管道创建了一个子shell,它是& 子进程的兄弟。这适用于 bash 以外的 shell,但 tail --pid 不是 POSIX 标准。 POSIX 版本:

    sleep 10 & PIPED_PID=$!
    while kill -s 0 $PIPED_PID; do sleep .1; done | ( zenity --progress || kill $PIPED_PID )
    

    【讨论】:

      【解决方案3】:

      这应该可行:

      coproc { echo 0; command; echo 100; }
      zenity --progress <&${COPROC[0]} || kill $!
      

      【讨论】:

      • 非常好的、紧凑的解决方案。仅: 1) 在协同进程结束之前必须运行第二行的意义上,它是脆弱的。行之间的微小等待会破坏
      • coproc 的另一个问题是 cannot be redirected to a subshell。如果 zenity 成功,我想运行一些命令,如果失败,我想运行其他命令,但我必须在不产生子 shell 的情况下执行此操作。
      • 重定向到 subshel​​l 问题很容易克服: exec 3
      猜你喜欢
      • 2021-07-24
      • 1970-01-01
      • 2013-01-26
      • 2015-03-28
      • 1970-01-01
      • 2020-09-01
      • 1970-01-01
      • 2018-11-29
      • 2021-10-13
      相关资源
      最近更新 更多