Jak zmusić potok do oczekiwania na koniec pliku lub zatrzymać po błędzie?

12

Próbowałem następującego polecenia po obejrzeniu tego filmu na shenanigans.

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

Zasadniczo drukuje listę stron podręcznych do dmenu, aby użytkownik mógł wybrać jedną z nich, a następnie używa xargs do uruchomienia man -Tpdf %(drukuj, aby ustawić pdf git strony podręcznika z wejścia xargs) i przekazać pdf do czytnika pdf (zathura ).

Problem polega na tym, że (jak widać na filmie) czytnik pdf uruchamia się jeszcze zanim wybiorę jedną stronę podręczną w dmenu. A jeśli kliknę Esc i nie wybiorę żadnego, czytnik pdf jest nadal otwarty i nie pokazuje żadnego dokumentu.

Jak sprawić, by czytnik pdf (i dowolne inne polecenie w łańcuchu potoków) działał tylko wtedy, gdy jego dane wejściowe osiągną koniec pliku lub gdy w ogóle otrzyma dane wejściowe? Lub, alternatywnie, w jaki sposób mogę zatrzymać łańcuch potoków, gdy jedno z połączonych poleceń zwróci niezerowy status wyjścia (tak, że jeśli dmenu zwróci błąd braku wyboru opcji, poniższe polecenia nie zostaną uruchomione)?

Seninha
źródło
1
Jakiej powłoki używasz? Czy to bash?
terdon
Próbowałem tego na bash, zsh i sh. Wszystkie miały takie samo zachowanie.
Seninha
2
Tak, zachowanie jest standardowe, zapytałem, która powłoka ze względu na pipefailopcję basha wymienioną w odpowiedzi Kusalandandy.
terdon

Odpowiedzi:

12

Jak sprawić, by czytnik pdf (i dowolne inne polecenie w łańcuchu potoków) działał tylko wtedy, gdy jego dane wejściowe osiągną koniec pliku lub gdy w ogóle otrzyma dane wejściowe?

Jest ifne(w Debianie jest w moreutilspakiecie):

ifne uruchamia następujące polecenie wtedy i tylko wtedy, gdy standardowe wejście nie jest puste.

W Twoim przypadku:

 | ifne zathura -
Kamil Maciorowski
źródło
Dzięki za odpowiedź, nie znałem tego polecenia! To polecenie (i pozostałe w moreutils) powinno być w oryginalnym Uniksie i określone przez posix ... To takie podstawowe i uniksowe narzędzie ...
Seninha 27.04.19
@Seninha Prostota ifnejest nieco zwodnicza. Unix nie ma operacji „podglądania potoku”, więc ifnemusi faktycznie odczytać co najmniej jeden bajt przed podjęciem decyzji o uruchomieniu polecenia zależnego. Oznacza to, że nie może po prostu wykonać testu i wykonać polecenia, ale musi utworzyć inną potok, rozwidlić inny proces, aby uruchomić zależne polecenie, i skopiować cały strumień z rury standardowej do rury dolnej. Jeśli przypadek „wprowadzania pustego” nie jest powszechny, ifnemoże z łatwością kosztować więcej zasobów niż średnio oszczędza.
@ Wumpus.Q.Wumbley to mit - nie musisz czytać żadnego bajtu, aby ustalić, czy w potoku są dane. Zobacz tutaj . I Linux można rzeczywiście peek dane z potoku (czyli odczyt danych bez konieczności wyjmowania go). Wspomniałem o tym i więcej w komentarzach do „kanonicznej” odpowiedzi tutaj, ale zostały one usunięte przez mody, ponieważ prawdopodobnie uważali, że te fakty były jak odwracanie uwagi od niesamowitości odpowiedzi.
mosvy
6

Pliki PDF powinny być widoczne; każda przeglądarka pdf będzie musiała najpierw spojrzeć na zwiastun, a następnie przejść do przesunięć z tabeli odnośników.

