Jak sprawdzić, czy rura jest pusta i uruchomić polecenie na danych, jeśli nie jest?

42

Pipowałem linię w skrypcie bash i chcę sprawdzić, czy potok ma dane, przed przekazaniem ich do programu.

Wyszukiwanie, o którym znalazłem, test -t 0ale tutaj nie działa. Zawsze zwraca false. Jak więc upewnić się, że potok ma dane?

Przykład:

echo "string" | [ -t 0 ] && echo "empty" || echo "fill"

Wynik: fill

echo "string" | tail -n+2 | [ -t 0 ] && echo "empty" || echo "fill"

Wynik: fill

W przeciwieństwie do standardowego / kanonicznego sposobu testowania, czy powyższy potok wytworzył wydajność? dane wejściowe należy zachować, aby przekazać je do programu. Uogólnia to Jak połączyć dane wyjściowe z jednego procesu do drugiego, ale wykonać je tylko, jeśli pierwszy ma dane wyjściowe? który koncentruje się na wysyłaniu wiadomości e-mail.

zetah
źródło
Bardzo podobnie: uzależnij potok od niepustego zwrotu (w przypadku superużytkownika )
G-Man mówi „Przywróć Monikę”

Odpowiedzi:

37

Nie ma sposobu, aby zajrzeć do zawartości potoku za pomocą powszechnie dostępnych narzędzi powłoki, ani nie ma sposobu, aby odczytać znak do potoku, a następnie odłożyć go z powrotem. Jedynym sposobem, aby dowiedzieć się, że potok ma dane, jest odczytanie bajtu, a następnie musisz doprowadzić ten bajt do miejsca docelowego.

Więc zrób tak: przeczytaj jeden bajt; jeśli wykryjesz koniec pliku, zrób to, co chcesz, gdy wejście jest puste; jeśli czytasz bajt, rozwidlaj to, co chcesz zrobić, gdy dane wejściowe nie są puste, wstaw do niego ten bajt i potokuj pozostałe dane.

first_byte=$(dd bs=1 count=1 2>/dev/null | od -t o1 -A n | tr -dc 0-9)
if [ -z "$first_byte" ]; then
  # stuff to do if the input is empty
else
  {
    printf "\\$first_byte"
    cat
  } | {
    # stuff to do if the input is not empty
  }      
fi

ifneNarzędziowy z moreutils Joey Hessa wykonuje polecenie, jeśli jego wejście nie jest pusta. Zwykle nie jest instalowany domyślnie, ale powinien być dostępny lub łatwy do zbudowania na większości wariantów Uniksa. Jeśli dane wejściowe są puste, ifnenic nie robi i zwraca stan 0, czego nie można odróżnić od komendy uruchomionej pomyślnie. Jeśli chcesz coś zrobić, jeśli dane wejściowe są puste, musisz ustawić komendę, aby nie zwracała 0, co można zrobić, zwracając rozróżnienie statusu błędu:

ifne sh -c 'do_stuff_with_input && exit 255'
case $? in
  0) echo empty;;
  255) echo success;;
  *) echo failure;;
esac

test -t 0nie ma z tym nic wspólnego; sprawdza, czy standardowe wejście jest terminalem. Nie mówi w żaden sposób, czy dane wejściowe są dostępne.

Gilles „SO- przestań być zły”
źródło
Wierzę, że w systemach z potokami opartymi na STREAMS (Solaris HP / UX) można użyć ioctl I_PEEK, aby zerknąć na to, co jest na potoku, nie zużywając go.
Stéphane Chazelas
@ StéphaneChazelas niestety nie ma sposobu, aby przeglądać dane z potoku / fifo na * BSD, więc nie ma perspektywy na wdrożenie przenośnego peek narzędzia, które mogłoby zwrócić rzeczywiste dane z potoku, nie tylko jego ilość. (w 4.4 BSD, 386BSD itp. rury zostały zaimplementowane jako pary gniazd , ale zostało to wypatroszone w późniejszych wersjach * BSD - chociaż utrzymywały je dwukierunkowo).
mosvy
bash ma procedurę sprawdzania wejścia odsłoniętego przez a read -t 0(t w tym przypadku oznacza przekroczenie limitu czasu, jeśli się zastanawiasz).
Isaac
11

Prostym rozwiązaniem jest użycie ifnepolecenia (jeśli dane wejściowe nie są puste). W niektórych dystrybucjach nie jest instalowany domyślnie. Jest to część pakietu moreutilsw większości dystrybucji.

