Elegante Möglichkeit, eine Pipeline basierend auf dem Rückgabewert und nicht auf dem Exit-Code zu erstellen?

9

Wenn der Statuscode unbrauchbar ist, gibt es überhaupt eine Möglichkeit, eine Pipeline basierend auf der Ausgabe von stdout zu erstellen?

Ich würde es vorziehen, wenn die Antwort nicht den Anwendungsfall, sondern die Frage im Rahmen des Shell-Scripting anspricht. Ich versuche, das spezifischste im Repository verfügbare Paket zu finden, indem ich den Namen basierend auf den Länder- und Sprachcodes errate.

Nehmen Sie zum Beispiel dies,

  • $PACKAGE1=hunspell-en-zz
  • $PACKAGE2=hunspell-en

Die erste Vermutung ist angemessener, existiert aber möglicherweise nicht. In diesem Fall möchte ich hunspell-en( $PACKAGE2) zurückgeben, da die erste Option hunspell-en-zz( $PACKAGE1) nicht vorhanden ist.

Pipelines von apt-cache

Der Befehl apt-cachegibt Erfolg zurück (der von der Shell als Exit-Code Null definiert wird), wenn der Befehl ausgeführt werden kann (aus den Dokumenten von apt-cache).

apt-cache gibt bei normalem Betrieb Null zurück, bei Fehler dezimal 100.

Dies erschwert die Verwendung des Befehls in einer Pipeline. Normalerweise erwarte ich, dass das Paket-Suchäquivalent eines 404 zu einem Fehler führt (wie es bei curloder passieren würde wget). Ich möchte suchen, ob ein Paket vorhanden ist, und wenn nicht, auf ein anderes Paket zurückgreifen, wenn es vorhanden ist .

Dies gibt nichts zurück, da der erste Befehl Erfolg zurückgibt (so dass die rhs in ||nie ausgeführt werden)

apt-cache search hunspell-en-zz || apt-cache search hunspell-en

apt-cache search mit zwei Argumenten

Dies gibt nichts zurück, da apt-cacheUND seine Argumente,

apt-cache search hunspell-en-zz hunspell-en

Aus den Dokumenten von apt-cache

Separate Argumente können verwendet werden, um mehrere Suchmuster anzugeben, die zusammengefügt werden.

Da eines dieser Argumente eindeutig nicht existiert, gibt dies nichts zurück.

Die Frage

Wie lautet die Shell-Sprache, um Konventionen wie die zu behandeln, bei apt-cachedenen der Rückkehrcode für die Aufgabe unbrauchbar ist? Und Erfolg wird nur durch das Vorhandensein von Ausgabe auf STDOUT bestimmt?

Ähnlich zu

  • make find schlägt fehl, wenn nichts gefunden wurde

    beide haben das gleiche Problem. Die dort gewählte Antwort erwähnt, find -zwelche Lösung hier leider nicht anwendbar und anwendungsfallspezifisch ist. Es wird keine Redewendung erwähnt oder eine Pipeline ohne Verwendung der Nullterminierung erstellt (keine Option aktiviert apt-cache).

Evan Carroll
quelle
Bist du sicher, dass hunspell-enes das gibt? Wie auch immer, Sie können verwenden apt-cache policyund grep für ^$PACKAGENAME:.
AlexP
@AlexP Dies sind nur Beispiele. Hunspell-en existiert nicht, da sie mit Ländernamen verpacken, hunspell-arexistieren und es keine Ländernamenpakete gibt. Ich muss das genaueste Paket für ein bestimmtes Land und eine bestimmte Sprache finden.
Evan Carroll
2
findist genau wie apt-cachein dieser Hinsicht - nutzloser Rückkehrcode, Erfolg basiert auf Ausgabe.
Muru
1
Ja, ich stimme zu, dass beide auf dasselbe Problem zurückzuführen sind. In der gewählten Antwort werden dort Erwähnungen erwähnt, -zdie hier leider keine Lösung darstellen, sodass das anwendungsfallspezifische Problem nicht anwendbar ist. Und es gibt keine Erwähnung einer Redewendung oder des Aufbaus einer Pipeline ohne Verwendung der Nullterminierung (keine Option apt-cache)
Evan Carroll
1
@EvanCarroll die Nullbeendigung ist völlig optional. Ich habe es nur verwendet, weil es der sicherste Weg ist, mit Dateinamen umzugehen. Man würde also erwarten find, dass man es verwendet -print0und damit grep -z. Da apt-cache keine nullterminierte Ausgabe liefert, benötigen Sie keine -z.
Muru

Antworten:

5

Erstellen Sie eine Funktion, die einen Befehl akzeptiert und true zurückgibt, wenn eine Ausgabe vorliegt.

r() { local x=$("$@"); [ -n "$x" ] && echo "$x"; }

