Verhindern Sie, dass grep einen Fehler zurückgibt, wenn die Eingabe nicht übereinstimmt

73

Ich möchte in ein Bash-Skript einen Code schreiben, der prüft, ob ein Programm bereits ausgeführt wird. Ich habe folgendes, um zu suchen, ob die Leiste läuft

 foo=`ps -ef | grep bar | grep -v grep`

Das

 grep -v grep

Teil ist sicherzustellen, dass die "Grep-Leiste" in ps-Ergebnissen nicht berücksichtigt wird

Wenn die Leiste nicht läuft, ist foo korrekt leer. Aber mein Problem liegt in der Tatsache, dass das Skript hat

 set -e

Dies ist ein Flag zum Beenden des Skripts, wenn ein Befehl einen Fehler zurückgibt. Es stellt sich heraus, dass "grep -v grep" mit nichts übereinstimmt und grep einen Fehler zurückgibt, wenn der Balken nicht ausgeführt wird. Ich habe versucht, -q oder -s zu verwenden, aber ohne Erfolg.

Gibt es eine Lösung dafür? Danke

George Kastrinis
quelle
Beachten Sie, dass dies set -enicht bash-spezifisch ist, sondern für jede POSIX-kompatible Shell ( shusw.) gilt
myrdd

Antworten:

72

Sicher:

ps -ef | grep bar | { grep -v grep || true; }

Oder auch:

ps -ef | grep bar | grep -v grep | cat
Sean
quelle
8
Das erste ist insofern besser, als es auch mit "-o Pipefail" funktioniert, einer weiteren guten Fail-Fast-Einstellung für Bash ähnlich wie "-e".
Jaka Jančar
5
Dies funktioniert gut, wenn grep1 zurückgegeben wird, weil keine Übereinstimmungen vorliegen. Was passiert jedoch, wenn 2 zurückgegeben wird (z. B. Fehler)?
André Fratelli
3
Wie André Fratelli sagte, grep $options || trueignoriert die Verwendung echte Fehler! Ich habe eine neue Antwort vorgeschlagen: stackoverflow.com/a/49627999/307637
myrdd
Beobachtung: Der Ansatz mit " ... | grep -v grep | cat" verhindert NICHT, dass ein Skript fehlschlägt, wenn es vorhanden ist set -o pipefail, obwohl dies möglicherweise ausreicht set -e. Der " ... | { grep -v grep || true; }" -Ansatz funktioniert set -o pipefail, aber eine sauberere Lösung, die speziell auf fehlende Grep-Übereinstimmungen prüft, finden Sie in der Antwort von @ myrdd.
Trutane
19

Kurze Antwort

Schreiben

ps -ef | grep bar | { grep -v grep || test $? = 1; }

wenn Sie verwenden set -e.

Wenn Sie die pipefailOption ( set -o pipefail) von bash verwenden , denken Sie daran, die Ausnahmebehandlung ( ||test) auf alle Elemente grepin der Pipeline anzuwenden :

ps -ef | { grep bar || test $? = 1; } | { grep -v grep || test $? = 1; }

In Shell-Skripten empfehle ich Ihnen, die Dienstprogrammfunktion „catch-1-grep“ (c1grep) zu verwenden:

c1grep() { grep "$@" || test $? = 1; }

Erklärt

grepDer Exit-Status ist entweder 0, 1 oder 2: [1]

  • 0 bedeutet, dass eine Linie ausgewählt ist
  • 1 bedeutet, dass keine Linien ausgewählt wurden
  • 2 bedeutet, dass ein Fehler aufgetreten ist

grepkann auch andere Codes zurückgeben, wenn diese durch ein Signal unterbrochen werden (z 130. B. für SIGINT).

Da wir nur Exit - Status ignorieren wollen 1, verwenden wir , testdass bestimmte Exit - Status zu unterdrücken.

  • Wenn grepzurückgegeben wird 0, testwird nicht ausgeführt.
  • Wenn grepzurückgegeben wird 1, testwird ausgeführt und zurückgegeben 0.
  • Wenn grepein anderer Wert zurückgegeben wird, testwird ausgeführt und zurückgegeben 1.

Im letzten Fall wird das Skript aufgrund von set -eoder sofort beendet set -o pipefail. Wenn Sie sich jedoch überhaupt nicht um grepFehler kümmern , können Sie natürlich schreiben

ps -ef | grep bar | { grep -v grep || true; }

wie von Sean vorgeschlagen .


