No es solo echo vs printf
Primero, comprendamos qué sucede con la read a b c
parte. read
realizará una división de palabras en función del valor predeterminado de IFS
variable que es space-tab-newline, y se ajustará a todo en función de eso. Si hay más datos que las variables para mantenerlo, se ajustarán las partes divididas en las primeras variables, y lo que no se puede ajustar, entrará en el último. Esto es lo que quiero decir:
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
Así es exactamente como se describe en bash
el manual (ver la cita al final de la respuesta).
En su caso, lo que sucede es que 1 y 2 se ajustan a las variables ayb, y c toma todo lo demás, que es 3 4 5 6
.
Lo que también verá muchas veces es que la gente usa while IFS= read -r line; do ... ; done < input.txt
para leer archivos de texto línea por línea. Nuevamente, IFS=
está aquí por una razón para controlar la división de palabras, o más específicamente: deshabilítelo y lea una sola línea de texto en una variable. Si no estuviera allí, read
estaría tratando de ajustar cada palabra individual en line
variable. Pero esa es otra historia, que te animo a estudiar más tarde, ya que while IFS= read -r variable
es una estructura muy utilizada.
Comportamiento echo vs printf
echo
hace lo que esperarías aquí. Muestra sus variables exactamente como las read
ha organizado. Esto ya ha sido demostrado en discusiones anteriores.
printf
es muy especial, porque seguirá ajustando variables en la cadena de formato hasta que todas se agoten. Entonces, cuando hace printf "%d, %d, %d \n" $a $b $c
printf ve la cadena de formato con 3 decimales, pero hay más argumentos que 3 (porque sus variables en realidad se expanden a 1,2,3,4,5,6 individuales). Esto puede sonar confuso, pero existe por una razón como un mejor comportamiento de lo que hace la función real printf()
en lenguaje C.
Lo que también hizo aquí que afecta el resultado es que sus variables no se citan, lo que permite que el shell (no printf
) descomponga las variables en 6 elementos separados. Compare esto con las citas:
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
Exactamente porque $c
se cita la variable, ahora se reconoce como una cadena completa 3 4
y no se ajusta al %d
formato, que es solo un entero
Ahora haga lo mismo sin citar:
bash-4.3$ printf "%d %d %d\n" $a $b $c
1 2 3
4 0 0
printf
nuevamente dice: "OK, tiene 6 elementos allí, pero el formato muestra solo 3, así que seguiré ajustando cosas y dejando en blanco lo que no pueda coincidir con la entrada real del usuario".
Y en todos estos casos no tiene que creer mi palabra. Simplemente corre strace -e trace=execve
y comprueba por ti mismo qué "ve" realmente el 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 +++
Notas adicionales
Como Charles Duffy señaló correctamente en los comentarios, bash
tiene su propio incorporado printf
, que es lo que está utilizando en su comando, en strace
realidad llamará /usr/bin/printf
versión, no versión de shell. Además de pequeñas diferencias, para nuestro interés en esta pregunta en particular, los especificadores de formato estándar son los mismos y el comportamiento es el mismo.
Lo que también debe tenerse en cuenta es que la printf
sintaxis es mucho más portátil (y, por lo tanto, preferida) que echo
, sin mencionar que la sintaxis es más familiar para C o cualquier lenguaje similar a C que tenga printf()
funciones. Ver este excelente respuesta por terdon sobre el tema de printf
frente echo
. Si bien puede hacer que la salida se adapte a su shell específico en su versión específica de Ubuntu, si va a portar scripts en diferentes sistemas, probablemente debería preferir en printf
lugar de echo. Tal vez sea un administrador de sistemas principiante que trabaje con máquinas Ubuntu y CentOS, o tal vez incluso FreeBSD, quién sabe, por lo que en tales casos tendrá que tomar decisiones.
Cita del manual bash, sección SHELL BUILTIN COMMANDS
leer [-ers] [-a aname] [-d delim] [-i texto] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [nombre ... ]
Se lee una línea de la entrada estándar, o del descriptor de archivo fd suministrado como argumento para la opción -u, y la primera palabra se asigna al primer nombre, la segunda palabra al segundo nombre, y así sucesivamente, con las sobras. palabras y sus separadores intermedios asignados al apellido. Si se leen menos palabras de la secuencia de entrada que nombres, a los nombres restantes se les asignan valores vacíos. Los caracteres en IFS se usan para dividir la línea en palabras usando las mismas reglas que el shell usa para la expansión (descrita anteriormente en Separación de palabras).
strace
caso y el otrostrace printf
es el uso/usr/bin/printf
, mientras queprintf
directamente en bash está usando el shell incorporado con el mismo nombre. No siempre serán idénticos; por ejemplo, la instancia de bash tiene especificadores de formato%q
y, en nuevas versiones,$()T
para formateo de hora.