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

时间:2010-12-24 20:48:19

标签: bash shell unix arguments

我有以下(bash)shell脚本,理想情况下我会用它来按名称杀死多个进程。

#!/bin/bash
kill `ps -A | grep $* | awk '{ print $1 }'`

然而,虽然这个脚本有效但是传递了一个参数:

  

结束铬

(脚本名称结束)

如果传递了多个参数,则它不起作用:

  

$ end chrome firefox

     

grep:firefox:没有这样的文件或目录

这里发生了什么?

我认为$*按顺序将多个参数传递给shell脚本。我没有在输入中输入任何内容 - 我要杀死的程序(chrome和firefox)是开放的。

感谢任何帮助。

4 个答案:

答案 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; }')

我非常喜欢$(...)语法来做反引号。它更加清晰,当你筑巢时它也不那么模糊。