¿Por qué siempre falla la asignación de una variable con sustitución de comando y luego la repetición de esa variable?

¿Por qué siempre falla la asignación de una variable con sustitución de comando y luego la repetición de esa variable?

¿Por qué lo siguiente no funciona en Bash?

# Ensure TEST is unset
export TEST=''
echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"

Siempre devuelve una línea en blanco por algún motivo.

Hacerlo en líneas separadas da el resultado esperado:

# Ensure TEST is unset
export TEST=''
echo "Hello world" > test.txt
TEST=$(cat test.txt)
echo "$TEST"
Hello world!

¿Cómo puedo hacer que esto funcione en una sola línea?

Necesito esto para descifrar unGPGarchivo que contiene una cadena con caracteres especiales que luego paso aansible.

ANSIBLE_VAULT_PASSWORD="$( gpg -d ~/gpg_encrypted_vault_password_file 2>/dev/null )" ansible-playbook etc etc etc

Por alguna razón, los desarrolladores de Ansible piensan que almacenar una contraseña de bóveda en un archivo de texto sin formato es seguro y no me gusta escribir (o copiar y pegar) contraseñas largas y seguras cada vez que pruebo una obra, por lo que uso el agente GPG con un archivo cifrado. es la única solución segura que se me ocurre.

Respuesta1

No quiero entrar en conflicto con ninguna de las otras respuestas preexistentes, pero pensé que un análisis paso a paso podría ayudar. (La respuesta de u1686_grawityParece exacto, pero pensé que esto podría ser más claro...)

# ensure TEST is unset export TEST=''

Buenas noticias: es probable que esto funcione como esperaba. # convierte la primera línea en un comentario y luego la siguiente línea hace que la variable TEST tenga una cadena vacía (cero bytes).

echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"

El shell nota el &&. Ejecutará la primera parte del código...

echo "Hello world!" > test.txt

Luego, procesa el &&. Observa el valor de retorno del comando echo y ve que es cero. Por tanto, puede proceder a ejecutar el segundo comando. El segundo comando es:

TEST="$(cat test.txt)" echo "$TEST"

Entonces, lo primero que hará el shell aquí es reemplazar las variables. La "sustitución de comando" actúa aquí como una variable, por lo que se ejecuta el comando "cat text.txt". En la perspectiva del shell, el comando ahora se parece más a:

TEST="Hello world!" echo "$TEST"

Ahora, probablemente todo se ve bien hasta ahora, pero aquí está el gran problema. El shell aún no comienza a ejecutar el TEST="Hello world!"comando ''. En cambio, el shell nota que todavía hay una referencia a $TEST, por lo que la reemplaza.

Ahora, el comando que el shell planea ejecutar se parece a este:

TEST="Hello world!" echo ""

Supongo que si voy a entrar en detalles, señalaré que el shell realiza el acto de dividir este comando en pedazos. Esta acción elimina las comillas. Entonces las piezas individuales se ven así:

  1. PRUEBA
  2. =
  3. ¡Hola Mundo!
  4. eco
  5. (valor vacío)

Bien, ahora el shell finalmente ha terminado de reemplazar cosas, por lo que puede pasar a la siguiente tarea, que es encontrar qué código ejecutable ejecutar.

