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

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

アップデート:

私はスクリプトで(私が意味しようとしていた)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ユーティリティもしそうなら、どうやって?

答え1

移植性に関する注意。出力形式ps -APOSIXでは未指定Unix非準拠システム(FreeBSDなど)の場合(出力フォーマットセクションとオプションの説明は-fすべてエクシィ仕様ではそうではないため、信頼性が高く移植可能な後処理を実際に行うことはできません。

たとえば、 Linux のpsfromでは、 columns (はプロセス名であり、コマンド引数ではありません)procpsが出力されますが、FreeBSD では(は引数) が出力されます。PID TTY TIME CMDCMDPID TT STAT TIME COMMANDCOMMAND

の使用状況から判断するとgrep -v grep、後者、または少なくともps -Aプロセス名 (通常は最後の実行コマンドのファイル名または最初の (0番目の) 引数から派生) だけではなく、プロセスが実行したコマンドの引数を出力する を期待していると思います。

コマンド引数のみを使用grepする場合は、次のようにします。grep

ps -A -o pid= -o args=

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

さて、あなたの問題は、マッチがmykillであるため、 が自らを殺してしまうことです。mykill foofoo

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

ただし、プロセスが見つからない場合でも常に 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 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
}

( および は POSIX ですが、Bourne ではないことに注意してください$(...)) read -r

またはksh93、、または(いずれも POSIX コマンドではありません) を使用すると、正規表現マッチングが組み込まれたシェルになりますbashzshyash

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

関連情報