ifne uruchamia dane polecenie tylko wtedy, gdy standardowe wejście nie jest puste

Zauważ, że jeśli standardowe wejście nie jest puste, jest przekazywane ifnedo podanego polecenia

Nick Wirth
źródło
2
Od 2017 roku domyślnie nie ma go w Macu ani Ubuntu.
Sridhar Sarnobat
7

Stare pytanie, ale na wypadek, gdyby ktoś zetknął się z nim tak jak ja: Moim rozwiązaniem jest czytanie z przekroczeniem limitu czasu.

while read -t 5 line; do
    echo "$line"
done

Jeśli stdinjest pusty, nastąpi powrót po 5 sekundach. W przeciwnym razie odczyta wszystkie dane wejściowe i możesz je przetworzyć w razie potrzeby.

Ladd
źródło
Chociaż podoba mi się ten pomysł, -tniestety nie jest on częścią POSIX: pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html
JepZ
6

sprawdź, czy deskryptor pliku stdin (0) jest otwarty czy zamknięty:

[ ! -t 0 ] && echo "stdin has data" || echo "stdin is empty"
mviereck
źródło
Gdy przekazujesz jakieś dane i chcesz sprawdzić, czy są jakieś, i tak zdasz FD, więc nie jest to również dobry test.
Jakuje
1
[ -t 0 ]sprawdza, czy fd 0 jest otwarte dla tty , a nie to, czy jest zamknięte czy otwarte.
mosvy
@mosvy, czy mógłbyś wyjaśnić, jak wpłynęłoby to na użycie tego rozwiązania w skrypcie? Czy zdarzają się przypadki, gdy to nie działa?
JepZ
@JepZ huh? ./that_script </dev/null=> "standardowe wejście ma dane". Lub ./that_script <&-mieć naprawdę zamknięte standardowe wejście .
mosvy
5

Możesz także użyć test -s /dev/stdin(w jawnej podpowłoce).

