Bash 向UNIX shell脚本传递多个参数

Bash 向UNIX shell脚本传递多个参数,bash,shell,unix,arguments,Bash,Shell,Unix,Arguments,我有以下(bash)shell脚本,理想情况下我会使用它来按名称杀死多个进程 #!/bin/bash kill `ps -A | grep $* | awk '{ print $1 }'` 但是,当此脚本工作时,会传递一个参数: 端铬 (脚本的名称为end) 如果传递了多个参数,则该选项无效: $end chrome firefox grep:firefox:没有这样的文件或目录 这是怎么回事 我认为$*会按顺序将多个参数传递给shell脚本。我没有在我的输入中输入任何错误——我想杀死的程序(

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

#!/bin/bash
kill `ps -A | grep $* | awk '{ print $1 }'`
但是,当此脚本工作时,会传递一个参数:

端铬

(脚本的名称为end)

如果传递了多个参数,则该选项无效:

$end chrome firefox

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

这是怎么回事

我认为
$*
会按顺序将多个参数传递给shell脚本。我没有在我的输入中输入任何错误——我想杀死的程序(chrome和firefox)是打开的


感谢您的帮助。

请查看,或者作为@khachik注释。

要回答您的问题,需要做的是将
$*
扩展为参数列表,因此第二个和后面的单词看起来像
grep(1)
的文件

要按顺序处理它们,您必须执行以下操作:

for i in $*; do
    echo $i
done
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
在这种情况下,通常使用“$@”(带引号)代替“$*”


请参见
mansh
,并查看
killall(1)
pkill(1)
,以及
pgrep(1)

$*
应该很少使用。我通常会推荐
“$@”
。Shell参数解析相对复杂,并且容易出错。通常,你搞错的方式是让不应该被评估的事情结束

例如,如果键入以下内容:

end '`rm foo`'
你会发现,如果你有一个名为“foo”的文件,你就不再需要它了

下面是一个脚本,它将完成您要求完成的任务。如果任何参数包含
'\n'
'\0'
字符,则失败:

#!/bin/sh

kill $(ps -A | fgrep -e "$(for arg in "$@"; do echo "$arg"; done)" | awk '{ print $1; }')

我非常喜欢使用
$(…)
语法来完成backtick所做的事情。当你嵌套东西时,它更清晰,也不那么模棱两可。

记住
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
的开销太痛苦(写它让我很痛苦,但我没有衡量成本),那么您可能会做如下操作:

for i in $*; do
    echo $i
done
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
使用plain
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也不允许使用
案例中条件的前括号)。但是,您可以使用数组来正确处理事情

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/'
或类似的内容,以提供一个默认的-不存在的-搜索命令。

是否有任何原因不能使用
killall
而不是grepping<代码>基尔铬;killall firefox
?或$*中pname的
;做killall“$pname”;完成
killall“$@”
就足够了。仅供参考,永远不要使用
$@
,很少使用
$*
,也不要使用
“$*”
当你的意思是
“$@”
。这不是一个答案,只是一个旁白,因此有这样一个评论:这是我自己做过无数次的最喜欢的“grep on ps”问题:如果你grep了例如“process”,你也会点击“process-a”、“process-b”等,使用“grep”而不是“fgrep”意味着您的参数是regexp,而不是简单的进程名称,这可能不是您想要的。您不需要为每个命名参数重复“
-e
”吗
fgrep-e firefox-e chrome
?@Johnathan Leffler-No.
“$(…)”
构造将输出视为一个参数。因此,您将向fgrep发送一个带有内嵌换行符的字符串作为参数。fgrep将接受这样一个参数,并将所有单独的“行”视为要搜索的单个字符串,就像它们在