bash 将多个参数传递给 UNIX shell 脚本
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4528292/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Passing multiple arguments to a UNIX shell script
提问by Waffles
I have the following (bash) shell script, that I would ideally use to kill multiple processes by name.
我有以下(bash)shell 脚本,我最好用它来按名称杀死多个进程。
#!/bin/bash
kill `ps -A | grep $* | awk '{ print }'`
However, while this script works is one argument is passed:
但是,虽然此脚本有效,但传递了一个参数:
end chrome
端铬
(the name of the script is end)
(脚本名称为end)
it does not work if more than one argument is passed:
如果传递了多个参数,则它不起作用:
$end chrome firefox
grep: firefox: No such file or directory
$end chrome firefox
grep: firefox: 没有那个文件或目录
What is going on here?
这里发生了什么?
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.
我认为$*
将多个参数按顺序传递给 shell 脚本。我没有在输入中打错任何东西 - 我想杀死的程序(chrome 和 firefox)是打开的。
Any help is appreciated.
任何帮助表示赞赏。
回答by Jonathan Leffler
Remember what grep
does with multiple arguments - the first is the word to search for, and the remainder are the files to scan.
记住grep
多参数的作用 - 第一个是要搜索的词,其余是要扫描的文件。
Also remember that $*
, "$*"
, and $@
all lose track of white space in arguments, whereas the magical "$@"
notation does not.
还要记住$*
,"$*"
, 和$@
都丢失了参数中的空格,而神奇的"$@"
符号不会。
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
. 您要么需要为每个参数使用grep -F
(aka fgrep
) 选项,要么需要交替使用grep -E
(aka egrep
)。在某种程度上,这取决于您是否可能必须处理本身包含管道符号的参数。
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:
使用单个调用来可靠地做到这一点非常棘手grep
;您可能最好忍受多次运行管道的开销:
for process in "$@"
do
kill $(ps -A | grep -w "$process" | awk '{print }')
done
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:
如果ps
像这样多次运行的开销太痛苦(写它让我很痛苦 - 但我没有衡量成本),那么你可能会做这样的事情:
case $# in
(0) echo "Usage: $(basename 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 }')
.sh) procname [...]" >&2; exit 1;;
(1) kill $(ps -A | grep -w "" | awk '{print }');;
(*) 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 }' |
sort -u |
xargs kill
rm -f $tmp.1
trap 0
;;
esac
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.
使用plainxargs
是可以的,因为它处理的是进程ID 列表,并且进程ID 不包含空格或换行符。这保留了简单情况下的简单代码;复杂的情况使用一个临时文件来保存输出,ps
然后在命令行中按进程名称扫描一次。在sort -u
确保如果某个进程恰好符合所有关键字(例如,grep -E '(firefox|chrome)'
将匹配两者),只有一个信号被发送。
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.
陷阱行等确保清理临时文件,除非有人对命令过于粗暴(捕获的信号是 HUP、INT、QUIT、PIPE 和 TERM,又名 1、2、3、13 和 15;零捕获shell 因任何原因退出)。任何时候脚本创建临时文件时,您都应该对文件的使用进行类似的捕获,以便在进程终止时将其清除。
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.
如果您感到谨慎并且拥有 GNU Grep,则可以添加该-w
选项,以便命令行上提供的名称仅匹配整个单词。
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.
以上所有内容都适用于 Bourne/Korn/POSIX/Bash 系列中的几乎所有 shell(您需要使用带有严格 Bourne shell 的反引号代替$(...)
,并且Bourne 中的条件的前导括号case
也不允许贝壳)。但是,您可以使用数组来正确处理事情。
for i in $*; do
echo $i
done
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.
这会小心地保留参数中的间距,并为进程名称使用精确匹配。它避免了临时文件。显示的代码不验证零参数;这必须事先完成。或者你可以添加一行args[0]='/collywobbles/'
或类似的东西来提供一个默认的——不存在的——命令来搜索。
回答by DigitalRoss
To answer your question, what's going on is that $*
expands to a parameter list, and so the second and later words look like files to grep(1)
.
为了回答您的问题,发生的事情是$*
扩展到参数列表,因此第二个和后面的单词看起来像 files to grep(1)
.
To process them in sequence, you have to do something like:
要按顺序处理它们,您必须执行以下操作:
end '`rm foo`'
Usually, "$@"
(with the quotes) is used in place of $*
in cases like this.
通常,"$@"
(带引号)用于代替$*
这种情况。
See man sh
, and check out killall(1)
, pkill(1)
, and pgrep(1)
as well.
见man sh
,并检查了killall(1)
,pkill(1)
和pgrep(1)
也。
回答by Omnifarious
$*
should be rarely used. I would generally recommend "$@"
. Shell argument parsing is relatively complex and easy to get wrong. Usually the way you get it wrong is to end up having things evaluated that shouldn't be.
$*
应该很少使用。我一般会推荐"$@"
. Shell参数解析比较复杂,容易出错。通常你弄错的方式是最终对不应该被评估的事情进行评估。
For example, if you typed this:
例如,如果您键入以下内容:
#!/bin/sh
kill $(ps -A | fgrep -e "$(for arg in "$@"; do echo "$arg"; done)" | awk '{ print ; }')
you would discover that if you had a file named 'foo' you don't anymore.
你会发现如果你有一个名为“foo”的文件,你就没有了。
Here is a script that will do what you are asking to have done. It fails if any of the arguments contain '\n'
or '\0'
characters:
这是一个脚本,它将执行您要求完成的操作。如果任何参数包含'\n'
或'\0'
字符,则失败:
I vastly prefer $(...)
syntax for doing what backtick does. It's much clearer, and it's also less ambiguous when you nest things.
我非常喜欢$(...)
做反引号的语法。它更加清晰,并且在嵌套事物时也不会那么模糊。
回答by Nikolai Fetissov
Look into pkill(1)
instead, or killall(1)
as @khachik comments.
pkill(1)
改为查看,或killall(1)
作为@khachik 评论。