Używając „podczas odczytu…”, echo i printf dają różne wyniki

14

Zgodnie z tym pytaniem „ Korzystanie” podczas czytania ... „w skrypcie linux

echo '1 2 3 4 5 6' | while read a b c;do echo "$a, $b, $c"; done

wynik:

1, 2, 3 4 5 6

ale kiedy wymienić echozprintf

echo '1 2 3 4 5 6' | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done

wynik

1, 2, 3
4, 5, 6

Czy ktoś mógłby mi powiedzieć, co odróżnia te dwa polecenia? Dzięki ~

użytkownik3094631
źródło

Odpowiedzi:

24

To nie tylko echo kontra printf

Po pierwsze, zrozummy, co dzieje się z read a b cczęścią. readwykona podział słów na podstawie domyślnej wartości IFSzmiennej, która jest spacją-tab-nowa linia, i dopasuje wszystko na tej podstawie. Jeśli jest więcej danych wejściowych niż zmienne do ich przechowywania, dopasuje podzielone części do pierwszych zmiennych, a to, czego nie można dopasować - przejdzie do ostatniego. Oto co mam na myśli:

bash-4.3$ read a b c <<< "one two three four"
bash-4.3$ echo $a
one
bash-4.3$ echo $b
two
bash-4.3$ echo $c
three four

Dokładnie tak jest opisane w bashpodręczniku (patrz cytat na końcu odpowiedzi).

W twoim przypadku dzieje się tak, że 1 i 2 pasują do zmiennych aib, a c bierze wszystko inne, to jest 3 4 5 6.

Wiele razy zobaczysz także to, że ludzie używają while IFS= read -r line; do ... ; done < input.txtdo czytania plików tekstowych wiersz po wierszu. Ponownie, IFS=jest tutaj powód do kontrolowania podziału słów, a ściślej - wyłącz go i przeczytaj jeden wiersz tekstu w zmiennej. Gdyby go nie było, readpróbowałbym dopasować każde słowo do linezmiennej. Ale to kolejna historia, którą zachęcam do przestudiowania później, ponieważ while IFS= read -r variablejest to bardzo często używana struktura.

echo a zachowanie printf

echorobi to, czego można się tutaj spodziewać. Wyświetla twoje zmienne dokładnie tak, jak readje ułożyłeś. Zostało to już wykazane w poprzedniej dyskusji.

printfjest bardzo wyjątkowy, ponieważ będzie dopasowywał zmienne do łańcucha formatu, dopóki wszystkie nie zostaną wyczerpane. Więc kiedy robisz printf "%d, %d, %d \n" $a $b $cprintf widzi ciąg formatu z 3 miejscami po przecinku, ale jest więcej argumentów niż 3 (ponieważ twoje zmienne faktycznie rozwijają się do pojedynczych 1,2,3,4,5,6). To może wydawać się mylące, ale istnieje z jakiegoś powodu jako ulepszone zachowanie w porównaniu z tym, co robi prawdziwa printf() funkcja w języku C.

To, co tutaj zrobiłeś, ma wpływ na wynik, że twoje zmienne nie są cytowane, co pozwala powłoce (nie printf) podzielić zmienne na 6 oddzielnych pozycji. Porównaj to z cytowaniem:

bash-4.3$ read a b c <<< "1 2 3 4"
bash-4.3$ printf "%d %d %d\n" "$a" "$b" "$c"
bash: printf: 3 4: invalid number
1 2 3

Dokładnie dlatego, że $czmienna jest cytowana, jest teraz rozpoznawana jako jeden ciąg 3 4i nie pasuje do %dformatu, który jest tylko jedną liczbą całkowitą

Teraz zrób to samo bez cytowania:

bash-4.3$ printf "%d %d %d\n" $a $b $c
1 2 3
4 0 0

printf ponownie mówi: „OK, masz tam 6 elementów, ale format pokazuje tylko 3, więc dopasowuję elementy i pozostawiam puste wszystko, czego nie mogę dopasować do faktycznego wkładu użytkownika”.