( ( r echo -n ) || echo 'nada' ) | cat      # Prints 'nada'
( ( r echo -n foo ) || echo 'nada' ) | cat  # Prints 'foo'

Für diesen Anwendungsfall wird es also so funktionieren:

r apt-cache search hunspell-en-zz || r apt-cache search hunspell-en
Roaima
quelle
Beachten Sie, dass r printf '\n\n\n'dies false zurückgeben würde. Mit Muscheln anders als zsh, r printf '\0\0\0'wäre auch falsch zurück. So wäre es r printf '\0a\0b\0c'mit einigen Muscheln.
Stéphane Chazelas
3

Soweit ich weiß, gibt es keine Standardmethode für Fälle, in denen der Erfolg eines Befehls durch das Vorhandensein einer Ausgabe bestimmt wird. Sie können jedoch Problemumgehungen schreiben.

Sie können beispielsweise die Ausgabe des Befehls in einer Variablen speichern und dann prüfen, ob diese Variable leer ist oder nicht:

output="$(command)"

if [[ -n "${output}" ]]; then
  # Code to execute if command succeded
else
  # Code to execute if command failed
fi

Ich denke, dies beantwortet die Frage allgemein, aber wenn wir über apt-cache searcheinige Lösungen sprechen , fallen mir diese ein.

Ich habe ein Skript, das die Paketverwaltung erleichtert. Einige seiner Funktionen sind folgende:

search() {
  local 'package' 'packages'
  packages="$( apt-cache search '.*' | cut -d ' ' -f '1' | sort )"
  for package; do
    grep -F -i -e "${package}" <<< "${packages}"
  done
}


search_all() {
  local 'package'
  for package; do
    apt-cache search "${package}" | sort
  done
}


search_description() {
  local 'package' 'packages'
  packages="$( apt-cache search '.*' | sort )"
  for package; do
    grep -F -i -e "${package}" <<< "${packages}"
  done
}


search_names_only() {
  local 'package'
  for package; do
    apt-cache search --names-only "${package}" | sort
  done
}

Mit diesen können Sie mehrere Suchvorgänge in einem einzigen Befehl durchführen. Zum Beispiel:

$ search hunspell-en-zz hunspell-en
hunspell-en-au
hunspell-en-ca
hunspell-en-gb
hunspell-en-med
hunspell-en-us
hunspell-en-za

Jede Funktion durchsucht die Datenbank auf unterschiedliche Weise, sodass die Ergebnisse je nach verwendeter Funktion variieren können:

$ search gnome | wc -l
538
$ search_all gnome | wc -l
1322
$ search_description gnome | wc -l
822
$ search_names_only gnome | wc -l
550
nxnev
quelle
2

Ich würde das nicht elegant nennen, aber ich denke, es könnte den Job machen:

search_packages () {
    local packages=($@)
    local results=()
    for package in "${packages[@]}"; do
        results=($(apt-cache -n search "$package"))
        if [[ "${#results[@]}" -eq 0 ]]; then
            echo "$package not found."
        elif [[ "${#results[@]}" -eq 1 ]]; then
            do stuff with "$package"
        else
            echo "Warning! Found multiple packages for ${package}:"
            printf '\t-> %s\n' "${results[@]}"
        fi
    done
}

Ich habe leider keine Debian-Maschine zum Testen. Ich habe die -nOption "Nur Namen" hinzugefügt, um apt-cachezu versuchen, die Suchergebnisse einzuschränken, da Sie anscheinend größtenteils sicher sind, wonach Sie suchen.

Kann ausgeführt werden wie:

