Wie lässt man eine Pipeline auf das Dateiende warten oder nach einem Fehler anhalten?

12

Ich habe den folgenden Befehl ausprobiert, nachdem ich dieses Video über Pipe Shenanigans gesehen habe.

man -k . | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf | zathura -

Grundsätzlich wird eine Liste von Manpages in das Menü gedruckt, damit der Benutzer eine davon auswählen kann. Anschließend wird xargs zum Ausführen man -Tpdf %(Drucken, um ein PDF des Manpage-Git aus der Eingabe von xargs zu stdouten) verwendet und das PDF an einen PDF-Reader (zathura) übergeben ).

Das Problem ist, dass (wie Sie im Video sehen können) der PDF-Reader startet, noch bevor ich eine Manpage im Menü auswähle. Und wenn ich auf Esc klicke und keine auswähle, ist der PDF-Reader immer noch geöffnet und zeigt überhaupt kein Dokument an.

Wie kann ich den PDF-Reader (und jeden anderen Befehl in einer Pipe-Kette) so ausführen, dass er nur ausgeführt wird, wenn seine Eingabe das Dateiende erreicht oder wenn er überhaupt eine Eingabe empfängt? Oder wie kann ich alternativ eine Rohrkette zum Stoppen bringen, nachdem einer der verketteten Befehle einen Exit-Status ungleich Null zurückgegeben hat (sodass die folgenden Befehle nicht ausgeführt werden, wenn dmenu einen Fehler zurückgibt, wenn keine Option ausgewählt wurde)?

Seninha
quelle
1
Welche Shell benutzt du? Ist das Bash?
Terdon
Ich habe es auf bash, zsh und sh versucht. Alle hatten das gleiche Verhalten.
Seninha
2
Ja, das Verhalten ist Standard. Ich habe gefragt, welche Shell aufgrund der pipefailin Kusalandandas Antwort erwähnten Bash- Option verwendet wird.
Terdon

Antworten:

12

Wie kann ich den PDF-Reader (und jeden anderen Befehl in einer Pipe-Kette) so ausführen, dass er nur ausgeführt wird, wenn seine Eingabe das Dateiende erreicht oder wenn er überhaupt eine Eingabe empfängt?

Es gibt ifne(in Debian ist es im moreutilsPaket):

ifne führt den folgenden Befehl nur dann aus, wenn die Standardeingabe nicht leer ist.

In deinem Fall:

 | ifne zathura -
Kamil Maciorowski
quelle
Danke für die Antwort, ich kannte diesen Befehl nicht! Dieser Befehl (und die anderen in moreutils) sollten im ursprünglichen Unix enthalten sein und von posix angegeben werden ... Es ist ein so grundlegendes und Unix-
artiges
@ Seninha Die Einfachheit von ifnetäuscht ein bisschen. Unix hat keine "Pipe Peek" -Operation, ifnemuss also mindestens ein Byte lesen, bevor der abhängige Befehl ausgeführt wird. Das heißt, es kann nicht nur den Test ausführen und den Befehl ausführen , sondern muss eine andere Pipe erstellen, einen anderen Prozess verzweigen, um den abhängigen Befehl auszuführen, und den gesamten Stream von der stdin-Pipe in die Downstream-Pipe kopieren. Wenn der Fall "Eingabe leer" nicht häufig vorkommt, ifnekann er im Durchschnitt leicht mehr Ressourcen kosten, als er spart.
@ Wumpus.Q.Wumbley, das ist ein Mythos - Sie müssen kein Byte lesen, um festzustellen, ob sich Daten in einer Pipe befinden. Siehe hier . Und auf Linux können Sie tatsächlich peek Daten aus einem Rohr (dh die Daten lesen , ohne sie zu entfernen). Ich habe dies und mehr in Kommentaren zu einer "kanonischen" Antwort hier erwähnt, aber sie wurden durch Mods entfernt, weil sie wahrscheinlich das Gefühl hatten, dass diese Fakten die Attraktivität der Antwort beeinträchtigen würden.
Mosvy
6

PDF-Dateien sollen durchsuchbar sein; Jeder PDF-Betrachter muss zuerst auf den Trailer schauen und von dort zu den Offsets aus der XRef-Tabelle springen.

Da Pipes nicht durchsuchbar sind, zathurawird ein Verschleierungstrick verwendet, bei dem alle Eingaben in eine temporäre Datei kopiert werden und diese temporäre Datei dann wie gewohnt verwendet wird. Diese Art von "cleverem" Trick weckt falsche Hoffnungen und lässt vermuten, dass PDF-Dateien streambar sind.

