¿Qué tiene de malo “echo $ (cosas)” o “echo` cosas` ”?

31

Usé uno de los siguientes

echo $(stuff)
echo `stuff`

(donde stuffes, por ejemplo pwd, o dateo algo más complicado).

Entonces me dijeron que esta sintaxis es incorrecta, una mala práctica, no elegante, excesiva, redundante, demasiado complicada, una programación de culto de carga, novato, ingenuo, etc.

Sin embargo, el comando hace el trabajo, así que ¿qué es exactamente el problema?

Kamil Maciorowski
fuente
11
Es redundante y, por lo tanto, no debe usarse. Comparar con el uso inútil del gato: porkmail.org/era/unix/award.html
dr01
77
echo $(echo $(echo $(echo$(echo $(stuff)))))también lo hace el trabajo, y aún así es probable que no lo utilizaría, ¿verdad? ;-)
Peter - Restablece a Mónica el
1
¿Qué estás tratando de hacer con esa expansión, exactamente?
curioso
@curiousguy Estoy tratando de ayudar a otros usuarios, de ahí mi respuesta a continuación. Recientemente, he visto al menos tres preguntas que usan esta sintaxis. Sus autores intentaban hacer ... varios stuff. : D
Kamil Maciorowski
2
Además, ¿no estamos todos de acuerdo en que no es prudente limitarse a una cámara de eco? ;-)
Peter - Restablece a Mónica el

Respuestas:

63

tl; dr

Una suela stuffsería más probable que funcione para usted.



Respuesta completa

Lo que pasa

Cuando corres foo $(stuff), esto es lo que sucede:

  1. stuff carreras;
  2. su salida (stdout), en lugar de imprimirse, reemplaza $(stuff)en la invocación de foo;
  3. luego se fooejecuta, sus argumentos de línea de comando obviamente dependen de lo que stuffdevuelve.

Este $(…)mecanismo se llama "sustitución de comando". En su caso, el comando principal es el echoque básicamente imprime sus argumentos de línea de comando en stdout. Entonces, cualquier cosa que stuffintente imprimir en stdout es capturada, pasada echoe impresa en stdout por echo.

Si desea que la salida stuffse imprima en stdout, simplemente ejecute la suela stuff.

La `…`sintaxis tiene el mismo propósito que $(…)(bajo el mismo nombre: "sustitución de comandos"), aunque existen pocas diferencias, por lo que no puede intercambiarlas a ciegas. Vea estas preguntas frecuentes y esta pregunta .


¿Debo evitar echo $(stuff)pase lo que pase?

Es posible que desee utilizar una razón echo $(stuff)si sabe lo que está haciendo. Por la misma razón que debes evitar echo $(stuff)si realmente no sabes lo que estás haciendo.

El punto es stuffy echo $(stuff)no es exactamente equivalente. Esto último significa llamar al operador split + glob en la salida de stuffcon el valor predeterminado de $IFS. Citar dos veces la sustitución del comando evita esto. Citar solo la sustitución de comandos hace que ya no sea una sustitución de comandos.

Para observar esto cuando se trata de dividir, ejecute estos comandos:

echo "a       b"
echo $(echo "a       b")
echo "$(echo "a       b")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "a       b")'

Y para globbing:

