【问题标题】:Bash script kill command in for loopfor循环中的Bash脚本kill命令
【发布时间】:2025-11-28 07:00:01
【问题描述】:

我想杀死所有包含一些字符串的进程。我为此编写了脚本。但是,当我执行它时,它会在 for 循环的第一次迭代后获得“Killed”信号。这是我的代码:

#!/bin/bash

executeCommand () {
 local pname="$1";
 echo $HOSTNAME;
 local  search_terms=($(ps aux | grep $pname | awk '{print $2}'))
 for pros in "${search_terms[@]}";  do
     kill -9 "$pros"
     echo $pros
 done
exit
}

executeCommand "$1"  # get the string that process to be killed contains

我像./my_script.sh zookeeper 一样执行它。 当我删除包含kill 命令的行时,for loop 会一直执行到结束,否则,在第一个kill 命令之后,我得到一个输出“Killed”并且程序退出。

这可能是什么原因,以及实现我目标的任何其他解决方案?

【问题讨论】:

  • “实现[你的]目标的其他解决方案”——你考虑过pkill吗?
  • 顺便说一句,您的代码是否有可能在名为 zookeeper 的用户帐户下运行?
  • 不。事实上,如果我从 for 循环输出 PID 并手动杀死 -9 它们一切正常。但是当把它们放在一起时,我就有麻烦了。我现在正在检查您提出的解决方案。
  • 您只在脚本执行期间收到错误是您应该预料到的行为,并且不会对此感到惊讶——只有在循环运行时,例如grep(或脚本的其他组件)是活跃存在的 PID,因此可以在列表中找到并终止。
  • 除非你知道-15(默认)和-2不起作用,否则不要使用kill -9

标签: linux bash shell scripting


【解决方案1】:

执行此操作的愚蠢(错误、错误)方法是将grep -v grep 添加到您的管道中:

# ${0##*/} expands to the name of the running script
# ...thus, we avoid killing either grep, or the script itself
ps aux | grep -e "$pname" | egrep -v "grep|${0##*/}" | awk '{print $2}'

更好的方法是使用为工作而构建的工具:

# pkill already, automatically, avoids killing any of its parent processes
pkill "$pname"

也就是说,一开始就按名称匹配进程是一种不好的做法——您还会杀死less yourproc.logvim yourproc.conf,而不仅仅是yourproc。不要这样做;相反,请使用适当的进程监督系统(upstart、DJB daemontools、Apple launchd、systemd 等)来监控您长时间运行的守护进程,并在需要时杀死或重新启动它们。


顺便说一句——根本不需要for 循环:kill 可以在一次调用中传递多个 PID,如下所示:

# a bit longer and bash-specific, but avoids globbing
IFS=$'\n' read -r -d '' -a pids \
  < <(ps auxw | awk -v proc="$pname" -v preserve="${0##*/}" \
      '$0 ~ proc && $0 !~ preserve && ! /awk/ { print $2 }' \
      && printf '\0')
kill -- "${pids[@]}"

...也可以表述为:

# setting IFS and running `set -f` necessary to make unquoted expansion safe
( IFS=$'\n'; set -f; exec kill -- \
  $(ps auxw | awk -v proc="$pname" -v preserve="${0##*/}" \
    '$0 ~ proc && $0 !~ preserve && ! /awk/ { print $2 }') )

【讨论】:

  • 我能想到的另一个选项是killall "$pname"
  • 感谢您的回复。我尝试了 pkill "$pname"。它不会停止进程运行。我从 ps -aux | 检查它grep 动物园管理员。也许它需要一些像 -9 这样的选项,比如在 kill 中。我按照您建议的方式更改了管道。我得到如下输出:cloud-12 22770 23030 Terminated。因此,并非所有进程都被再次杀死。不知怎的,我在中间收到了一个信号
  • Grepping ps 是首先可靠地杀死进程树的错误方法。 fuser -k,在识别(或安排拥有)一个需要终止的进程树打开的文件之后,是你的朋友。
  • 顺便说一句,pkill 具有防止杀死调用它的 shell 的父进程的安全措施。你想要那些安全,在这里;考虑运行pkill -9
  • I 要做的一件事是在您的管道中放置一个tee procs-to-kill.log,就在awk 命令之前;这样您就可以查看该日志文件以查看实际被杀死的内容。
【解决方案2】:

grep 会显示,它是自己的进程。应该使用 grep -v 选项删除它

这样试试

for i in ` ps -ef | grep "$pname" | grep -v grep | awk '{print $2}'`
do    
  kill -9 $i
done

【讨论】:

  • 我编辑添加了缺少的代码格式,但代码中仍然存在一些错误。
  • 编辑后更好,但仍然有问题——$pname 不会在不应该出现的空白处扩展,如果 IFS 设置为一个值,则会由于未引用的扩展而中断不包括空格(或者特别是如果它设置为一个包含 PID 中可能存在的数字的值),并且在现代(1991 年之后的 POSIX-sh 标准)shell 中不赞成使用反引号而不是 $()
  • cloud-12 22770 23030 已终止。这是我得到的输出。并不是所有的 zookeeper 进程都被杀死。输出被杀者的 PID,然后显示“终止”消息