Aber egal, zathurawirklich tut Warten auf die EOF bevor das Dokument angezeigt wird , müssen Sie nichts tun , dafür zu hapen:

(sleep 10; cat file.pdf) | zathura -
# will really show the content of file.pdf after 10 seconds

Das Problem ist, dass zathuraes keine Option gibt, das Fenster nur zu öffnen, wenn die Datei in Ordnung ist, und mit einem Fehler zu beenden, wenn dies nicht der Fall ist - es bleibt einfach dort, als ob alles in Ordnung ist:

$ dd if=file.pdf bs=50000 count=1 status=none | zathura -
error: could not open document  # its window still hanging around showing nothing

$ echo $?
0  # really?

Selbst wenn Sie die Ausgabe selbst in eine temporäre Datei umleiten und nur ausführen, zathurawenn alles in Ordnung war, gibt es keine Garantie dafür, dass dem Benutzer kein schwarzes Fenster angezeigt wird, wenn zathuraihm die Ausgabe aus dem einen oder anderen Grund nicht gefällt .


Übrigens,

man -X man

zeigt eine Manpage in einem X11-Fenster mit an gxditview, auch wenn es direkt aus der '70 aussieht ;-)

Und natürlich können Sie immer Folgendes verwenden:

... | xargs xterm -e man

Neben vielen anderen Verbesserungen können Sie bei der Suche und der richtigen Textauswahl reguläre Ausdrücke verwenden.

Mosvy
quelle
6

Alle Befehle in einer Pipeline werden fast gleichzeitig gestartet. Es ist nur die E / A über der Pipe, die sie synchronisiert. Außerdem kann eine Pipe nur so viele Informationen enthalten, wie der Puffer der Pipe zulässt.

Sie können daher nicht vermeiden, eine Stufe einer Pipeline auszuführen, weil

  1. Der Befehl in dieser Phase wird gestartet, sobald alle anderen Phasen trotzdem gestartet sind
  2. Wenn der Befehl die über die Pipe eingehende Eingabe nicht verbraucht, werden die vorherigen Stufen der Pipeline blockiert.

Schreiben Sie stattdessen die Ausgabe in eine Datei, während die Pipeline beendet wird. Verwenden Sie dann diese Datei.

Beispiel (als Funktion mit einem Argument):

myman () {
    tmpfile=$( mktemp )

    if man -k "$1" | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf >"$tmpfile" && [ -s  "$tmpfile" ]
    then
        zathura "$tmpfile"
    fi

    rm -f "$tmpfile"
}

Dies würde das zathuraProgramm außerdem nicht ausführen , wenn die Pipeline fehlschlägt (der xargsTeil ist ungleich Null zurückgegeben) oder die generierte Datei leer ist.

In der bashShell möchten Sie möglicherweise auch die pipefailShell-Option festlegen set -o pipefail, damit die Pipeline den Exit-Status des ersten fehlgeschlagenen Befehls in der Pipeline zurückgibt. Und Sie möchten die tmpfileVariable machen local:

myman () {
    local tmpfile=$( mktemp )

    if [ -o pipefail ]; then
        set -o pipefail
        trap 'set +o pipefail' RETURN
    fi

    if man -k "$1" | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf >"$tmpfile"
    then
        zathura "$tmpfile"
    fi

    rm -f "$tmpfile"
}

Dadurch wird die pipefailOption für die Dauer der Funktion festgelegt, sofern diese noch nicht festgelegt wurde, und bei Bedarf deaktiviert. Der -sTest in der Ausgabedatei wird entfernt.

Kusalananda
quelle
1
Warum rm -f? Denken Sie an Fälle, in denen die Pipe die Berechtigungen der tmpfile ändert?
Terdon
2
@terdon Ich denke an Fälle, in denen die temporäre Datei vorzeitig entfernt wird. rm -fwürde nicht ausfallen, wenn die Datei bereits entfernt wurde (möglicherweise von zathura, ich weiß nicht).
Kusalananda
Die erste Funktion funktioniert nicht wie erwartet: Außerdem wird Zathura ein schwarzes Fenster anzeigen, aber jetzt wird Zathura nach Abschluss der Pipeline ausgeführt, anstatt entlang der Pipeline. Dies liegt daran, dass die Pipeline den Exit-Status von xargs zurückgibt, der 0 ist. Der Befehl, der in der Pipeline fehlschlägt, ist dmenu (der 1 zurückgibt, wenn ich nichts auswähle). Die Bash-Funktion mit der pipefailOption funktioniert wie erwartet (und auch in zsh, das dieselbe Option hat).
Seninha
1
@Seninha Ich habe die erste Funktion behoben, indem ich überprüfen ließ, ob die generierte Datei nicht leer ist.
Kusalananda