【问题标题】:bash: silently kill background function processbash:静默杀死后台函数进程
【发布时间】:2011-08-08 19:24:21
【问题描述】:

贝壳大师,

我有一个 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

这完全破坏了我原本非常酷的进度条显示的威力。

我如何摆脱这条消息?

【问题讨论】:

  • +1 用于在 re: bash 脚本中使用“非常酷”:)
  • 即使将 kill foo_pid 更改为 kill $foo_pid,我也无法重现。
  • @Tanktalus,我认为这是因为脚本可能在输出发送到 stderr 之前就死了。我在伪代码的末尾添加了一个睡眠,它应该使您能够重新创建问题。
  • while [ 1 ]; do可以写成while :; do
  • 这应该与stackoverflow.com/questions/81520/… 合并,后者更专注但缺少一些来自这里的答案。

标签: linux bash shell unix scripting


【解决方案1】:

错误消息应该来自在脚本中转储信号源的默认信号处理程序。我只在 bash 3.x 和 4.x 上遇到了类似的错误。为了始终安静地杀死子进程(在 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]'

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

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

      更新

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

      这个答案对后台作业无效的原因是 在 kill 命令完成后,异步 Bash 自身, 输出有关已终止作业的状态消息,您不能 直接抑制 - 除非您使用等待,如在接受的答案中。

      【讨论】:

      • 在尝试杀死不存在的进程以防止 kill foo_pid failed: no such process 消息时帮助我
      • @Koen。是的,但这与后台工作无关。您可以使用2>/dev/null 使kill 自身 发出的任何错误消息静音 - 与任何命令一样 - 使用2>/dev/null。这个答案对 background jobs 无效的原因是 Bash 本身 asynchronously kill 命令完成后,输出一个状态有关已终止作业的消息,您无法直接禁止该消息 - 除非您使用 wait,如已接受的答案中所示。
      • @mklement0 我今天真的试过了,发现只有kill $foo_pid 2>/dev/null 可以消除错误消息。
      • @Alaska,据我所知,只有在使用kill 命令以交互方式 终止作业时才会出现问题。 scripts 内的作业控制消息(如问题中所示)not 打印(除非您从交互式提示中source脚本 )。 (这与问题相矛盾)。换句话说:如果您从(非来源)脚本调用kill $foo_pidkill $foo_pid 2>/dev/null 就足够了(涵盖$foo_pid 不再存在的情况)。 以交互方式(或在以交互方式获取的脚本中),您需要{ kill $foo_pid && wait $foo_pid; } 2>/dev/null
      【解决方案4】:

      我自己也遇到了这个问题,并意识到我们正在寻找“不承认”。

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

      正在打印死亡消息,因为该进程仍在监视的“作业”的 shell 列表中。 disown 命令将从该列表中删除最近生成的进程,以便在杀死它时不会生成调试消息,即使使用 SIGKILL (-9) 也是如此。

      【讨论】:

      • 效果很好。我同意 - 这是 Bash 的最佳解决方案。但是 disown 是 Bash 内置命令,在大多数其他 shell 中不可用。
      • @mattst:确实值得指出disown 不符合POSIX;但是,它,在kshzsh 中也可用。
      • 另外,似乎使用disown 不仅仅意味着将当前shell 与后台进程解除关联:unix.stackexchange.com/a/148698/54804
      • @mklement0 感谢您的信息。和有趣的链接。 nohup 实际上对于我目前使用 disown 的几个脚本来说似乎是一个很好的解决方案。对于最初的问题disown 肯定是要使用的,我相信你已经意识到了。
      • @mattst: disown 对于手头的问题很好(如果终端过早死亡,后台作业将在下一次尝试写入标准输出时终止),但是,鉴于通用标题这个问题,值得指出disown 的含义不仅仅是让随后的kill 静音。
      【解决方案5】:

      另一种禁用作业通知的方法是将您的命令置于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
      

      【讨论】:

        【解决方案6】:

        另一种方法:

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

        调用它
            func_terminate_service "firefox"
        

        【讨论】:

          【解决方案7】:

          这是我针对类似问题提出的解决方案(希望在长时间运行的进程中显示时间戳)。这实现了一个 killsub 函数,只要你知道 pid,它就可以让你安静地杀死任何 subshel​​l。请注意,陷阱指令很重要,包括:如果脚本被中断,子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
          

          【讨论】:

            【解决方案8】:

            在函数开头添加:

            trap 'exit 0' TERM
            

            【讨论】:

            • 这适用于 macos。我用它来杀死尾巴:trap 'exit 0' TERM ; (killall -m tail 2>&1) >/dev/null
            【解决方案9】:
            kill $foo_pid
            wait $foo_pid 2>/dev/null
            

            顺便说一句,我不知道你的进度条很酷,但你看过 Pipe Viewer (pv) 吗? http://www.ivarch.com/programs/pv.shtml

            【讨论】:

            • 这对我不起作用。我仍然得到“被信号 15 杀死”。写入终端。我正在尝试通过 ssh 执行此操作 - 我开始一个会话并启动一个进程,然后我再次 ssh 并终止该进程。当我尝试“等待 $pid”时,它表示该进程不是子进程(我认为是因为它是不同的会话),然后是“被信号 15 杀死”。仍然写入终端。在这种情况下有没有办法抑制这种情况?
            • 好东西;我建议{ kill $foo_pid && wait $foo_pid; } 2>/dev/null 以使目标进程不再存在的情况保持沉默。
            • bash 完成kill 命令之前不存在(远程)孩子死亡的可能性,从而生成终止报告吗?
            • 谢谢@mklement0 - 你也可以使用kill && wait 模式和工作ID:{ kill %1 && wait %1; } 2>/dev/null
            • 其实我试过了,但只有kill $foo_pid 2>/dev/null对我有用。
            【解决方案10】:

            这个“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
            

            它的灵感来自here。世界秩序已经恢复。

            【讨论】:

            • 这行得通,但不需要kill $foo_pid 之后的>/dev/null 2>&1 部分,因为stderr(这是不需要的文本的来源)已经被定向到/dev/null
            猜你喜欢
            • 1970-01-01
            • 2014-08-09
            • 1970-01-01
            • 1970-01-01
            • 2013-08-19
            • 1970-01-01
            • 2015-04-02
            • 1970-01-01
            • 2017-06-15
            相关资源
            最近更新 更多