
AKTUALISIEREN:
Ich habe den grep $1
Teil 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: 15
Nachricht). 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 mykill
und 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 sh
und 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 -A
istnicht spezifiziert durch POSIXfür nicht Unix-konforme Systeme (wie FreeBSD) (Sie werden feststellen, dass die Abschnitte zum Ausgabeformat und die Beschreibung der -f
Option alle markiert sindXSIin der Spezifikation), sodass Sie es nicht wirklich zuverlässig und portabel nachbearbeiten können.
Beispielsweise werden mit dem ps
Formular procps
unter Linux Spalten ausgegeben PID TTY TIME CMD
(wobei CMD
der Prozessname und nicht die Befehlsargumente sind), während unter FreeBSD PID TT STAT TIME COMMAND
(wobei COMMAND
die Argumente sind) ausgegeben wird.
Angesichts Ihrer Verwendung von grep -v grep
nehme ich an, dass Sie Letzteres erwarten oder dass zumindest ps -A
die 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 grep
zurü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 mykill
sich selbst zerstört, weil die mykill foo
Streichhölzer foo
.
Ein weiteres Problem ist, dass dadurch mykill grep
nichts 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- sh
Dienstprogramms noch den She-Bang-Mechanismus angibt und es sich daher /bin/sh
möglicherweise nicht um eine POSIX-Shell handelt. In der Praxis wird She-Bang jedoch auf den meisten POSIX-Systemen unterstützt und /bin/sh
ist entweder ein POSIX sh
oder die Bourne-Shell. sh
Der 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 1
es Ihr Ansatz nahelegt.
Jetzt trap '' SIGTERM
stellen 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 pattern
oder 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 -r
POSIX 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:zsh
yash
#! /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
}