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.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
$c
zmiennej jak3 4 5 6
później1
i2
są przypisywane doa
ib
.echo
nie podzieli zmiennej dla ciebie, gdzieprintf
będzie z%d
s.Możesz jednak sprawić, że będą w zasadzie dawać te same odpowiedzi, manipulując echem liczb na początku polecenia:
W
/bin/bash
możesz użyć:W
/bin/sh
można po prostu użyć:Bash używa -e, aby włączyć znaki ucieczki \ tam, gdzie sh nie potrzebuje go, ponieważ jest już włączony.
\n
powoduje, ż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:Który z kolei wytwarza takie same dane wyjściowe za pomocą
printf
polecenia:W
sh
Mam nadzieję że to pomoże!
źródło
echo -e
nie jest tylko rozszerzeniem POSIX-aecho
, ale nie emituje-e
na 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 obaposix
ixpg_echo
flagi 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 -e
zgodnie z oczekiwaniami tutaj).echo
na pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html , wyraźnie opisując, żeecho
zachowanie to nie jest określone w obecności ani-n
odwrotnego ukośnika w dowolnym miejscu na wejściu, i zaleca sięprintf
zamiast tego być używane.