shell 腳本與 shell 中的行為不同?

shell 腳本與 shell 中的行為不同?

更新:

我已將腳本中的部分更改grep $1grep '$1'(當我試圖表示時),這次我得到了grep "$1"

kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]

訊息(而不是Terminated: 15訊息)。我不明白發生了什麼事。

問題:

我寫了一個簡單的 shell 腳本,名為mykill.

mykill:

#!/bin/sh

kill `ps -A | grep $1 | grep -v 'grep' | grep -Eom 1 '^[0-9]+'`

然而,有一個奇怪的行為。當我寫下這一行時:

kill `ps -A | grep process_name | grep -v 'grep' | grep -Eom 1 '^[0-9]+'`

手動在 bash 上,如果沒有任何輸出ps -A | grep process_name,我會得到以下內容:

kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]

如果出現問題,該命令將正確執行並靜默終止。

現在,如果我透過執行檔來執行腳本mykill,如果 的輸出出現某些內容ps -A | grep process_name,則腳本會正確執行並靜默終止,這與手動執行命令的行為相同。

但是,如果 的輸出沒有出現任何內容ps -A | grep process_name,我就不會收到有關使用kill 命令的訊息。相反,我得到:

Terminated: 15

我還檢查了返回碼。當我嘗試透過在 shell 上手動編寫命令來終止不存在的進程後,echo $?給出1.但是,在我嘗試透過呼叫腳本終止不存在的進程後,echo $?給了143.

這裡發生了什麼事?為什麼我在透過手動在 shell 上編寫的方式執行相同的命令時與在 shell 腳本中執行它時觀察到不同的行為?

注意:sh我的工作 shell 都是bash.

獎勵:我的 shell 腳本能否以更有效和/或更優雅的方式編寫,僅使用POSIX 實用程式?如果是這樣,怎麼辦?

答案1

可移植性說明。的輸出格式ps -APOSIX 未指定對於不符合 Unix 的系統(如 FreeBSD)(您會注意到輸出格式部分和選項的描述-f都被標記為XSI在規範中),所以你不能真正可靠地對其進行後處理。

例如,對於Linux 上的psfrom procps,它將輸出PID TTY TIME CMDcolumns(其中CMD是進程名稱,而不是命令參數),而在 FreeBSD 上它會輸出PID TT STAT TIME COMMAND(其中COMMAND是參數)。

鑑於您的使用情況grep -v grep,我想您期望後者或至少ps -A輸出進程執行的命令的參數,而不僅僅是進程名稱(通常源自最後一個執行命令或第一個(第0)參數)。

如果您grep只想grep使用命令參數,則應該使用:

ps -A -o pid= -o args=

其輸出由 POSIX 指定。

現在,你的問題是它mykill正在自殺,因為mykill foo匹配foo.

另一個問題是它mykill grep不會殺死任何東西。

在這裡,你可以這樣做:

#! /bin/sh -
PATTERN=${1?} export PATTERN
trap '' TERM # ignore SIGTERM for the shell and its children
ps -A -o pid= -o args= | awk '$0 ~ ENVIRON["PATTERN"] {
  system("kill " $1); exit}'

(請注意,POSIX 不指定 POSIX 實用程式的路徑,sh也不指定 she-bang 機制,因此/bin/sh可能不是 POSIX shell。但實際上,大多數 POSIX 系統都支援 she-bang,並且/bin/sh是 POSIXsh或 Bournesh並且上面的程式碼應該適用於兩者)。

儘管這並不理想,因為即使沒有找到進程,它總是會傳回 true (0) 退出狀態。更好的方法是:

#! /bin/sh -
pattern=${1?}
trap '' TERM # ignore SIGTERM for the shell and its children
ps -A -o pid= -o args= | grep -e "$pattern" | {
  read pid args && kill "$pid"
}

grep -m 1在這兩種情況下,我們只會按照您的方法建議的方式終止第一個匹配進程。

現在,trap '' SIGTERM我們確保我們的進程沒有被殺死,如果我們要殺死它就可以了全部匹配的進程,但因為在這裡,我們只殺死第一個匹配的進程,問題是第一個很可能是正在運行的進程mykill patterngrep pattern

grep -ve grep -e mykill您可以嘗試比較匹配進程的進程 ID,而不是添加一些進程(這並不是萬無一失的,因為它可能會排除比預期更多的進程)。

#! /bin/sh -
pattern=${1?}
trap '' TERM # ignore SIGTERM for the shell and its children
             # just in case
psoutput=$(exec ps -A -o pid= -o ppid= -o args=)
printf '%s\n' "$psoutput" | grep -e "$pattern" | {
  while read -r pid ppid args; do
    if [ "$pid" -ne "$$" ] && [ "$ppid" -ne "$$" ]; then
      kill "$pid"
      exit # with the exit status of kill above
    fi
  done
  exit 1 # not found
}

(請注意,$(...)read -r是 POSIX,但不是 Bourne)。

或使用ksh93, bash, zshor yash(都不是 POSIX 指令),這是一個具有內建正規表示式匹配的 shell:

#! /bin/bash -
pattern=${1?}
trap '' TERM # ignore SIGTERM for the shell and its children
             # just in case
psoutput=$(exec ps -A -o pid= -o ppid= -o args=)
printf '%s\n' "$psoutput" | {
  while read -r pid ppid args; do
    if ((pid != $$ && ppid != $$)) && [[ $args =~ $pattern ]]; then
      kill "$pid"
      exit # with the exit status of kill above
    fi
  done
  exit 1 # not found
}

相關內容