¿Dónde se ha ido el char de línea nueva de mi sustitución de comando?

15

El siguiente código describe mejor la situación. ¿Por qué la última línea no emite el carácter de línea nueva final? La salida de cada línea se muestra en el comentario. Estoy usando GNU bash, versión 4.1.5

     echo -n $'a\nb\n'                  | xxd -p  # 610a620a  
           x=$'a\nb\n'   ; echo -n "$x" | xxd -p  # 610a620a
     echo -ne "a\nb\n"                  | xxd -p  # 610a620a
x="$(echo -ne "a\nb\n")" ; echo -n "$x" | xxd -p  # 610a62
Peter.O
fuente
44
Solución para los raros momentos en que se necesita:tmp=$(somecommand; echo a); tmp=${tmp%a}
Gilles 'SO- deja de ser malvado'
1
@Gilles: Re su ejemplo, arriba: tmp=$(somecommand; echo a)... Esto ciertamente ha llevado el punto a casa ... Hasta que vi el ejemplo, mi tendencia todavía habría sido usar echo -n a... ¡pero, por supuesto !, no hay necesidad de el -n, porque la sustitución de comandos eliminará la nueva línea final introducida en cualquier caso. ... gracias ...
Peter.O
stackoverflow.com/questions/613572/…
sancho.s Restablecer Monica

Respuestas:

18

La función de sustitución de comandos $()(y su primo el backtick) elimina específicamente las nuevas líneas finales. Este es el comportamiento documentado , y siempre debe tenerlo en cuenta al usar la construcción.

El operador de sustitución no elimina las líneas nuevas dentro del cuerpo del texto, pero también se pueden eliminar al dividir las palabras en el shell, por lo que el resultado depende de si usó comillas o no. Tenga en cuenta la diferencia entre estos dos usos:

$ echo -n "$(echo -n 'a\nb')"
a
b

$ !! | xxd -p
610a62

$ echo -n  $(echo -n 'a\nb')
a b

$ !! | xxd -p   
612062

En el segundo ejemplo, la salida no fue citada y la nueva línea se interpretó como una división de palabras, ¡haciendo que aparezca en la salida como un espacio!

Caleb
fuente
1
Gracias Caleb ... Estaba al tanto de la división de palabras que cambiaba el espacio en blanco a un solo espacio cuando no estaba entre comillas ... Es por eso que me sorprendió mucho ver desaparecer mi nueva línea final a pesar de haberla citado ... Ahora sé que es por el comportamiento 'normal' de la Sustitución de Comando que deja caer una nueva línea final ... Oh, bueno, c'est la vie .. y gracias por el enlace
Peter.O
6

Cuando se usa la sustitución de comandos, el shell ejecuta los comandos en un subshell, devolviendo su stdout. en este proceso, los caracteres IFS pierden su importancia (si no se citan), ya que el comando devuelve palabras divididas simples, por lo que se eliminan las últimas. Por ejemplo:

$ echo "$(echo -e '\n')" | wc
 1       0       1

$ echo -e '\n' | wc
2       0       2

y más prácticamente, pwdfuncionará incluso si el nombre de su directorio tiene una nueva línea en el medio, pero $(pwd)no lo hará.

La solución habitual es agregar algo al final de su comando y luego quitarlo.

Philomath
fuente
1
Gracias Philomath ... por cierto, tu primer ejemplo es sintácticamente incorrecto ('wc' no acepta una cadena como un argumento). Para que tus ejemplos tengan sentido, el primero podría ser echo "$(echo -e '\n')" | wc, el que sale 1   0   1, en comparación con el2   0   2
Peter.O
@fred: Vaya, solo un error tipográfico.
Philomath
1
"convertido a espacios" no es la explicación adecuada, solo hay algo de división involucrada. En particular, puede eliminar espacio de IFS y esos no actuarán como separadores. Más aún, su ejemplo cae en una categoría de caso especial, y hay una diferencia entre $(pwd)y "$(pwd)", vea la respuesta de Caleb.
Stéphane Gimenez
PD ... Su sugerencia de la solución habitual es justo lo que necesitaba para aclarar esto ...
Peter.O
Re "convertido a espacios", ver Word Splitting
Peter.O