To nie tylko echo kontra printf
Po pierwsze, zrozummy, co dzieje się z read a b c
częścią. read
wykona podział słów na podstawie domyślnej wartości IFS
zmiennej, 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 bash
podrę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.txt
do 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, read
próbowałbym dopasować każde słowo do line
zmiennej. Ale to kolejna historia, którą zachęcam do przestudiowania później, ponieważ while IFS= read -r variable
jest to bardzo często używana struktura.
echo a zachowanie printf
echo
robi to, czego można się tutaj spodziewać. Wyświetla twoje zmienne dokładnie tak, jak read
je ułożyłeś. Zostało to już wykazane w poprzedniej dyskusji.
printf
jest 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 $c
printf 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 $c
zmienna jest cytowana, jest teraz rozpoznawana jako jeden ciąg 3 4
i nie pasuje do %d
formatu, 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=execve
i 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, bash
ma swoją wbudowaną funkcję printf
, której używasz w swoim poleceniu, strace
tak naprawdę wywoła /usr/bin/printf
wersję, 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 printf
skł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 printf
vs 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ś printf
raczej 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).
strace
sprawą a drugą -strace printf
jest używanie/usr/bin/printf
, podczas gdyprintf
bezpoś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$()T
formatowania czasu.