I we wszystkich tych przypadkach nie musisz mi wierzyć na słowo. Po prostu uruchom strace -e trace=execvei przekonaj się, co tak naprawdę polecenie „widzi”:

bash-4.3$ strace -e trace=execve printf "%d %d %d\n" $a $b $c
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3", "4"], [/* 80 vars */]) = 0
1 2 3
4 0 0
+++ exited with 0 +++

bash-4.3$ strace -e trace=execve printf "%d %d %d\n" "$a" "$b" "$c"
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3 4"], [/* 80 vars */]) = 0
1 2 printf: 3 4’: value not completely converted
3
+++ exited with 1 +++

Dodatkowe uwagi

Jak słusznie zauważył Charles Duffy w komentarzach, bashma swoją wbudowaną funkcję printf, której używasz w swoim poleceniu, stracetak naprawdę wywoła /usr/bin/printfwersję, a nie wersję powłoki. Oprócz drobnych różnic, dla naszego zainteresowania tym konkretnym pytaniem specyfikatory formatu standardowego są takie same, a zachowanie jest takie samo.

Należy również pamiętać, że printfskładnia jest znacznie bardziej przenośna (i dlatego preferowana) niż echo, nie wspominając, że składnia jest bardziej znana w języku C lub dowolnym języku podobnym do języka C, który ma printf()w nim funkcję. Zobacz tę doskonałą odpowiedź przez terdon na temat printfvs echo. Chociaż możesz dostosować dane wyjściowe do konkretnej powłoki w konkretnej wersji Ubuntu, jeśli zamierzasz przenosić skrypty między różnymi systemami, prawdopodobnie powinieneś printfraczej wybrać echo. Może jesteś początkującym administratorem systemu pracującym z Ubuntu i CentOS, a może nawet FreeBSD - kto wie - więc w takich przypadkach będziesz musiał dokonać wyboru.

Cytat z podręcznika bash, sekcja KOMENDY BUDOWLANE

czytaj [-ers] [-a aname] [-d delim] [-i tekst] [-n nchars] [-N nchars] [-p monit] [-t timeout] [-u fd] [name ... ]

Jeden wiersz jest odczytywany ze standardowego wejścia lub z deskryptora pliku fd podanego jako argument opcji -u, a pierwsze słowo jest przypisywane do imienia, drugie słowo do drugiej nazwy i tak dalej, z pozostawionymi resztkami słowa i ich separatory przypisane do nazwiska. Jeśli ze strumienia wejściowego odczytanych jest mniej słów niż nazw, do pozostałych nazw przypisywane są puste wartości. Znaki w IFS służą do podziału wiersza na słowa przy użyciu tych samych reguł, których używa powłoka do rozwijania (opisanych powyżej w rozdziale Podział słów).

Sergiy Kolodyazhnyy
źródło
2
Jedną z godnych uwagi różnic między stracesprawą a drugą - strace printfjest używanie /usr/bin/printf, podczas gdy printfbezpośrednio w bash używa wbudowanej powłoki o tej samej nazwie. Nie zawsze będą identyczne - na przykład instancja bash ma specyfikatory formatu %q, aw nowych wersjach $()Tformatowania czasu.
Charles Duffy
7

To tylko sugestia i wcale nie ma na celu zastąpienia odpowiedzi Sergiya. Myślę, że Sergiy napisał świetną odpowiedź, dlaczego różnią się drukowaniem. W jaki sposób zmienna w odczycie jest przypisywana z pozostałymi do $czmiennej jak 3 4 5 6później 1i 2są przypisywane do ai b. echonie podzieli zmiennej dla ciebie, gdzie printfbędzie z %ds.

Możesz jednak sprawić, że będą w zasadzie dawać te same odpowiedzi, manipulując echem liczb na początku polecenia:

W /bin/bashmożesz użyć:

echo -e "1 2 3 \n4 5 6"

W /bin/shmożna po prostu użyć:

