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