Bash:静默杀死后台函数进程



shell大师,

我有一个bash shell脚本,我在其中启动一个后台函数,比如foo(),以显示一个无聊而长的命令的进度条:

foo()
{
    while [ 1 ]
    do
        #massively cool progress bar display code
        sleep 1
    done
}
foo &
foo_pid=$!
boring_and_long_command
kill $foo_pid >/dev/null 2>&1
sleep 10

现在,当foo死亡时,我看到以下文本:

/home/user/script: line XXX: 30290 Killed                  foo

这完全破坏了我的进度条显示,否则会非常酷。

如何删除此消息?

kill $foo_pid
wait $foo_pid 2>/dev/null
顺便说一句,我不知道你的进度条有多酷,但你见过Pipe Viewer (pv)吗?http://www.ivarch.com/programs/pv.shtml

我自己刚刚遇到这个,并意识到"disown"是我们正在寻找的。

foo &
foo_pid=$!
disown
boring_and_long_command
kill $foo_pid
sleep 10

正在打印死亡消息,因为该进程仍在监视的"作业"的shell列表中。disown命令将从该列表中删除最近生成的进程,这样当它被杀死时,即使使用SIGKILL(-9)也不会生成任何调试消息。

尝试将kill $foo_pid >/dev/null 2>&1行替换为:

(kill $foo_pid 2>&1) >/dev/null

:

这个答案是不正确的,原因由@mklement0在他的评论中解释:

这个答案对后台工作无效的原因是Bash本身是异步的,在kill命令完成后,输出关于已终止作业的状态消息,您不能这样做直接抑制——除非你使用wait,就像被接受的答案一样。

这个"hack"似乎起作用了:

# Some trickery to hide killed message
exec 3>&2          # 3 is now a copy of 2
exec 2> /dev/null  # 2 now points to /dev/null
kill $foo_pid >/dev/null 2>&1
sleep 1            # sleep to wait for process to die
exec 2>&3          # restore stderr to saved
exec 3>&-          # close saved version

,灵感来自这里。世界秩序已经恢复。

这是我针对类似问题(希望在长时间运行的进程中显示时间戳)提出的解决方案。这实现了一个killsub函数,允许您在知道pid的情况下安静地终止任何子shell。注意,包含陷阱指令很重要:如果脚本被中断,子shell将不会继续运行。

foo()
{
    while [ 1 ]
    do
        #massively cool progress bar display code
        sleep 1
    done
}
#Kills the sub process quietly
function killsub() 
{
    kill -9 ${1} 2>/dev/null
    wait ${1} 2>/dev/null
}
foo &
foo_pid=$!
#Add a trap incase of unexpected interruptions
trap 'killsub ${foo_pid}; exit' INT TERM EXIT
boring_and_long_command
#Kill foo after finished
killsub ${foo_pid}
#Reset trap
trap - INT TERM EXIT

在函数开始处添加:

trap 'exit 0' TERM

另一种禁用作业通知的方法是将命令置于sh -c 'cmd &'构造的后台。

#!/bin/bash
foo()
{
   while [ 1 ]
   do
       sleep 1
   done
}
#foo &
#foo_pid=$!
export -f foo
foo_pid=`sh -c 'foo & echo ${!}' | head -1`
# if shell does not support exporting functions (export -f foo)
#arg1='foo() { while [ 1 ]; do sleep 1; done; }'
#foo_pid=`sh -c 'eval "$1"; foo & echo ${!}' _ "$arg1" | head -1`

sleep 3
echo kill ${foo_pid}
kill ${foo_pid}
sleep 3
exit

您可以使用set +m之前来抑制它。更多信息在这里

另一种方法:

    func_terminate_service(){
      [[ "$(pidof ${1})" ]] && killall ${1}
      sleep 2
      [[ "$(pidof ${1})" ]] && kill -9 "$(pidof ${1})" 
    }

调用
    func_terminate_service "firefox"

错误消息应该来自默认的信号处理程序,它会在脚本中转储信号源。我只在bash 3上遇到了类似的错误。X和4。为了总是悄悄地在所有地方杀死子进程(在bash 3/4/5、dash、ash、zsh上测试),我们可以在子进程的第一个位置捕获TERM信号:

#!/bin/sh
## assume script name is test.sh
foo() {
  trap 'exit 0' TERM ## here is the key
  while true; do sleep 1; done
}
echo before child
ps aux | grep 'test.s[h]|slee[p]'
foo &
foo_pid=$!
sleep 1 # wait trap is done
echo before kill
ps aux | grep 'test.s[h]|slee[p]'
kill $foo_pid
sleep 1 # wait kill is done
echo after kill
ps aux | grep 'test.s[h]|slee[p]'
 #!/bin/bash
 exec {safe2}>&2 2>/dev/null {fdevent}< <(
   sh -c 'echo $$; exec inotifywait -mqe CLOSE_WRITE /tmp' 2>&$safe2
   ) 2>&$safe2
 read -u $fdevent pidevent
 trap "$(trap -p EXIT)"$'n'"kill $pidevent" EXIT
 grep -m1 somevent <&$fdevent

说明一个特殊的情况,因为进程替换控件的错误文件描述符不能以其他方式访问。

相同的exec语句依次保存错误文件描述符,将错误替换为/dev/null以供进程替换继承,为进程替换输出分配新的文件描述符,并恢复原来的错误文件描述符。

在进程替换本身内部,原始错误文件描述符被激活,但不希望的"进程完成";消息将刷新到/dev/null

在退出时,inotifywait监视器将被静默终止。

然而,取决于要终止的进程,SIGPIPE或除SIGTERM以外的其他信号会导致静默退出,而不需要任何努力,并反映一个有意义的逻辑:

 #!/bin/bash
 exec {fdevent}< <(sh -c 'echo $$; exec inotifywait -mqe CLOSE_WRITE /tmp')
 read -u $fdevent pidevent
 ## work with file descriptor, sigpipe exception when done
 kill -PIPE $pidevent

最新更新