Cómo "expandir" una variable bash (el código incluido funciona para bash pero no para zsh)

Cómo "expandir" una variable bash (el código incluido funciona para bash pero no para zsh)

Una respuesta comoesteHace un buen trabajo explicando cómo controlar el pase.todovariables hasta un comando.

Quería explorar cómo hacer esto por argumento. Observe (esto fue probado en zsh):

$ program() { echo 1: $1 2: $2 3: $3; }

$ run() { program "$@"; }

$ run2() { echo `run $1`; }

$ run2 'a b' c
1: a b 2: 3:

Quiero una forma de pasar un primer argumento que es una cadena que tiene espacios y tener ese $@estilo empalmado en otro comando. por ejemplo, run2 "a b" cdebería producir1: a 2: b 3:

En este punto, he resuelto mi problema inmediato porque, aunque mi código de prueba se rompe cuando lo probé en zsh, una vez implementado en un script bash real, funciona.

Esto indica que tal vez esto dependa de algunas complejidades en el manejo de cadenas y argumentos que no son "seguras". Por lo tanto, esto es más bien una solicitud de comentarios sobre una forma más sólida de lograr este comportamiento de manera confiable.

Respuesta1

La diferencia entre bash y zsh que importa aquí está en la forma de run2llamar run, específicamente el efecto de dejarlo $1sin comillas.

  • En zsh, run $1aplica el operador "remove-if-empty" a $1, es decir, llama runcon el primer argumento que se pasó a run2, excepto que si el primer argumento run2estaba vacío (o si run2se llamó sin argumento), entonces runse llama sin argumento.
  • En otros shells estilo Bourne, como bash, run $1se aplica el operador “split+glob” a $1, es decir, divide el primer argumento run2en fragmentos separados por espacios¹, interpreta cada pieza como un patrón comodín² y reemplaza cada patrón comodín que coincida con uno o más archivo por la lista de coincidencias.

Por lo tanto, run2 'a b' cllama runcon el argumento a ben zsh (el argumento se pasa sin cambios), pero llama runcon los dos argumentos ay ben bash (dividido en partes delimitadas por espacios en blanco).

Quiero una forma de pasar un primer argumento que es una cadena que tiene espacios y tener ese $@estilo empalmado en otro comando. por ejemplo, run2 "a b" cdebería producir1: a 2: b 3:

Tu descripción y tu ejemplo dicen cosas diferentes. Si desea pasar el primer argumento al otro comando, utilice run "$1"para asegurarse de que el argumento no esté dividido. Pasar argumentos sin cambios es el objetivo de "$@".

Parece que lo que realmente quieres hacer es dividir el primer argumento en run2fragmentos delimitados por espacios en blanco. En bash, puede hacer esto desactivando la expansión con comodines y luego (suponiendo que IFSno haya cambiado con respecto al valor predeterminado) usando una expansión sin comillas.

run2 () ( set -f; run $1; )

( echo "$(somecommand)"es esencialmente equivalente a ejecutar somecommanden un subshell, y parece que esto es lo que quiso decir en lugar de echo $(somecommand)aplicar split+glob en la salida del comando, por lo que eliminé la sustitución de comando de eco redundante).

En zsh, puede usar el =carácter en una sustitución de parámetros para realizar la división del mundo (y no globalización) en el valor.

run2 () { run $=1; }

La sintaxis de zsh no es compatible con la de sh simple. Si desea obtener un script sh de zsh, puede utilizar la emulatecompilación:

emulate sh -c '. myscript.sh'

Úselo emulate kshpara emular (algunas características de) ksh. Esto no emula todas las funciones de bash, pero le permite usar matrices.

¹ De manera más general, basado en el valor de IFS.
² A menos que esto se haya desactivado con set -f.

Respuesta2

Bienvenido al mundo de las citas y las sorpresas.

El problema principal es que zshno se divide en caracteres IFS de forma predeterminada.
En eso: se diferencia de todos los demás caparazones.

Para probar cómo las comillas cambian la forma en que funcionan los shells, necesitamos un código que pruebe las versiones entre comillas y sin comillas de su código.

Su código (agregando un par de variables):

program() { printf '%02d-1: %6s   2: %6s    3: %6s' "$i" "$1" "$2" "$3"; }
runa()  { program  "$@" ; }
run1()  { echo "`runa  $1 $2 $3 `"; }
run1    'a b' c

Permítanme ampliar los detalles.

Supongamos que crea un shenlace local que apunta al lugar donde zshvive:

ln -s "/usr/bin/zsh" ./sh

Y, además, supongamos que copia el siguiente script en un soarchivo.

Un script que repite cada función con variables entre comillas y sin comillas:

program() { printf '%02d-1: %6s   2: %6s    3: %6s' "$i" "$1" "$2" "$3"; }
runa() { program "$@"; }
runb() { program  $@ ; }

run1() { echo "`runa  "$1" "$2" "$3"`"; }
run2() { echo "`runb  "$1" "$2" "$3"`"; }
run3() { echo "`runa  $1 $2 $3`"; }
run4() { echo "`runb  $1 $2 $3`"; }

for i in `seq 4`; do
    run"$i" 'a b' c
done

Luego, al ejecutar, obtenemos esto impreso:

# Any shell (except zsh) results.
01-1:    a b   2:      c    3:
02-1:      a   2:      b    3:      c
03-1:      a   2:      b    3:      c
04-1:      a   2:      b    3:      c

Sólo se mantiene unida la primera ejecución (ejecución1), donde se cita todo 'a b'.

Sin embargo, zsh actúa como si todo hubiera sido citado todo el tiempo:

# ZSH results.
01-1:    a b   2:      c    3:
02-1:    a b   2:      c    3:
03-1:    a b   2:      c    3:
04-1:    a b   2:      c    3:

zsh en emulación.

Se supone que zshemulará shells más antiguos si se llama como sho ksh.
Pero en la práctica esto no siempre es cierto:

$ ./sh ./so   # emulated sh
01-1:    a b   2:      c    3:
02-1:    a b   2:      c    3:
03-1:      a   2:      b    3:      c
04-1:      a   2:      b    3:      c

La segunda línea es diferente a la segunda línea de cualquier otro shell.

Es una buena idealee las respuestas a esta pregunta

información relacionada