¿Cómo puedo expandir una variable entre comillas a nada si está vacía?

21

Digamos que tengo un script haciendo:

some-command "$var1" "$var2" ...

Y, en el caso de que var1esté vacío, prefiero que se reemplace con nada en lugar de la cadena vacía, para que el comando ejecutado sea:

some-command "$var2" ...

y no:

some-command '' "$var2" ...

¿Hay una manera más simple que probar la variable e incluirla condicionalmente?

if [ -n "$1" ]; then
    some-command "$var1" "$var2" ...
    # or some variant using arrays to build the command
    # args+=("$var1")
else
    some-command "$var2" ...
fi

¿Hay una sustitución de parámetros que puede expandirse a nada en bash, zsh o similares? Es posible que todavía quiera usar globbing en el resto de los argumentos, por lo que deshabilitar eso y desarmar la variable no es una opción.

muru
fuente
Sabía que había visto y probablemente había usado esto antes, pero resultó difícil de buscar. Ahora que Michael mostró la sintaxis, recordé dónde la había visto lo suficientemente rápido: unix.stackexchange.com/a/269549/70524 , unix.stackexchange.com/q/68484/70524
muru
Si sabe que es algún tipo de sustitución de parámetros, ¿por qué no buscó en la sección de expansión de parámetros de la manpágina? (-;
Philippos
1
@Philippos No sabía qué era en ese momento, solo que lo había visto o usado antes. Conocimientos conocidos y desconocidos. :(
muru
1
puntos de cookie adicionales por mencionar el uso de una matriz para contener los argumentos en la pregunta misma.
ilkkachu

Respuestas:

29

Los shells y Bash compatibles con Posix tienen${parameter:+word} :

Si el parámetro no está establecido o es nulo, se sustituirá nulo; de lo contrario, se sustituirá la expansión de la palabra (o una cadena vacía si se omite la palabra ).

Entonces solo puedes hacer:

${var1:+"$var1"}

y han var1sido verificados y "$var1"utilizados si está configurado y no está vacío (con las reglas normales de comillas dobles). De lo contrario, se expande a la nada. Tenga en cuenta que aquí solo se cita la parte interna , no todo.

Lo mismo también funciona en zsh. Debe repetir la variable, por lo que no es ideal, pero funciona exactamente como lo deseaba.

Si desea que una variable set-but-empty se expanda a un argumento vacío, use ${var1+"$var1"}en su lugar.

Michael Homer
fuente
1
Suficientemente bueno para mi. Entonces, en esto, no se cita todo, solo la wordparte.
Muru
1
Como la pregunta pedía "bash, zsh o similar" (y para el archivo de preguntas y respuestas), edité para reflejar que esta es una característica posix. Incluso, si agrega una etiqueta / bash a la pregunta después de mi comentario. (-;
Philippos
2
Me tomé la libertad de editar la diferencia entre :+y +.
ilkkachu
¡Muchas gracias por una solución compatible con POSIX! Intento utilizar sh/ dashpara posibles guiones de punto de estrangulamiento, por lo que siempre aprecio que alguien muestre cómo es posible una cosa sin recurrir a Bash.
JamesTheAwesomeDude
Estaba buscando el efecto contrario (expandir a otra cosa si comienza como nulo). Resulta que esta lógica se puede invertir cambiando el + a a - como en: $ {empty_var: -replacement}
Alex Jansen
5

Eso es lo que zshhace por defecto cuando omite las comillas:

some-command $var1 $var2

En realidad, la única razón por la que todavía necesita comillas en zsh alrededor de la expansión de parámetros es para evitar ese comportamiento (la eliminación vacía) ya zshque no tiene los otros problemas que afectan a otros shells cuando no cita las expansiones de parámetros (la división implícita + glob) .

Puede hacer lo mismo con otros shells similares a POSIX si deshabilita split y glob:

(IFS=; set -o noglob; some-command $var1 $var2)

Ahora, argumentaría que si su variable puede tener un valor 0 o 1, debería ser una matriz y no una variable escalar y usar:

some-command "${var1[@]}" "${var2[@]}"

Y use var1=(value)cuándo var1debe contener un valor, var1=('')cuándo debe contener un valor vacío y var1=()cuándo no debe contener ningún valor.

Stéphane Chazelas
fuente
0

Me encontré con esto usando rsync en un script bash que inició el comando con o sin -nalternar para ejecutar en seco. Resulta que rsync y varios comandos gnu toman ''como un primer argumento válido y actúan de manera diferente que si no estuvieran allí.

Esto llevó bastante tiempo a la depuración ya que los parámetros nulos son casi completamente invisibles.

Alguien en la lista de rsync me mostró una forma de evitar este problema al tiempo que simplificaba enormemente mi codificación. Si lo entiendo correctamente, esta es una variación de la última sugerencia de @ Stéphane Chazelas.

Construya sus argumentos de comando en varias variables separadas. Estos se pueden configurar en cualquier orden o lógica que se adapte al problema.

Luego, al final, use las variables para construir una matriz con todo en su lugar apropiado y use eso como argumentos para el comando real.

De esta forma, el comando solo se emite en un lugar del código en lugar de repetirse para cada variación de los argumentos.

Cualquier variable que esté vacía simplemente desaparece con este método.

Sé que usar eval está muy mal visto. No recuerdo todos los detalles, pero parecía necesitarlo para que las cosas funcionen de esta manera, algo que ver con el manejo de parámetros con espacios en blanco incrustados.

Ejemplo:

dry_run=''
if [[ it is a test run ]]
then
  dry_run='-n'
fi
...
rsync_options=(
  ${dry_run}
  -avushi
  ${delete}
  ${excludes}
  --stats
  --progress
)
...
eval rsync "${rsync_options[@]}" ...
Joe
fuente
Esa es una forma peor de hacer lo que describí en la pregunta (construir una serie de argumentos condicionalmente). Al dejar las variables sin comillas, quién sabe a qué problemas te estás abriendo.
muru
@muru Tienes razón, pero todavía necesito algo como esto. No veo cómo solucionarlo usando las técnicas de las otras respuestas. Sería genial ver un fragmento de código hecho de la manera correcta que lo reúna todo. Voy a jugar con la última opción de Stephane más tarde, ya que eso podría hacer lo que quiero.
Joe
¿Te gusta paste.ubuntu.com/26383847 ?
muru
Acabo de volver a esto. Gracias por el ejemplo Que tiene sentido. Voy a trabajar con eso.
Joe
Tengo un caso de uso similar aquí, pero podría hacer algo como esto: if ["$ dry" == "true"]; entonces seco = "- n"; fi ... así que si la variable dry es verdadera, se hace -n, y luego construye su comando rsync como de costumbre y lo tiene así: rsync $ {dry1: + "$ dry1"} ... si es nulo nada sucederá, de lo contrario se convertirá en -n bajo su mando
Freedo