我有以下(bash)shell脚本,理想情况下我会用它来按名称杀死多个进程。
#!/bin/bash
kill `ps -A | grep $* | awk '{ print $1 }'`
然而,虽然这个脚本有效但是传递了一个参数:
结束铬
(脚本名称结束)
如果传递了多个参数,则它不起作用:
$ end chrome firefox
grep:firefox:没有这样的文件或目录
这里发生了什么?
我认为$*
按顺序将多个参数传递给shell脚本。我没有在输入中输入任何内容 - 我要杀死的程序(chrome和firefox)是开放的。
感谢任何帮助。
答案 0 :(得分:3)
记住grep
对多个参数的作用 - 第一个是要搜索的单词,其余的是要扫描的文件。
另请注意,$*
,"$*"
和$@
都会在参数中丢失空格,而神奇的"$@"
符号则不会。
因此,要处理您的案例,您需要修改调用grep
的方式。您需要使用grep -F
(又名fgrep
)和每个参数的选项,或者您需要使用grep -E
(又名egrep
)进行更改。在某种程度上,它取决于您是否必须处理自身包含管道符号的参数。
通过grep
的单次调用可靠地完成这项工作是非常棘手的。你可能最好能够容忍多次运行管道的开销:
for process in "$@"
do
kill $(ps -A | grep -w "$process" | awk '{print $1}')
done
如果像这样多次运行ps
的开销太痛苦了(写它会让我受伤 - 但我没有测量成本),那么你可能会做类似的事情:
case $# in
(0) echo "Usage: $(basename $0 .sh) procname [...]" >&2; exit 1;;
(1) kill $(ps -A | grep -w "$1" | awk '{print $1}');;
(*) tmp=${TMPDIR:-/tmp}/end.$$
trap "rm -f $tmp.?; exit 1" 0 1 2 3 13 15
ps -A > $tmp.1
for process in "$@"
do
grep "$process" $tmp.1
done |
awk '{print $1}' |
sort -u |
xargs kill
rm -f $tmp.1
trap 0
;;
esac
使用普通xargs
是可以的,因为它正在处理进程ID列表,而进程ID不包含空格或换行符。这保留了简单案例的简单代码;复杂的情况使用临时文件来保存ps
的输出,然后在命令行中为每个进程名称扫描一次。 sort -u
确保如果某个流程恰好匹配您的所有关键字(例如,grep -E '(firefox|chrome)'
将匹配两者),则只会发送一个信号。
陷阱线等确保临时文件被清除,除非有人对命令过于残忍(捕获的信号是HUP,INT,QUIT,PIPE和TERM,又称1,2,3,13和15;零捕获shell出于任何原因退出)。每当脚本创建临时文件时,您应该对文件的使用进行类似的捕获,以便在进程终止时将其清除。
如果您感到谨慎且有GNU Grep,则可以添加-w
选项,以便命令行中提供的名称仅匹配整个单词。
以上所有内容都适用于Bourne / Korn / POSIX / Bash系列中的几乎所有shell(您需要使用带有严格Bourne shell的反引号来代替$(...)
,以及条件的前导括号在case
中也不允许使用Bourne shell。但是,您可以使用数组来处理正确的事情。
n=0
unset args # Force args to be an empty array (it could be an env var on entry)
for i in "$@"
do
args[$((n++))]="-e"
args[$((n++))]="$i"
done
kill $(ps -A | fgrep "${args[@]}" | awk '{print $1}')
这会小心地保留参数中的间距,并使用与进程名称完全匹配。它避免了临时文件。显示的代码不验证零参数;这必须事先完成。或者你可以添加一行args[0]='/collywobbles/'
或类似的东西来提供一个默认的 - 不存在的 - 来搜索命令。
答案 1 :(得分:2)
要回答您的问题,正在进行的是$*
扩展为参数列表,因此第二个和后面的文字看起来像grep(1)
的文件。
要按顺序处理它们,您必须执行以下操作:
for i in $*; do
echo $i
done
在这种情况下,通常使用"$@"
(带引号)代替$*
。
请参阅man sh
,并查看killall(1)
,pkill(1)
和pgrep(1)
。
答案 2 :(得分:0)
改为pkill(1)
,或{@ 3}} @khachik评论。
答案 3 :(得分:0)
$*
。我通常会建议"$@"
。 Shell参数解析相对复杂且容易出错。通常你弄错的方法是最终评估不应该做的事情。
例如,如果您输入以下内容:
end '`rm foo`'
你会发现如果你有一个名为'foo'的文件,你就不会了。
这是一个脚本,可以完成您要求完成的任务。如果任何参数包含'\n'
或'\0'
个字符,则会失败:
#!/bin/sh
kill $(ps -A | fgrep -e "$(for arg in "$@"; do echo "$arg"; done)" | awk '{ print $1; }')
我非常喜欢$(...)
语法来做反引号。它更加清晰,当你筑巢时它也不那么模糊。