Unterschiedliches Verhalten im Shell-Skript gegenüber der Shell?

Unterschiedliches Verhalten im Shell-Skript gegenüber der Shell?

AKTUALISIEREN:

Ich habe den grep $1Teil im Skript in grep '$1'(während ich versuchte zu meinen ) geändert und dieses Mal bekam ich dasgrep "$1"

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

Nachricht (anstelle der Terminated: 15Nachricht). Ich verstehe nicht, was los ist.

FRAGE:

Ich habe ein einfaches Shell-Skript mit dem Namen geschrieben mykill.

mykill:

#!/bin/sh

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

Es gibt jedoch ein seltsames Verhalten. Wenn ich die Zeile schreibe:

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

Wenn bei der manuellen Ausführung in Bash nichts als Ausgabe kommt ps -A | grep process_name, erhalte ich Folgendes:

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

Wenn etwas passiert, wird der Befehl korrekt ausgeführt und ohne Meldung beendet.

Wenn ich das Skript jetzt durch Ausführen der Datei ausführe mykillund eine Ausgabe erscheint ps -A | grep process_name, wird das Skript korrekt ausgeführt und im Hintergrund beendet. Dies entspricht dem Verhalten, als würde der Befehl manuell ausgeführt.

Wenn aber nichts als Ausgabe kommt ps -A | grep process_name, bekomme ich nicht die Meldung über die Verwendung des Kill-Befehls. Stattdessen bekomme ich:

Terminated: 15

Ich habe auch die Rückgabecodes überprüft. Wenn ich versuche, einen nicht vorhandenen Prozess zu beenden, indem ich den Befehl manuell in die Shell schreibe, echo $?wird angezeigt 1. Wenn ich jedoch versuche, einen nicht vorhandenen Prozess durch Aufrufen des Skripts zu beenden, echo $?wird angezeigt 143.

Was ist hier los? Warum beobachte ich unterschiedliche Verhaltensweisen, wenn ich denselben Befehl manuell in die Shell schreibe oder wenn ich ihn in einem Shell-Skript ausführe?

HINWEIS: Beide shund meine Arbeitsshell sind bash.

BONUS: Könnte mein Shell-Skript effizienter und/oder eleganter geschrieben werden, indem nurPOSIX-Dienstprogramme? Wenn das so ist, wie?

Antwort1

Ein Hinweis zur Portabilität. Das Ausgabeformat für ps -Aistnicht spezifiziert durch POSIXfür nicht Unix-konforme Systeme (wie FreeBSD) (Sie werden feststellen, dass die Abschnitte zum Ausgabeformat und die Beschreibung der -fOption alle markiert sindXSIin der Spezifikation), sodass Sie es nicht wirklich zuverlässig und portabel nachbearbeiten können.

Beispielsweise werden mit dem psFormular procpsunter Linux Spalten ausgegeben PID TTY TIME CMD(wobei CMDder Prozessname und nicht die Befehlsargumente sind), während unter FreeBSD PID TT STAT TIME COMMAND(wobei COMMANDdie Argumente sind) ausgegeben wird.

Angesichts Ihrer Verwendung von grep -v grepnehme ich an, dass Sie Letzteres erwarten oder dass zumindest ps -Adie Argumente des Befehls ausgegeben werden, den der Prozess ausgeführt hat, und nicht nur der Prozessname (normalerweise abgeleitet vom Dateinamen des zuletzt ausgeführten Befehls oder dem ersten (0. ) Argument).

Wenn Sie nur auf die Befehlsargumente grepzurückgreifen möchten , sollten Sie Folgendes verwenden:grep

ps -A -o pid= -o args=

dessen Ausgabe durch POSIX angegeben wird.

Ihr Problem besteht nun darin, dass es mykillsich selbst zerstört, weil die mykill fooStreichhölzer foo.

Ein weiteres Problem ist, dass dadurch mykill grepnichts getötet würde.

Hier könnten Sie Folgendes tun:

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

(Beachten Sie, dass POSIX weder den Pfad des POSIX- shDienstprogramms noch den She-Bang-Mechanismus angibt und es sich daher /bin/shmöglicherweise nicht um eine POSIX-Shell handelt. In der Praxis wird She-Bang jedoch auf den meisten POSIX-Systemen unterstützt und /bin/shist entweder ein POSIX shoder die Bourne-Shell. shDer obige Code sollte auf beiden funktionieren.)

Das ist allerdings nicht ideal, da es immer einen wahren (0) Exit-Status zurückgibt, selbst wenn kein Prozess gefunden wird. Ein besserer Ansatz wäre:

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

In beiden Fällen beenden wir lediglich den ersten Abgleichvorgang, wie grep -m 1es Ihr Ansatz nahelegt.

Jetzt trap '' SIGTERMstellen wir sicher, dass unsere Prozesse nicht beendet werden, was in Ordnung wäre, wenn wir beenden würdenalledie passenden Prozesse, aber da wir hier nur den ersten passenden beenden, besteht das Problem darin, dass dieser erste sehr wohl derjenige sein kann, der ausgeführt wird mykill patternoder grep pattern.

Anstatt einige hinzuzufügen grep -ve grep -e mykill(was nicht narrensicher wäre, da dadurch mehr Prozesse ausgeschlossen werden könnten als beabsichtigt), könnten Sie versuchen, die Prozess-IDs der übereinstimmenden Prozesse zu vergleichen.

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

(Beachten Sie, dass $(...)und read -rPOSIX sind, aber nicht Bourne).

Oder verwenden Sie ksh93, oder bash( beides keine POSIX-Befehle), d. h. eine Shell mit integrierter Suche nach regulären Ausdrücken:zshyash

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

verwandte Informationen