将多个参数传递给 UNIX shell 脚本 [英] Passing multiple arguments to a UNIX shell script

查看:30
本文介绍了将多个参数传递给 UNIX shell 脚本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下 (bash) shell 脚本,我最好用它来按名称杀死多个进程.

I have the following (bash) shell script, that I would ideally use to kill multiple processes by name.

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

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

However, while this script works is one argument is passed:

结束铬

(脚本名称为end)

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

it does not work if more than one argument is passed:

$end chrome firefox

$end chrome firefox

grep: firefox: 没有那个文件或目录

grep: firefox: No such file or directory

这里发生了什么?

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

I thought the $* passes multiple arguments to the shell script in sequence. I'm not mistyping anything in my input - and the programs I want to kill (chrome and firefox) are open.

感谢任何帮助.

推荐答案

记住 grep 对多个参数的作用 - 第一个是要搜索的词,其余是要扫描的文件.

Remember what grep does with multiple arguments - the first is the word to search for, and the remainder are the files to scan.

还要记住 $*"$*"$@ 都丢失了参数中的空格,而神奇的"$@" 符号没有.

Also remember that $*, "$*", and $@ all lose track of white space in arguments, whereas the magical "$@" notation does not.

因此,为了处理您的情况,您需要修改调用 grep 的方式.你要么需要使用 grep -F(又名 fgrep)和每个参数的选项,或者你需要使用 grep -E(又名 fgrep)code>egrep) 与交替.在某种程度上,这取决于您是否可能需要处理本身包含管道符号的参数.

So, to deal with your case, you're going to need to modify the way you invoke grep. You either need to use grep -F (aka fgrep) with options for each argument, or you need to use grep -E (aka egrep) with alternation. In part, it depends on whether you might have to deal with arguments that themselves contain pipe symbols.

通过一次grep 的调用来可靠地做到这一点非常棘手;您最好忍受多次运行管道的开销:

It is surprisingly tricky to do this reliably with a single invocation of grep; you might well be best off tolerating the overhead of running the pipeline multiple times:

for process in "$@"
do
    kill $(ps -A | grep -w "$process" | awk '{print $1}')
done

如果像这样多次运行 ps 的开销太痛苦(写它让我很痛苦 - 但我没有衡量成本),那么你可能会这样做:

If the overhead of running ps multiple times like that is too painful (it hurts me to write it - but I've not measured the cost), then you probably do something like:

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)' 将匹配两者),则只有一个发送信号.

The use of plain xargs is OK because it is dealing with a list of process IDs, and process IDs do not contain spaces or newlines. This keeps the simple code for the simple case; the complex case uses a temporary file to hold the output of ps and then scans it once per process name in the command line. The sort -u ensures that if some process happens to match all your keywords (for example, grep -E '(firefox|chrome)' would match both), only one signal is sent.

陷阱行等确保临时文件被清理,除非有人对命令过于粗暴(捕获的信号是 HUP、INT、QUIT、PIPE 和 TERM,又名 1、2、3、13 和 15;零捕获出于任何原因退出的外壳).任何时候脚本创建临时文件时,您都应该对文件的使用进行类似的捕获,以便在进程终止时将其清除.

The trap lines etc ensure that the temporary file is cleaned up unless someone is excessively brutal to the command (the signals caught are HUP, INT, QUIT, PIPE and TERM, aka 1, 2, 3, 13 and 15; the zero catches the shell exiting for any reason). Any time a script creates a temporary file, you should have similar trapping around the use of the file so that it will be cleaned up if the process is terminated.

如果您感到谨慎并且拥有 GNU Grep,则可以添加 -w 选项,以便命令行上提供的名称仅匹配整个单词.

If you're feeling cautious and you have GNU Grep, you might add the -w option so that the names provided on the command line only match whole words.

以上所有内容都适用于 Bourne/Korn/POSIX/Bash 系列中的几乎所有 shell(您需要使用带有严格 Bourne shell 的反引号代替 $(...),以及 case 中条件的前导括号也不允许用于 Bourne shell).但是,您可以使用数组来正确处理事情.

All the above will work with almost any shell in the Bourne/Korn/POSIX/Bash family (you'd need to use backticks with strict Bourne shell in place of $(...), and the leading parenthesis on the conditions in the case are also not allowed with Bourne shell). However, you can use an array to get things handled right.

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/' 或类似的东西来提供一个默认的——不存在的——命令来搜索.

This carefully preserves spacing in the arguments and uses exact matches for the process names. It avoids temporary files. The code shown doesn't validate for zero arguments; that would have to be done beforehand. Or you could add a line args[0]='/collywobbles/' or something similar to provide a default - non-existent - command to search for.

这篇关于将多个参数传递给 UNIX shell 脚本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