Creo un archivo con campos delimitados por tabuladores.
echo foo$'\t'bar$'\t'baz$'\n'foo$'\t'bar$'\t'baz > input
Tengo el siguiente script llamado zsh.sh
#!/usr/bin/env zsh
while read line; do
<<<$line cut -f 2
done < "$1"
Lo pruebo
$ ./zsh.sh input
bar
bar
Esto funciona bien Sin embargo, cuando cambio la primera línea para invocar bash
, falla.
$ ./bash.sh input
foo bar baz
foo bar baz
¿Por qué falla bash
y funciona con esto zsh
?
Solución de problemas adicional
- Usar rutas directas en el shebang en lugar de
env
producir el mismo comportamiento. - Tubería en
echo
lugar de usar la cadena aquí<<<$line
también produce el mismo comportamiento. es decirecho $line | cut -f 2
. - Utilizando en
awk
lugar decut
obras para ambos proyectiles. es decir<<<$line awk '{print $2}'
.
bash
zsh
quoting
whitespace
here-string
Gavilán
fuente
fuente
echo -e 'foo\tbar\tbaz\n...'
,echo $'foo\tbar\tbaz\n...'
oprintf 'foo\tbar\tbaz\n...\n'
o variaciones de éstos. Le ahorra tener que ajustar individualmente cada pestaña o nueva línea.Respuestas:
Lo que sucede es que
bash
reemplaza las pestañas con espacios. Puede evitar este problema diciendo en su"$line"
lugar, o cortando explícitamente espacios.fuente
\t
y lo reemplaza con un espacio?<<< $line
,bash
se divide pero no es global. No hay razón para que se divida aquí, ya que<<<
espera una sola palabra. Se divide y luego se une en ese caso, lo que tiene poco sentido y está en contra de todas las otras implementaciones de shells que han sido compatibles<<<
antes o despuésbash
. OMI es un error.Esto se debe a que
<<< $line
, en , labash
división de palabras (aunque no engloba)$line
ya que no se cita allí y luego une las palabras resultantes con el carácter de espacio (y lo coloca en un archivo temporal seguido de un carácter de nueva línea y lo convierte en el stdin decut
).tab
pasa a estar en el valor predeterminado de$IFS
:La solución con
bash
es citar la variable.Tenga en cuenta que es el único shell que hace eso.
zsh
(de dónde<<<
viene, inspirado en el puerto Unix derc
)ksh93
,mksh
yyash
que también es compatible<<<
, no lo hagas.Cuando se trata de matrices,
mksh
,yash
yzsh
unirse en el primer carácter de$IFS
,bash
yksh93
en el espacio.Hay una diferencia entre
zsh
/yash
ymksh
(versión R52 al menos) cuando$IFS
está vacío:El comportamiento es más consistente en todos los shells cuando se usa
"${a[*]}"
(excepto quemksh
todavía tiene un error cuando$IFS
está vacío).En
echo $line | ...
, ese es el operador habitual de split + glob en todos los shells similares a Bourne perozsh
(y los problemas habituales asociados conecho
).fuente
El problema es que no estás citando
$line
. Para investigar, cambie los dos scripts para que simplemente impriman$line
:y
Ahora, compare su salida:
Como puede ver, porque no está citando
$line
, bash no interpreta las pestañas correctamente. Zsh parece lidiar con eso mejor. Ahora, secut
usa\t
como delimitador de campo por defecto. Por lo tanto, dado que subash
script está comiendo las pestañas (debido al operador split + glob),cut
solo ve un campo y actúa en consecuencia. Lo que realmente está ejecutando es:Entonces, para que su script funcione como se espera en ambos shells, cite su variable:
Entonces, ambos producen la misma salida:
fuente
bash.sh
Como ya se ha respondido, una forma más portátil de usar una variable es citarla:
Hay una diferencia de implementación en bash, con la línea:
Este es el resultado de la mayoría de los proyectiles:
Solo bash divide la variable a la derecha de
<<<
cuando no está entre comillas.Sin embargo, eso se ha corregido en la versión 4.4 de bash.
Eso significa que el valor de
$IFS
afecta el resultado de<<<
.Con la linea:
Todos los shells usan el primer carácter de IFS para unir valores.
Con
"${l[@]}"
, se necesita un espacio para separar los diferentes argumentos, pero algunos shells eligen usar el valor de IFS (¿Es correcto?).Con un IFS nulo, los valores deben unirse, como con esta línea:
Pero tanto lksh como mksh no lo hacen.
Si cambiamos a una lista de argumentos:
Tanto yash como zsh no pueden mantener los argumentos separados. ¿Eso es un error?
fuente
zsh
/yash
y"${l[@]}"
en contextos que no son listas, eso es por diseño, donde"${l[@]}"
solo es especial en contextos de listas. En contextos que no son listas, no hay separación posible, debe unir los elementos de alguna manera. Unirse con el primer personaje de $ IFS es más consistente que unirse con un personaje espacial IMO.dash
lo hace también (dash -c 'IFS=; a=$@; echo "$a"' x a b
). Sin embargo, POSIX tiene la intención de cambiar ese IIRC. Ver esta (larga) discusiónvar=$@
especificar.