Non è solo echo vs printf
Innanzitutto, capiamo cosa succede con la read a b cparte. readeseguirà la suddivisione delle parole in base al valore predefinito della IFSvariabile 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 bashmanuale (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.txtleggere 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 readfosse, cercherebbe di adattare ogni singola parola alla linevariabile. 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
echofa quello che ti aspetteresti qui. Visualizza le variabili esattamente come le readha 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 $cprintf 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 $cvariabile è quotata, ora è riconosciuta come una stringa intera 3 4e non si adatta al %dformato, 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=execvee 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, bashha il suo built-in printf, che è quello che stai usando nel tuo comando, stracechiamerà effettivamente la /usr/bin/printfversione, 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 printfsintassi è 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 printfvs 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 printfpiuttosto 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).
stracecaso e l'altro -strace printfsta usando/usr/bin/printf, mentreprintfdirettamente 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%qe, nelle nuove versioni,$()Tper la formattazione dell'ora.