繁体   English   中英

将多个参数传递给UNIX shell脚本

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM