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.Esto es solo una sugerencia y no pretende reemplazar la respuesta de Sergiy. Creo que Sergiy escribió una gran respuesta sobre por qué son diferentes en la impresión. Cómo se asigna la variable en la lectura con el resto en la
$c
variable como3 4 5 6
después1
y2
se asignan aa
yb
.echo
no dividirá la variable por ti donde loprintf
harás con el%d
s.Sin embargo, puedes hacer que básicamente te den las mismas respuestas manipulando el eco de los números al comienzo del comando:
En
/bin/bash
puedes usar:En
/bin/sh
solo puedes usar:Bash usa -e para habilitar los caracteres \ escape donde sh no lo necesita ya que ya está habilitado.
\n
hace que cree una nueva línea, por lo que ahora la línea de eco se divide en dos líneas separadas que ahora se pueden usar dos veces para su declaración de bucle de eco:Lo que a su vez produce la misma salida con el uso del
printf
comando:En
sh
¡Espero que esto ayude!
fuente
echo -e
no es solo una extensión de POSIX, sino cualquieraecho
que no se emita-e
en su salida dado que la entrada en realidad está desafiando / rompiendo la especificación de letras negras. (bash no es compatible de manera predeterminada, pero cumple con el estándar cuando se configuran ambosposix
y losxpg_echo
indicadores; este último se puede hacer predeterminado en tiempo de compilación, lo que significa que no todas las versiones de bash funcionaránecho -e
como espera aquí).echo
en pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html , que describe explícitamente queecho
el comportamiento de ese no está especificado en presencia de-n
barras diagonales inversas o en cualquier lugar en la entrada, y aconseje queprintf
ser utilizado en su lugar.