¿Por qué no puedo usar variables como prefijo de un comando para establecer variables de entorno?

11

Normalmente, es posible establecer una variable de entorno para un comando con el prefijo así:

hello=hi bash -c 'echo $hello'

También sé que podemos usar una variable para sustituir cualquier parte de una invocación de comando como la siguiente:

$ cmd=bash
$ $cmd -c "echo hi" # equivalent to bash -c "echo hi"

Me sorprendió mucho descubrir que no se puede usar una variable para prefijar un comando para establecer una variable de entorno. Caso de prueba:

$ prefix=hello=hi
$ echo $prefix # prints hello=hi
$ $prefix bash -c 'echo $hello'
hello=hi: command not found

¿Por qué no puedo establecer la variable de entorno usando una variable? ¿La parte del prefijo es una parte especial? Pude hacerlo funcionar usando eval en frente, pero todavía no entiendo por qué. Estoy usando bash 4.4.

wbkang
fuente
1
Puede usar en envlugar de evalcuál IIRC es más seguro, pero más lento.
wjandrea

Respuestas:

14

Sospecho que esta es la parte de la secuencia que te está atrapando:

Las palabras que no son asignaciones variables o redirecciones se expanden (ver Expansiones de Shell). Si queda alguna palabra después de la expansión, la primera palabra se toma como el nombre del comando y las palabras restantes son los argumentos

Eso es del manual de referencia de Bash en la sección sobre Expansión de comandos simples.

En el cmd=bashejemplo, no se establecen variables de entorno, y bash procesa la línea de comandos a través de la expansión de parámetros, dejando bash -c "echo hi".

En el prefix=hello=hiejemplo, nuevamente no hay asignaciones variables en la primera pasada, por lo que el procesamiento continúa con la expansión de parámetros, lo que resulta en una primera palabra de hello=hi.

Una vez que se han procesado las asignaciones de variables, no se vuelven a procesar durante la ejecución del comando.

Vea el procesamiento y sus resultados en set -x:

$ prefix=hello=hi
+ prefix=hello=hi
$ $prefix bash -c 'echo $hello'
+ hello=hi bash -c 'echo $hello'
-bash: hello=hi: command not found
$ hello=42 bash -c 'echo $hello'
+ hello=42
+ bash -c 'echo $hello'
42

Para una variación más segura de "expansión variable" -como- "variables de entorno" que eval, considere la sugerencia de wjandrea deenv :

prefix=hello=hi
env "$prefix" bash -c 'echo "$hello"'
hi

No es estrictamente una asignación de variable de línea de comandos, ya que estamos utilizando la envfunción principal de la utilidad de asignar variables de entorno a un comando, pero cumple el mismo objetivo. La $prefixvariable se expande durante el procesamiento de la línea de comandos, proporcionando el nombre = valor a envquién se la pasa bash.

Jeff Schaller
fuente
3
Del mismo modo, $foo=bar bash -c ...no funcionará, porque $foo=barno es una asignación variable (cualquiera que sea el valor de $foo), porque $foo=barno se ajusta al name=valuepatrón para la asignación variable.
muru
3

Porque $prefixno es una tarea. @Jeff tiene la explicación más larga .

En su lugar, podría hacer algo similar con una función:

$ prefix() { hello=hi "$@"; }
$ prefix bash -c 'echo "$hello"'
hi

... e incluso puedes apilarlos, si quieres:

$ foo() { foo=123 "$@"; }
$ bar() { bar=456 "$@"; }
$ foo bar bash -c 'echo "$bar $foo"'
456 123
ilkkachu
fuente