głowa zjada dodatkowe postacie


15

Następujące polecenie powłoki miało wypisywać tylko nieparzyste linie strumienia wejściowego:

echo -e "aaa\nbbb\nccc\nddd\n" | (while true; do head -n 1; head -n 1 >/dev/null; done)

Ale zamiast po prostu drukuje pierwszą linię: aaa.

To samo nie dzieje się, gdy jest używane z opcją -c( --bytes):

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 >/dev/null; done)

To polecenie generuje 1234512345zgodnie z oczekiwaniami. Ale działa to tylko w implementacji narzędzia coreutilshead . BusyBox realizacja nadal spożywa dodatkowe znaki, więc wyjście jest po prostu 12345.

Wydaje mi się, że ten konkretny sposób implementacji jest wykonywany w celach optymalizacyjnych. Nie możesz wiedzieć, gdzie kończy się linia, więc nie wiesz, ile znaków musisz przeczytać. Jedynym sposobem, aby nie zużywać dodatkowych znaków ze strumienia wejściowego, jest czytanie strumienia bajt po bajcie. Ale czytanie ze strumienia jeden bajt na raz może być powolne. Więc chyba headczyta strumień wejściowy do wystarczająco dużego bufora, a następnie zlicza wiersze w tym buforze.

Tego samego nie można powiedzieć o przypadku, gdy --bytesużywana jest opcja. W takim przypadku wiesz, ile bajtów musisz odczytać. Możesz więc odczytać dokładnie tę liczbę bajtów i nie więcej. Corelibs implementacja wykorzystuje tę okazję, ale BusyBox jeden nie, to nadal czyta więcej niż bajt wymaganego do bufora. Prawdopodobnie zrobiono to w celu uproszczenia implementacji.

Więc pytanie. Czy to właściwe, że headnarzędzie zużywa więcej znaków ze strumienia wejściowego niż zostało to poproszone? Czy istnieje jakiś standard dla narzędzi uniksowych? A jeśli tak, to czy określa to zachowanie?

PS

Musisz nacisnąć, Ctrl+Caby zatrzymać powyższe polecenia. Narzędzia uniksowe nie zawodzą przy czytaniu dalej EOF. Jeśli nie chcesz naciskać, możesz użyć bardziej złożonego polecenia:

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 | [ `wc -c` -eq 0 ] && break >/dev/null; done)

których nie użyłem dla uproszczenia.


2
Neardupe unix.stackexchange.com/questions/48777/... i unix.stackexchange.com/questions/84011/… . Ponadto, jeśli ten tytuł był na movies.SX moja odpowiedź byłaby Zardoz :)
dave_thompson_085

Odpowiedzi:


30

Czy narzędzie head może zużywać więcej znaków ze strumienia wejściowego niż zostało to poproszone?

Tak, jest dozwolone (patrz poniżej).

Czy istnieje jakiś standard dla narzędzi uniksowych?

Tak, POSIX tom 3, Shell i programy narzędziowe .

A jeśli tak, to czy określa to zachowanie?

Na początku wprowadza:

Kiedy standardowe narzędzie odczytuje widoczny plik wejściowy i kończy się bezbłędnie, zanim osiągnie koniec pliku, narzędzie musi upewnić się, że przesunięcie pliku w otwartym opisie pliku jest właściwie ustawione tuż za ostatnim bajtem przetworzonym przez narzędzie. W przypadku plików, których nie można zobaczyć, stan przesunięcia pliku w otwartym opisie pliku dla tego pliku jest nieokreślony.

headjest jednym ze standardowych narzędzi , więc implementacja zgodna z POSIX musi implementować zachowanie opisane powyżej.

GNU ANTYLOPA head nie próbować opuścić deskryptor we właściwej pozycji, ale jest to niemożliwe, aby szukać na rurach, więc w teście nie udaje mu się przywrócić pozycję. Możesz to zobaczyć za pomocą strace:

$ echo -e "aaa\nbbb\nccc\nddd\n" | strace head -n 1
...
read(0, "aaa\nbbb\nccc\nddd\n\n", 8192) = 17
lseek(0, -13, SEEK_CUR)                 = -1 ESPIPE (Illegal seek)
...

Do readzwraca 17 bajtów (wszystkie dostępne wejścia), headprzetwarza cztery osoby, a następnie próbuje wrócić 13 bajtów, ale nie mogę. (Możesz również zobaczyć, że GNU headużywa bufora 8 KiB).

Kiedy każesz headliczyć bajty (co jest niestandardowe), wie, ile bajtów do odczytania, więc może (jeśli zaimplementowane w ten sposób) odpowiednio ograniczyć jego odczyt. Oto dlaczego twój head -c 5test działa: GNU headodczytuje tylko pięć bajtów i dlatego nie musi próbować przywracać pozycji deskryptora pliku.