echo "/*"
echo $(echo "/*")
echo "$(echo "/*")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "/*")'

Como puede ver, echo "$(stuff)"es equivalente (-ish *) a stuff. Podrías usarlo, pero ¿de qué sirve complicar las cosas de esta manera?

Por otro lado, si desea que la salida se stuffdivida + globbing, entonces puede resultarle echo $(stuff)útil. Sin embargo, tiene que ser tu decisión consciente.

Hay comandos que generan resultados que deben evaluarse (lo que incluye división, globalización y más) y ejecutarse por el shell, por lo que eval "$(stuff)"es una posibilidad (vea esta respuesta ). Nunca he visto un comando que necesita su salida para someterse a división adicional + globbing antes de imprimirse . El uso deliberado echo $(stuff)parece muy poco común.


¿Qué hay de var=$(stuff); echo "$var"?

Buen punto. Este fragmento:

var=$(stuff)
echo "$var"

debe ser equivalente a echo "$(stuff)"equivalente (-ish *) a stuff. Si es todo el código, simplemente ejecute en su stufflugar.

Sin embargo, si necesita usar la salida de stuffmás de una vez, entonces este enfoque

var=$(stuff)
foo "$var"
bar "$var"

suele ser mejor que

foo "$(stuff)"
bar "$(stuff)"

Incluso si fooes así echoy obtiene echo "$var"su código, puede ser mejor mantenerlo de esta manera. Cosas para considerar:

  1. Con var=$(stuff) stuffcarreras una vez; incluso si el comando es rápido, evitar calcular la misma salida dos veces es lo correcto. O tal vez stufftenga otros efectos además de escribir en stdout (por ejemplo, crear un archivo temporal, iniciar un servicio, iniciar una máquina virtual, notificar a un servidor remoto), por lo que no desea ejecutarlo varias veces.
  2. Si stuffgenera resultados dependientes del tiempo o algo aleatorios, puede obtener resultados inconsistentes de foo "$(stuff)"y bar "$(stuff)". Después de que var=$(stuff)el valor de $vares fijo y puede estar seguro foo "$var"y bar "$var"obtener un argumento de línea de comando idéntico.

En algunos casos, en lugar de foo "$var"que desee (necesite) usar foo $var, especialmente si stuffgenera múltiples argumentos para foo(una variable de matriz puede ser mejor si su shell lo admite). De nuevo, sepa lo que está haciendo. Cuando se trata de echola diferencia entre echo $vary echo "$var"es lo mismo que entre echo $(stuff)y echo "$(stuff)".


* Equivalente ( -ish )?

Dije que echo "$(stuff)"es equivalente (-ish) a stuff. Hay al menos dos problemas que hacen que no sea exactamente equivalente:

  1. $(stuff)se ejecuta stuffen una subshell, por lo que es mejor decir que echo "$(stuff)"es equivalente (-ish) a (stuff). Los comandos que afectan el shell en el que se ejecutan, si están en un subshell, no afectan al shell principal.

    En este ejemplo stuffes a=1; echo "$a":

    a=0
    echo "$(a=1; echo "$a")"      # echo "$(stuff)"
    echo "$a"
    

    Compáralo con

    a=0
    a=1; echo "$a"                # stuff
    echo "$a"
    

    y con

    a=0
    (a=1; echo "$a")              # (stuff)
    echo "$a"
    

    Otro ejemplo, comienza con stuffser cd /; pwd:

    cd /bin
    echo "$(cd /; pwd)"           # echo "$(stuff)"
    pwd
    

    y prueba stuffy (stuff)versiones.

  2. echoNo es una buena herramienta para mostrar datos no controlados . Esto de lo echo "$var"que estábamos hablando debería haber sido printf '%s\n' "$var". Pero como la pregunta menciona echoy dado que la solución más probable es no usarla echoen primer lugar, decidí no presentarla printfhasta ahora.

  3. stuffo (stuff)intercalará la salida stdout y stderr, mientras echo $(stuff)imprimirá toda la salida stderr de stuff(que se ejecuta primero), y solo luego la salida stdout digerida por echo(que se ejecuta al final).

  4. $(…)elimina cualquier nueva línea final y luego la echoagrega de nuevo. Entonces echo "$(printf %s 'a')" | xxdda una salida diferente que printf %s 'a' | xxd.

  5. Algunos comandos ( lspor ejemplo) funcionan de manera diferente dependiendo de si la salida estándar es una consola o no; así ls | catque no hace lo mismo ls. Del mismo modo echo $(ls)funcionará de manera diferente que ls.

    Dejando de lslado, en un caso general, si tiene que forzar este otro comportamiento, entonces stuff | cates mejor echo $(ls)o echo "$(ls)"porque no desencadena todos los otros problemas mencionados aquí.

  6. Posiblemente diferente estado de salida (mencionado para completar la respuesta de esta wiki; para más detalles vea otra respuesta que merece crédito).

revs Kamil Maciorowski
fuente
55
Una diferencia más: el stuffcamino se intercalará stdouty generará stderr, mientras echo `stuff` que imprimirá toda la stderrsalida stuff, y solo entonces la stdoutsalida.
Ruslan
3
Y este es otro ejemplo de: Difícilmente puede haber demasiadas citas en los scripts de bash. echo $(stuff)puede echo "$(stuff)"meterte en muchos más problemas como si no quisieras englobar y expandirte explícitamente.
rexkogitans
2
Una pequeña diferencia más: $(…)elimina cualquier nueva línea final y luego la echoagrega de nuevo. Entonces echo "$(printf %s 'a')" | xxdda una salida diferente que printf %s 'a' | xxd.
derobert
1
No debe olvidar que algunos comandos ( lspor ejemplo) funcionan de manera diferente dependiendo de si la salida estándar es una consola o no; así ls | catque no hace lo mismo ls. Y, por supuesto echo $(ls), funcionará de manera diferente que ls.
Martin Rosenau
@Ruslan La respuesta ahora es wiki de la comunidad. He añadido tu comentario útil a la wiki. Gracias.
Kamil Maciorowski
5

Otra diferencia: el código de salida del subconjunto se pierde, por lo que echose recupera el código de salida de .

> stuff() { return 1
}
> stuff; echo $?
1
> echo $(stuff); echo $?
0
WaffleSouffle
fuente
3
¡Bienvenido a Super User! ¿Puedes explicar la diferencia que estás demostrando? Gracias
bertieb
44
Creo que esto muestra que el código de retorno $?será el código de retorno de echo(para echo $(stuff)) en lugar del código returneditado por stuff. La stufffunción return 1(código de salida), pero la echoestablece en "0" independientemente del código de retorno de stuff. Todavía debería estar en la respuesta.
Ismael Miguel
se perdió el valor de retorno de la subshell
phuclv