Observa que la línea de comando comienza con una forma de A=B C(donde Arepresenta un nombre de variable, el signo igual especifica que a una variable se le asignará un valor y Brepresenta el valor que se asignará, y luego Chay una parte opcional que representa un comando para correr.

Cuando el shell nota que la línea de comando se ajusta a este patrón general, el código ejecutable que va a ejecutar es el código que maneja el signo igual. Básicamente, el signo igual es similar al nombre de un archivo ejecutable, en el sentido de que el signo igual termina controlando qué código ejecutará el shell. El shell procede a ejecutar el código para asignar una variable cuyo nombre TESTtenga un valor de Hello world!.

Luego, una vez hecho esto, el shell iniciará el comando solicitado, que utiliza las pociones cuarta y quinta identificadas anteriormente (donde la cuarta parte es el echocomando y la quinta parte es la cadena vacía).

La razón por la que respondo (err... quiero decir,La respuesta de sqrt-1) funciona es que el segundo && hace que el shell ejecute el TEST="$(cat test.txt)"valor de retorno del código, observe que es cero, y solo entonces proceda a procesar lo que viene después del segundo &&(de modo que la echo "$TEST"parte tendrá la $TESTvariable procesada, y ahora todo obras).

Tenga en cuenta que la respuesta proporcionada por sqrt-1 muestra un código que es un poco derrochador, aunque es comprensible porque responde directamente a la pregunta formulada. Lo que básicamente hace es:

Primero, divide el comando en tres partes:

  • eco "¡Hola mundo!" > prueba.txt
  • PRUEBA="$(cat prueba.txt)"
  • eco "$PRUEBA"
  • La tercera parte sólo se ejecuta si la segunda parte (se ejecuta y) tiene éxito, y la segunda parte sólo se ejecuta si el primer comando tiene éxito, debido a los dos &&operadores.

    Otra variación podría ser:

    echo "Hello world!" > test.txt && ( TEST="$(cat test.txt)" ; echo "$TEST" )

    Con ese punto y coma, los resultados de ' TEST="$(cat test.txt)"' no se evaluarán para determinar si ' echo "$TEST"' will be run. When I see a &&, tiendo a tratar de averiguar qué se va a evaluar y por qué eso importaría. Entonces, a pesar de la complejidad adicional de tener que usar paréntesis, esta variación con un punto y coma en realidad me parece que puede ser un poco más sencilla de procesar mentalmente.

    Respuesta2

    No funciona porque $varel shell realiza las expansiones antes de ejecutar la línea de comando, es decirnomediante el comando 'eco'.

    Mientras tanto, VAR=value somecommandno es una asignación regular: solo pretende inyectar una variable de entorno en el nuevo proceso, pero no tiene ningún efecto en las variables del shell antes de que se cree ese proceso, es decir, es completamente diferente de VAR=value; somecommand.

    En otras palabras, sucede en el orden opuesto al que describe el título de su publicación. Primero, el (stil-empty) "$TEST"se expande a "",entonces echo ""se ejecuta con una TEST=...variable de entorno adicional que no le importa.

    Dado que se supone que sus comandos de prueba imitan el comportamiento de Ansible, en realidad deberían mirarsuvariables de entorno y no depender de la expansión previa del shell; algo así como VAR=value envo VAR=value printenv VARencajaría mucho mejor.

    Respuesta3

    ¿Por qué lo siguiente no funciona en bash?

    echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"
    

    Aunque si lo ejecutasShellCheck: herramienta de análisis de scripts de shellte dirá por qué:

    $ shellcheck myscript
     
    Line 2:
    echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"
                                      ^-- SC2097 (warning): This assignment is only seen by the forked process.
    >>                                                             ^-- SC2098 (warning): This expansion will not see the mentioned assignment.
    

    VerShellCheck: SC2097: esta asignación solo la ve el proceso bifurcado.

    Respuesta4

    Según el enlace proporcionado por @DavidPostill, usaría

    echo "Hello world!" > test.txt && TEST="$(cat test.txt)" && echo "$TEST"
    

    DEBIDA EDICIÓN
    Como muchos usuarios señalaron acertadamente, la mía está lejos de ser una respuesta útil: no permite que nadie aprenda nada sobre el tema.
    Por favor, consulte en su lugar el de @TOOGAM.correoa continuación, cuál es la mejor respuesta (mi opinión personal) y expone completamente por qué el código de @Peter Mortensen no funciona, cómo funciona la expansión variable de bash e incluso por qué mi uso de un segundo &&no es la opción óptima.

    información relacionada