[英]Passing multiple arguments to a UNIX shell script
我有以下(bash)shell脚本,我理想情况下用于按名称杀死多个进程。
#!/bin/bash
kill `ps -A | grep $* | awk '{ print $1 }'`
但是,虽然这个脚本有效,但是传递了一个参数:
结束铬
(脚本的名称结束)
如果传递了多个参数,它将不起作用:
$ end chrome firefox
grep:firefox:没有这样的文件或目录
这里发生了什么?
我认为$*
按顺序将多个参数传递给shell脚本。 我没有在输入中输入任何内容 - 我要杀死的程序(chrome和firefox)是开放的。
任何帮助表示赞赏。
记住grep
对多个参数的作用 - 第一个是要搜索的单词,其余的是要扫描的文件。
还要记住$*
, "$*"
和$@
都会在参数中丢失空格,而神奇的"$@"
表示法则没有。
因此,为了处理您的情况,您将需要修改调用grep
的方式。 你需要使用grep -F
(aka fgrep
)和每个参数的选项,或者你需要使用grep -E
(aka 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的反引号代替$(...)
,以及条件中的前导括号Bourne shell也不允许使用case
。 但是,您可以使用数组来正确处理事物。
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/'
或类似的东西来提供一个默认的 - 不存在的 - 来搜索命令。
要回答你的问题,正在发生的是$*
扩展到参数列表,因此第二个和后面的单词看起来像grep(1)
文件。
要按顺序处理它们,您必须执行以下操作:
for i in $*; do
echo $i
done
通常,在这种情况下,使用"$@"
(带引号)代替$*
。
见man sh
,并查看killall(1)
, pkill(1)
和pgrep(1)
。
相反,请查看pkill(1)
或killall(1)
作为@khachik的评论。
应该很少使用$*
。 我一般会推荐"$@"
。 Shell参数解析相对复杂且容易出错。 通常你弄错的方法是最终评估不应该做的事情。
例如,如果您输入以下内容:
end '`rm foo`'
你会发现,如果你有一个名为'foo'的文件,你就不会再这样了。
这是一个脚本,可以完成您要求完成的任务。 如果任何参数包含'\\n'
或'\\0'
字符,则会失败:
#!/bin/sh
kill $(ps -A | fgrep -e "$(for arg in "$@"; do echo "$arg"; done)" | awk '{ print $1; }')
我非常喜欢使用$(...)
语法来做反引号。 它更清晰,当你筑巢时它也不那么模棱两可。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.