echo "1 2 3 \n4 5 6"

Bash używa -e, aby włączyć znaki ucieczki \ tam, gdzie sh nie potrzebuje go, ponieważ jest już włączony. \npowoduje, że tworzy nową linię, więc teraz linia echa jest podzielona na dwie osobne linie, których można teraz użyć dwa razy dla instrukcji pętli echa:

:~$ echo -e "1 2 3 \n4 5 6" | while read a b c; do echo "$a, $b, $c"; done
1, 2, 3
4, 5, 6

Który z kolei wytwarza takie same dane wyjściowe za pomocą printfpolecenia:

:~$ echo -e "1 2 3 \n4 5 6" | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done
1, 2, 3 
4, 5, 6 

W sh

$ echo "1 2 3 \n4 5 6" | while read a b c; do echo "$a, $b, $c"; done
1, 2, 3
4, 5, 6
$ echo "1 2 3 \n4 5 6" | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done
1, 2, 3 
4, 5, 6 

Mam nadzieję że to pomoże!

Terrance
źródło
echo -enie jest tylko rozszerzeniem POSIX-a echo, ale nie emituje -ena wyjściu danych, biorąc pod uwagę, że dane wejściowe są w rzeczywistości niezgodne ze specyfikacją czarnej litery. (bash jest domyślnie niezgodny, ale jest zgodny ze standardem, gdy oba posixi xpg_echoflagi są ustawione; te ostatnie można ustawić jako domyślne w czasie kompilacji, co oznacza, że ​​nie wszystkie wersje bash będą działać echo -ezgodnie z oczekiwaniami tutaj).
Charles Duffy
Zobacz sekcję UŻYWANIE APLIKACJI i RATIONALE specyfikacji POSIX echona pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html , wyraźnie opisując, że echozachowanie to nie jest określone w obecności ani -nodwrotnego ukośnika w dowolnym miejscu na wejściu, i zaleca się printfzamiast tego być używane.
Charles Duffy
@CharlesDuffy Czy kiedykolwiek napisałem w moim, że spodziewam się, że zadziała we wszystkich wersjach bash? A jaki masz problem z moją odpowiedzią? Jeśli uważasz, że masz lepsze rozwiązanie, napisz je jako odpowiedź, zamiast próbować udowodnić, że ludzie się mylą. Przebywałem tę drogę zbyt wiele razy z ludźmi takimi jak ty, więc przestańcie z tymi komentarzami. To wszystko, co mam do powiedzenia.
Terrance
3
@CharlesDuffy w Ask Ubuntu czasami piszemy odpowiedzi, których nie można przenieść na inne dystrybucje Linuksa. Ponieważ twój przedstawiciel ma tutaj tylko 103 punkty, być może tego nie zauważyłeś. Twoje repozytorium w przypadku przepełnienia stosu wynosi 123,3 tys. Punktów, więc oczywiście znasz mnóstwo rzeczy na temat systemów * NIX w ogóle. Myślę, że Ty, Terrace i Serg mają się dobrze, ale patrzą na nitkę z różnych punktów widzenia. Wyobrażam sobie (bez zamierzonej gry słów) sentyment do napisania odpowiedzi, która jest * przenośna w systemie NIX lub przynajmniej przenośna w różnych dystrybucjach systemu Linux.
WinEunuuchs2Unix
2
@CharlesDuffy Chociaż zgadzam się z twoimi opiniami, odpowiedzi powinny być przenośne, jest to jednak strona zorientowana na Ubuntu, dlatego użytkownicy powinni założyć narzędzia specyficzne dla Ubuntu. Ostrzeżenie jest więc nieco implikowane. Nie wdawajmy się w zbyt długie dyskusje. Masz rację, że należy wziąć pod uwagę inne powłoki, a także wziąć pod uwagę początkujących czytających, aby uczyć się na podstawie odpowiedzi. Krótki komentarz byłby prawdopodobnie wystarczający, aby o tym powiedzieć.
Sergiy Kolodyazhnyy