Ponieważ potoki nie są widoczne, zathuraużywa sztuczki zaciemniającej, polegającej na kopiowaniu wszystkich danych wejściowych do pliku tymczasowego, a następnie jak zwykle używaniu tego pliku tymczasowego. Tego rodzaju „sprytna” sztuczka budzi fałszywe nadzieje i prowadzi ludzi do założenia, że ​​pliki pdf można przesyłać strumieniowo.

Ale tak czy inaczej, zathuranaprawdę nie czekać na EOF przed wyświetleniem dokumentu, nie musisz robić nic za to do hapen:

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

Problem polega na tym, że zathuranie ma opcji, aby otworzyć okno tylko wtedy, gdy plik jest OK, i wyjść z błędem, jeśli tak nie jest - pozostanie tam tak, jakby wszystko było OK:

$ 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?

Więc nawet jeśli przekierowujesz dane wyjściowe do pliku tymczasowego i działasz tylko zathurawtedy, gdy wszystko jest w porządku, nie ma gwarancji, że użytkownikowi nie zostanie wyświetlone czarne okno, jeśli z jakiegoś zathurapowodu nie spodoba się wynikowi .


Btw,

man -X man

wyświetli stronę podręczną w oknie X11 z gxditview, nawet jeśli wygląda prosto z '70 ;-)

I oczywiście zawsze możesz użyć:

... | xargs xterm -e man

co, oprócz wielu innych ulepszeń, pozwoli ci używać wyrażeń regularnych podczas wyszukiwania i właściwego wyboru tekstu.

mosvy
źródło
6

Wszystkie polecenia w potoku zaczynają się prawie w tym samym czasie. Synchronizuje je tylko wejście / wyjście nad potokiem. Ponadto potok może przechowywać tylko tyle informacji, na ile pozwala bufor bufora.

Dlatego nie można uniknąć uruchomienia jednego etapu potoku, ponieważ

  1. polecenie na tym etapie jest uruchamiane, gdy wszystkie pozostałe etapy zostaną uruchomione, i
  2. jeśli polecenie nie wykorzystało danych wejściowych nad potokiem, zablokowałoby poprzednie etapy potoku.

Zamiast tego zapisz dane wyjściowe do pliku, pozwalając na zakończenie potoku. Następnie użyj tego pliku.

Przykład (jako funkcja przyjmująca jeden 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"
}

To dodatkowo nie uruchomiłoby zathuraprogramu, gdyby potok nie powiódł się ( xargsczęść zwróciła wartość niezerową) lub wygenerowany plik jest pusty.

W bashpowłoce można również ustawić pipefailopcję powłoki, set -o pipefailaby potok zwracał status wyjścia pierwszego polecenia w potoku, który się nie powiedzie. I chciałbyś zrobić tmpfilezmienną 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"
}

To ustawia pipefailopcję na czas trwania funkcji, jeśli nie została jeszcze ustawiona, a następnie resetuje ją w razie potrzeby. Pozbywa się -stestu z pliku wyjściowego.

Kusalananda
źródło
1
Dlaczego rm -f? Czy myślisz o przypadkach, w których potok zmienia uprawnienia do pliku tmp?
terdon
2
@terdon Myślę o przypadkach, w których plik tymczasowy jest przedwcześnie usuwany. rm -fnie wystąpił błąd, jeśli plik został już usunięty (prawdopodobnie przez zathura, nie wiem).
Kusalananda
Pierwsza funkcja nie działa zgodnie z oczekiwaniami: sprawi, że Zathura pokaże czarne okno, ale teraz Zathura działa po zakończeniu rurociągu, zamiast biegać wzdłuż rurociągu. Wynika to z faktu, że potok zwraca status wyjścia xargs, czyli 0. Polecenie, które nie działa w potoku, to dmenu (które zwraca 1, gdy nic nie wybiorę). Funkcja bash z pipefailopcją działa zgodnie z oczekiwaniami (a także w Zsh, który ma tę samą opcję).
Seninha
1
@Seninha Naprawiłem pierwszą funkcję, pozwalając jej sprawdzić, czy wygenerowany plik nie jest pusty.
Kusalananda