$ search_packages hunspell-en-zz hunspell-en
$ my_packages=('hunspell-en-zz' 'hunspell-en')
$ search_packages "${my_packages[@]}"
jesse_b
quelle
1
Dies ist genau das, woran ich gedacht habe, aber ich habe nach etwas etwas eleganterem gesucht. Lassen Sie uns also sehen, ob jemand etwas anderes Kluges hat (wie eine abstraktere Lösung außerhalb des Anwendungsfalls), wenn nicht, werde ich markieren es wie gewählt.
Evan Carroll
1
Im Idealfall würde apt-cache nur etwas weniger Dummes zurückgeben.
Evan Carroll
1
@EvanCarroll, hast du versucht, mit der -qleisen Option herumzuspielen ? Die Manpage ist nicht sehr ausführlich, aber vielleicht ändert sie die Rückgabewerte?
Jesse_b
1
gibt immer noch 0 zurück. = (
Evan Carroll
2

Muru hat dies in den Kommentaren klargestellt und gibt den grepStatus 1 zurück, wenn keine Eingabe erfolgt. Sie können grep .dem Stream also etwas hinzufügen. Wenn keine Eingabe vorhanden ist, die dem Muster entspricht ., wird der Statuscode geändert:

( ( echo -n | grep . ) || echo 'nada' ) | cat      # prints 'nada'
( ( echo -n foo | grep . ) || echo 'nada' ) | cat  # prints 'foo'

Für den Anwendungsfall sieht das so aus. Im Folgenden gibt es keine, -pl-plso dass es zurückfällt und zurückkehrthunspell-pl

apt-cache search hunspell-pl-pl | grep . || apt-cache search hunspell-pl

Oder,

apt-cache search hunspell-en-US | grep . || apt-cache search hunspell-en

Es gibt ein -en-USso kehrt es zurück hunspell-en-us.

Siehe auch,

Evan Carroll
quelle
grep .Gibt true zurück, wenn die Eingabe mindestens eine (bei einigen Implementierungen vollständig begrenzte) Zeile enthält, die mindestens ein (bei den meisten Implementierungen gut geformtes) Zeichen enthält, und ansonsten die leeren Zeilen entfernt. grep '^'würde besser funktionieren, um zu überprüfen, ob es eine Ausgabe gibt, obwohl bei einigen Implementierungen immer noch false zurückgegeben werden könnte, wenn die Eingabe eine nicht begrenzte Zeile ist (und diese Zeile entfernen könnte, oder bei anderen Implementierungen true zurückgeben, aber die fehlende neue Zeile hinzufügen). Einige grep-Implementierungen drosseln auch den NUL-Charakter.
Stéphane Chazelas
2

Sie könnten Folgendes definieren:

has_output() {
  LC_ALL=C awk '1;END{exit!NR}'
}

Und dann:

if cmd | has_output; then
  echo cmd did produce some output
fi

Einige awkImplementierungen können NUL-Zeichen in der Eingabe verschlucken.

Im Gegensatz dazu grep '^'funktioniert das oben Gesagte garantiert für eine Eingabe, die nicht mit einem Zeilenumbruch endet, sondern die fehlende Zeilenumbruchzeile hinzufügt.

Um dies zu vermeiden und auf Systeme portierbar zu sein, auf denen awkDrosseln auf NUL vorhanden sind, können Sie perlstattdessen Folgendes verwenden :

has_output() {
  perl -pe '}{exit!$.'
}

Mit perlkönnen Sie auch eine Variante definieren, die beliebige Dateien eleganter behandelt:

has_output() {
  PERLIO=:unix perl -pe 'BEGIN{$/=\65536} END{exit!$.}'
}

Dies begrenzt die Speichernutzung (wie bei Dateien, die keine Zeilenumbrüche enthalten, wie z. B. große Dateien mit geringer Dichte).

Sie können auch Varianten erstellen wie:

has_at_least_one_non_empty_line() {
  LC_ALL=C awk '$0 != "" {n++};1; END{exit!n}'
}

oder:

has_at_least_one_non_blank_line() {
  awk 'NF {n++};1; END{exit!n}'
}

(Beachten Sie, dass die Definition von Leerzeichen zwischen den awkImplementierungen unterschiedlich ist. Einige beschränken sich auf Leerzeichen und Tabulatoren, andere enthalten vertikale ASCII-Zeichen wie CR oder FF, andere berücksichtigen die Leerzeichen des Gebietsschemas.)

Idealerweise möchten Sie unter Linux den splice()Systemaufruf verwenden, um die Leistung zu maximieren. Ich kenne keinen Befehl, der es verfügbar machen würde, aber Sie könnten immer pythonFolgendes verwenden ctypes:

has_output() {
  python -c 'if 1:
    from ctypes import *
    import sys
    l = CDLL("libc.so.6")
    ret = 1
    while l.splice(0,0,1,0,65536,0) > 0:
      ret = 0
    sys.exit(ret)'
}

(Beachten Sie, dass entweder has_outputstdin oder stdout (oder beides) eine Pipe sein muss, damit splice()es funktioniert.)

Stéphane Chazelas
quelle
0

Ich würde vorschlagen, sehr grundlegende integrierte Funktionen der Shell zu verwenden:

ck_command() { [ -n $("$@") ] ; }

Hier ist der einfachste Testfall:

ck_command echo 1 ; echo $?

ck_command echo ; echo $?

Dann könnten Sie es leicht mit dem ||Konstrukt verwenden, an das Sie gewöhnt sind:

ck_command command_1 || ck_command command_2

Diese einfache Funktion funktioniert mit Ihrem apt_cacheVerhalten so, wie Sie es möchten, unabhängig von der Anzahl der Argumente.

Dan
quelle
Außer dies verliert STDOUT im Prozess, ck_command echo 'asdf' | catgibt nichts aus.
Evan Carroll
2
→ EvanCarroll: Dies war nicht in Ihrem § "Die Frage". Um auch diese Ausgabeerhaltung zu erreichen, sehen Sie sich die sehr elegante und einfache Antwort von @roaima an: unix.stackexchange.com/a/413344/31707 .
Dan