Jeśli napiszesz dokument do pliku i użyjesz go zamiast tego, otrzymasz zachowanie, którego szukasz:

$ echo -e "aaa\nbbb\nccc\nddd\n" > file
$ < file (while true; do head -n 1; head -n 1 >/dev/null; done)
aaa
ccc

2
Zamiast tego można użyć narzędzi line(teraz usuniętych z POSIX / XPG, ale wciąż dostępnych w wielu systemach) lub read( IFS= read -r line), które odczytują jeden bajt na raz, aby uniknąć problemu.
Stéphane Chazelas,

3
Pamiętaj, że to, czy head -c 5odczytuje 5 bajtów, czy pełny bufor, zależy od implementacji (pamiętaj też, że head -cnie jest to standard), nie możesz na tym polegać. Musisz dd bs=1 count=5mieć gwarancję, że nie będzie można odczytać więcej niż 5 bajtów.
Stéphane Chazelas,

Dzięki @ Stéphane, zaktualizowałem -c 5opis.
Stephen Kitt,

Zauważ, że headwbudowana funkcja ksh93odczytuje jeden bajt na raz, head -n 1gdy dane wejściowe nie są widoczne.
Stéphane Chazelas,

1
@anton_rh, dddziała poprawnie tylko z potokami, bs=1jeśli użyjesz countjako, że odczyty potoków mogą zwrócić mniej niż zażądano (ale przynajmniej jeden bajt, chyba że zostanie osiągnięty eof). GNU ddma iflag=fullblock, że można złagodzić, że choć.
Stéphane Chazelas,

6

z POSIX

Program narzędziowy Head skopiuje pliki wejściowe na standardowe wyjście, kończąc wyjście dla każdego pliku w wyznaczonym punkcie.

Nie mówi nic o tym, ile head trzeba odczytać z wejścia. Wymaganie odczytywania bajt po bajcie byłoby głupie, ponieważ w większości przypadków byłoby bardzo wolne.

Jest to jednak rozwiązane we readwbudowanym / narzędziu: wszystkie powłoki, które mogę znaleźć readz potoków jeden bajt na raz, a standardowy tekst można interpretować w ten sposób, że należy to zrobić, aby móc odczytać tylko ten jeden wiersz:

Odczytu narzędzie odczytuje pojedynczą logiczną linię ze standardowego wejścia do jednego lub większej liczby zmiennych powłoki.

W przypadku read, który jest używany w skryptach powłoki, częstym przypadkiem użycia byłoby coś takiego:

read someline
if something ; then 
    someprogram ...
fi

Tutaj standardowe wejście someprogramjest takie samo jak powłoki, ale można się spodziewać, że someprogramprzeczyta wszystko, co nastąpi po pierwszym wierszu wejściowym zajętym przez, reada nie cokolwiek, co pozostało po buforowanym odczytaniu read. Z drugiej strony używanie headjak w twoim przykładzie jest znacznie rzadsze.


Jeśli naprawdę chcesz usunąć co drugą linię, lepiej (i szybciej) byłoby użyć jakiegoś narzędzia, które może obsłużyć całe wejście za jednym razem, np.

$ seq 1 10 | sed -ne '1~2p'   # GNU sed
$ seq 1 10 | sed -e 'n;d'     # works in GNU sed and the BSD sed on macOS

$ seq 1 10 | awk 'NR % 2' 
$ seq 1 10 | perl -ne 'print if $. % 2'

Ale zobacz sekcję „PLIKI WEJŚCIOWE” we wstępie POSIX do tomu 3 ...
Stephen Kitt

1
POSIX mówi: „Gdy standardowe narzędzie odczytuje widoczny plik wejściowy i kończy się bez błędu, zanim osiągnie koniec pliku, narzędzie musi upewnić się, że przesunięcie pliku w opisie otwartego pliku jest właściwie ustawione tuż za ostatnim bajtem przetworzonym przez narzędzie. W przypadku plików, których nie można zobaczyć, stan przesunięcia pliku w otwartym opisie pliku dla tego pliku jest nieokreślony.
AlexP

2
Zauważ, że jeśli nie użyjesz -r, readmoże odczytać więcej niż jedną linię (bez IFS=niej również usuwałaby spacje początkowe i końcowe oraz tabulatory (z wartością domyślną $IFS)).
Stéphane Chazelas,

@AlexP, tak, Stephen właśnie połączył tę część.
ilkkachu

Zauważ, że headwbudowana funkcja ksh93odczytuje jeden bajt na raz, head -n 1gdy dane wejściowe nie są widoczne.
Stéphane Chazelas,

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.