web-dev-qa-db-ja.com

シェルスクリプトとシェルの動作が異なりますか?

更新:

スクリプトでgrep $1の部分をgrep '$1'に変更しました(grep "$1"を意味しようとしていたとき)。今回は

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

メッセージ(Terminated: 15メッセージの代わりに)。何が起こっているのかわかりません。

質問:

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

リターンコードも確認しました。シェルにコマンドを手動で書き込んで存在しないプロセスを終了しようとすると、echo $?1を返します。ただし、スクリプトを呼び出して存在しないプロセスを終了しようとすると、echo $?143を返します。

何が起きてる?同じコマンドをシェルに手動で書き込んで実行する場合と、シェルスクリプト内で実行する場合で、異なる動作が見られるのはなぜですか?

注:shと私の作業シェルはどちらもbashです。

ボーナス:シェルスクリプトは、 POSIXユーティリティ のみを使用して、より効率的および/またはエレガントな方法で記述できますか?もしそうなら、どのように?

2
Utku

移植性に関する注意。 _ps -A_の出力形式は POSIXでは指定されていません 非Unix準拠システム(FreeBSDなど)の場合(出力形式のセクションと_-f_の説明に気付くでしょう。オプションはすべてタグ付けされています[〜#〜] xsi [〜#〜]仕様内)、したがって、移植可能に確実に後処理することはできません。

たとえば、Linuxのpsからのprocpsを使用すると、オンのときに_PID TTY TIME CMD_列(CMDはプロセス名であり、コマンド引数ではありません)が出力されます。 FreeBSDは_PID TT STAT TIME COMMAND_を出力します(COMMANDが引数です)。

_grep -v grep_の使用法を考えると、後者を期待していると思います。少なくとも、_ps -A_は、プロセス名(通常はファイルから派生)だけでなく、プロセスが実行したコマンドの引数を出力します。最後の実行コマンドまたは最初の実行コマンドの名前(0th)引数)。

grepがコマンド引数のみでgrepを対象としている場合は、次を使用する必要があります。

_ps -A -o pid= -o args=
_

その出力はPOSIXによって指定されます。

さて、あなたの問題は、_mykill foo_がmykillと一致するため、fooが自殺していることです。

もう1つの問題は、_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シェルではない可能性があることに注意してください。ただし、実際には、she-bangはでサポートされています。ほとんどのPOSIXシステムと_/bin/sh_はPOSIX shまたはBourneshのいずれかであり、上記のコードは両方で機能するはずです)。

プロセスが見つからない場合でも常に真(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 pattern_または_grep 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_、bashzshまたはyash(いずれもPOSIXコマンドではありません)を使用します。これは、正規表現マッチングが組み込まれたシェルです。

_#! /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
}
_
4