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).
stracesprawą a drugą -strace printfjest używanie/usr/bin/printf, podczas gdyprintfbezpoś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.