# test if a pipe is empty or not
echo "string" | 
    (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

echo "string" | tail -n+2 | 
    (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')

: | (test -s /dev/stdin && echo 'pipe has data' && cat || echo 'pipe is empty')
trent55
źródło
8
Nie działa dla mnie Zawsze mówi, że rura jest pusta.
amfetamachina
2
Działa na moim Macu, ale nie na moim Linux-ie.
szczyt
3

W bash:

read -t 0 

Wykrywa, czy dane wejściowe zawierają dane (bez odczytywania czegokolwiek). Następnie możesz odczytać dane wejściowe (jeśli dane wejściowe są dostępne w momencie wykonania odczytu):

if     read -t 0
then   read -r input
       echo "got input: $input"
else   echo "No data to read"
fi

Uwaga: należy zrozumieć, że zależy to od czasu. Wykrywa, czy dane wejściowe zawierają już dane tylko w czasie wykonywania read -t.

Na przykład za pomocą

{ sleep 0.1; echo "abc"; } | read -t 0; echo "$?"

wyjście jest 1(błąd odczytu, tzn .: puste wejście). Echo zapisuje niektóre dane, ale nie jest bardzo szybkie rozpoczęcie i zapisanie pierwszego bajtu, dlatego read -t 0zgłosi, że jego dane wejściowe są puste, ponieważ program jeszcze niczego nie napisał.

Izaak
źródło
github.com/bminor/bash/blob/… - oto źródło tego, jak bash wykrywa, że ​​coś jest w deskryptorze pliku.
Pavel Patrin
Dzięki @PavelPatrin
Isaac
1
@PavelPatrin To nie działa . Jak wyraźnie widać z linku, bashzrobi select()albo jeden ioctl(FIONREAD), albo żaden z nich, ale nie oba, tak jak powinien, aby działało. read -t0jest zepsuta. Nie używaj go
mosvy
Oooh, dzisiaj próbuję zrozumieć, co jest z nią nie tak przez dwie godziny! Dziękuję, @mosvy!
Pavel Patrin
3

Jednym z łatwych sposobów sprawdzenia, czy w Unixie są dostępne dane do odczytu, jest FIONREADużycie ioctl.

Nie mogę wymyślić żadnego standardowego narzędzia, które to robi, więc oto robi to trywialny program (lepszy niż ifnez moreutils IMHO ;-)).

fionread [ prog args ... ]

Jeśli nie ma dostępnych danych na standardowym wyjściu, zostanie zakończone ze statusem 1. Jeśli są dostępne dane, uruchomi się prog. Jeśli nie progzostanie podany, wyjdzie ze statusem 0.

Możesz usunąć pollpołączenie, jeśli interesują Cię tylko dane natychmiast dostępne. Powinno to działać z większością rodzajów fds, nie tylko z rurami.

fionread.c

#include <unistd.h>
#include <poll.h>
#include <sys/ioctl.h>
#ifdef __sun
#include <sys/filio.h>
#endif
#include <err.h>

int main(int ac, char **av){
        int r; struct pollfd pd = { 0, POLLIN };
        if(poll(&pd, 1, -1) < 0) err(1, "poll");
        if(ioctl(0, FIONREAD, &r)) err(1, "ioctl(FIONREAD)");
        if(!r) return 1;
        if(++av, --ac < 1) return 0;
        execvp(*av, av);
        err(1, "execvp %s", *av);
}
mosvy
źródło
Czy ten program faktycznie działa? Nie musisz też czekać na POLLHUPwydarzenie, aby obsłużyć pustą skrzynkę? Czy to działa, jeśli na drugim końcu potoku jest wiele opisów plików?
Gilles „SO- przestań być zły”
Tak to działa. POLLHUP jest zwracany tylko przez ankietę, powinieneś użyć POLLIN, aby poczekać na POLLHUP. Nie ma znaczenia, ile otwartych uchwytów znajduje się na dowolnym końcu rury.
mosvy
Zobacz unix.stackexchange.com/search?q=FIONREAD+user%3A22565, aby dowiedzieć się, jak uruchomić FIONREAD z Perla (częściej dostępny niż kompilatory)
Stéphane Chazelas
Procedura używająca FIONREAD (lub HAVE_SELECT) jest zaimplementowana w bash tutaj .
Izaak,
2

Jeśli lubisz krótkie i tajemnicze jedno-linijki:

$ echo "string" | grep . && echo "fill" || echo "empty"
string
fill
$ echo "string" | tail -n+2 | grep . && echo "fill" || echo "empty"
empty

Użyłem przykładów z pierwotnego pytania. Jeśli nie chcesz korzystać -qz opcji przesyłania danych w potoku z grep

yashma
źródło
0

Wydaje się to być rozsądną implementacją ifne w bashu, jeśli nie masz nic przeciwko czytaniu całej pierwszej linii

ifne () {
        read line || return 1
        (echo "$line"; cat) | eval "$@"
}


echo hi | ifne xargs echo hi =
cat /dev/null | ifne xargs echo should not echo
Eric Woodruff
źródło
5
readzwróci również false, jeśli dane wejściowe nie są puste, ale nie zawierają znaku nowej linii, readwykonują pewne przetwarzanie na danych wejściowych i mogą odczytać więcej niż jedną linię, chyba że wywołasz ją jako IFS= read -r line. echonie można użyć do dowolnych danych.
Stéphane Chazelas
0

Działa to dla mnie przy użyciu read -rt 0

przykład z oryginalnego pytania, bez danych:

echo "string" | tail -n+2 | if read -rt 0 ; then echo has data ; else echo no data ; fi
użytkownik333755
źródło
nie, to nie działa. spróbuj z { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }(fałszywie ujemny) i true | { sleep .1; read -rt0 && echo YES; }(fałszywie pozytywny). W rzeczywistości, bash użytkownika readbędzie oszukać nawet przez FDS otwartych tylko do zapisu trybie: { read -rt0 && echo YES; cat; } 0>/tmp/foo. Jedyne, co wydaje się robić, to select(2)na tym fd.
mosvy
... i selectzwróci fd jako „gotowy”, jeśli read(2)nie zablokuje się na nim, bez względu na to, czy zwróci EOFlub błąd. Wniosek: read -t0jest uszkodzony w bash. Nie używaj tego.
mosvy
@mosvy Czy zgłosiłeś to z bashbug?
Izaak
@mosvy To { sleep .1; echo yes; } | { read -rt0 || echo NO; cat; }nie jest fałszywy negatyw, ponieważ (w czasie wykonywania odczytu) nie ma danych wejściowych. Później (uśpienie .1) wejście to jest dostępne (dla kota).
Izaak
@mosvy Dlaczego opcja r wpływa na wykrywanie ?: echo "" | { read -t0 && echo YES; }drukuje TAK, ale echo "" | { read -rt0 && echo YES; }nie.
Izaak