[zusätzliche] Verwendung in Shell-Skripten

Wenn Sie in Shell-Skripten grepviel verwenden, empfehle ich Ihnen, eine Dienstprogrammfunktion zu definieren:

# "catch exit status 1" grep wrapper
c1grep() { grep "$@" || test $? = 1; }

Auf diese Weise wird Ihre Pfeife wieder kurz und einfach, ohne die Funktionen von set -eund zu verlieren set -o pipefail:

ps -ef | c1grep bar | c1grep -v grep

Zu Ihrer Information:

  • Ich habe es genannt, um c1grepzu betonen, dass es einfach den Exit-Status erreicht 1, sonst nichts.
  • Ich hätte grepstattdessen die Funktion aufrufen können ( grep() { env grep "$@" ...; }), aber ich bevorzuge einen weniger verwirrenden und expliziteren Namen c1grep.

[zusätzlich] ps+grep

Wenn Sie also wissen möchten, wie Sie grep -v grepden | grepTeil vermeiden oder gar vermeiden ps|grepkönnen, werfen Sie einen Blick auf einige der anderen Antworten. aber das ist imho etwas off-topic.


[1] grepManpage

Myrdd
quelle
15

Ein guter Trick, den Sie vermeiden sollten, grep -v grepist folgender:

ps -ef | grep '[b]ar'

Dieser reguläre Ausdruck stimmt nur mit der Zeichenfolge "bar" überein. In der psAusgabe wird die Zeichenfolge "bar" jedoch beim grep-Prozess nicht angezeigt.


In den Tagen, bevor ich davon erfuhr pgrep, habe ich diese Funktion geschrieben, um den obigen Befehl zu automatisieren:

psg () { 
    local -a patterns=()
    (( $# == 0 )) && set -- $USER
    for arg do
        patterns+=("-e" "[${arg:0:1}]${arg:1}")
    done
    ps -ef | grep "${patterns[@]}"
}

Dann,

psg foo bar

verwandelt sich in

ps -ef | grep -e '[f]oo' -e '[b]ar'
Glenn Jackman
quelle
Heh, ich habe genau das getan. Es funktioniert , weil der Ausdruck tut Match Bar , aber es nicht passen b.ar
DigitalRoss
5
@ grok12, was in der ps-Ausgabe angezeigt wird, ist grep [b]arund der reguläre Ausdruck [b]ar kann nicht mit der Zeichenfolge übereinstimmen [b]ar- der reguläre Ausdruck entspricht genau 3 Zeichen, während die Zeichenfolge 5 Zeichen enthält
Glenn Jackman
@Glenn: Wieder brillant.
Grok12
10

Warum sollten Sie psnach massiven Produktionsmengen fragen , -efwenn Sie nur 99% davon wegwerfen wollen? psund vor allem die GNU-Version ist ein Schweizer Taschenmesser mit praktischer Funktionalität. Versuche dies:

ps -C bar -o pid= 1>/dev/null

Ich spezifiziere -o pid=hier nur weil, aber in der Tat ist es sinnlos, da wir sowieso alles von stdout wegwerfen. Es wäre jedoch nützlich, wenn Sie die tatsächlich laufende PID wissen möchten.

pskehrt automatisch mit einem Status ungleich Null zurück, wenn -Cnichts übereinstimmt, und mit Null, wenn es übereinstimmt. Man könnte das einfach sagen

ps -C bar 1>/dev/null && echo bar running || echo bar not running

Oder

if ps -C bar 1>/dev/null ; then
    echo bar running
else
    echo bar not running
fi

Ist das nicht einfacher? Keine Notwendigkeit für grep, nicht zweimal oder sogar einmal.

sorpigal
quelle
Sie beantworten hier eine etwas andere Frage. Das wichtigste ist, grepdass mit jeder Eingabe verwendet werden kann, nicht nur von ps.
Victor Yarema
1
foo=`ps -ef | grep bar | grep -v grep` || true
DigitalRoss
quelle
x || wahr immer wahr?
user48956
1

Versuchen Sie es so zu machen:

ps auxw | grep -v grep | Katze

cat gibt immer 0 zurück und ignoriert den Exit-Code von grep

Vladyslav Savchenko
quelle
1
Es ist keine gute Idee, alle Exit-Codes zu ignorieren, da außer not foundeinem anderen Fehler auftreten kann. Diese Lösung schlägt auch fehl, wenn Modi durch Befehle set -eoder set -o pipefailBefehle verursacht werden.
Victor Yarema