Non è solo echo vs printf
Innanzitutto, capiamo cosa succede con la read a b c
parte. read
eseguirà la suddivisione delle parole in base al valore predefinito della IFS
variabile che è spazio-tab-newline e si adatterà a tutto in base a quello. Se c'è più input rispetto alle variabili per tenerlo, si adatterà le parti divise nelle prime variabili e ciò che non può essere adattato - entrerà per ultimo. Ecco cosa intendo:
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
Questo è esattamente come è descritto nel bash
manuale (vedi la citazione alla fine della risposta).
Nel tuo caso, ciò che accade è che 1 e 2 si adattano alle variabili aeb, e c prende tutto il resto, che è 3 4 5 6
.
Quello che vedrai anche molte volte è che le persone usano while IFS= read -r line; do ... ; done < input.txt
leggere file di testo riga per riga. Ancora una volta, IFS=
è qui per un motivo per controllare la suddivisione delle parole, o più specificamente - disabilitarlo e leggere una singola riga di testo in una variabile. Se non ci read
fosse, cercherebbe di adattare ogni singola parola alla line
variabile. Ma questa è un'altra storia, che ti incoraggio a studiare in seguito, poiché while IFS= read -r variable
è una struttura molto usata.
comportamento echo vs printf
echo
fa quello che ti aspetteresti qui. Visualizza le variabili esattamente come le read
ha disposte. Ciò è già stato dimostrato nella discussione precedente.
printf
è molto speciale, perché continuerà ad adattare le variabili alla stringa di formato fino a quando tutte non saranno esaurite. Quindi quando lo fai printf "%d, %d, %d \n" $a $b $c
printf vede una stringa di formato con 3 decimali, ma ci sono più argomenti di 3 (perché le tue variabili si espandono effettivamente a singoli 1,2,3,4,5,6). Questo può sembrare confuso, ma esiste per una ragione come un comportamento migliorato rispetto a quello che fa la vera printf()
funzione in linguaggio C.
Quello che hai fatto anche qui che influenza l'output è che le tue variabili non sono quotate, il che consente alla shell (non printf
) di scomporre le variabili in 6 elementi separati. Confronta questo con la citazione:
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
Esattamente perché la $c
variabile è quotata, ora è riconosciuta come una stringa intera 3 4
e non si adatta al %d
formato, che è solo un singolo numero intero
Ora fai lo stesso senza citare:
bash-4.3$ printf "%d %d %d\n" $a $b $c
1 2 3
4 0 0
printf
dice ancora: "OK, ci sono 6 elementi lì ma il formato ne mostra solo 3, quindi continuerò a sistemare le cose e lascerò vuoto tutto ciò che non posso abbinare all'input effettivo dell'utente".
E in tutti questi casi non devi credermi sulla parola. Basta correre strace -e trace=execve
e vedere di persona cosa "vede" effettivamente il comando:
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 +++
Note aggiuntive
Come Charles Duffy ha giustamente sottolineato nei commenti, bash
ha il suo built-in printf
, che è quello che stai usando nel tuo comando, strace
chiamerà effettivamente la /usr/bin/printf
versione, non la versione della shell. A parte piccole differenze, per il nostro interesse in questa particolare domanda gli identificatori di formato standard sono gli stessi e il comportamento è lo stesso.
Ciò che dovrebbe anche essere tenuto presente è che la printf
sintassi è molto più portatile (e quindi preferita) di echo
, per non parlare del fatto che la sintassi è più familiare a C o a qualsiasi linguaggio simile a C che ha printf()
funzione in essa. Vedere questo eccellente risposta di terdon sul tema della printf
vs echo
. Sebbene sia possibile personalizzare l'output in base alla propria shell specifica sulla versione specifica di Ubuntu, se si esegue il porting di script su sistemi diversi, probabilmente si dovrebbe preferire printf
piuttosto che l'eco. Forse sei un amministratore di sistema principiante che lavora con macchine Ubuntu e CentOS, o forse anche FreeBSD - chi lo sa - quindi in questi casi dovrai fare delle scelte.
Citazione dal manuale di bash, sezione SHELL BUILTIN COMMANDS
leggi [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ... ]
Una riga viene letta dall'input standard o dal descrittore di file fd fornito come argomento all'opzione -u, e la prima parola viene assegnata al primo nome, la seconda parola al secondo nome e così via, con gli avanzi le parole e i loro separatori intermedi assegnati al cognome. Se il flusso di input legge un numero inferiore di parole rispetto ai nomi, ai nomi rimanenti vengono assegnati valori vuoti. I caratteri in IFS sono usati per dividere la linea in parole usando le stesse regole che la shell usa per l'espansione (descritte sopra in Word Splitting).
strace
caso e l'altro -strace printf
sta usando/usr/bin/printf
, mentreprintf
direttamente in bash sta usando la shell incorporata con lo stesso nome. Non saranno sempre identici - ad esempio, l'istanza di bash ha identificatori di formato%q
e, nelle nuove versioni,$()T
per la formattazione dell'ora.