Usar "$ {a: -b}" para la asignación de variables en scripts

427

He estado mirando algunos scripts que otras personas escribieron (específicamente Red Hat), y muchas de sus variables se asignan usando la siguiente notación VARIABLE1="${VARIABLE1:-some_val}" o algunas expanden otras variables VARIABLE2="${VARIABLE2:-`echo $VARIABLE1`}"

¿Cuál es el punto de usar esta notación en lugar de declarar los valores directamente (por ejemplo, VARIABLE1=some_val)?

¿Hay beneficios para esta notación o posibles errores que podrían evitarse?

¿Tiene el :-significado específico en este contexto?

Rothgar
fuente
Echa un vistazo a man bash; busque el bloque "Expansión de parámetros" (aproximadamente el 28%). Estas asignaciones son, por ejemplo, funciones predeterminadas: "Use el valor predeterminado solo si aún no se ha configurado ninguno".
Hauke ​​Laging

Respuestas:

698

Esta técnica permite que a una variable se le asigne un valor si otra variable está vacía o no está definida. NOTA: Esta "otra variable" puede ser la misma u otra variable.

extracto

${parameter:-word}
    If parameter is unset or null, the expansion of word is substituted. 
    Otherwise, the value of parameter is substituted.

NOTA: este formulario también funciona ${parameter-word},. Si desea ver una lista completa de todas las formas de expansión de parámetros disponibles en Bash, le sugiero que eche un vistazo a este tema en la wiki de Bash Hacker titulada: " Expansión de parámetros ".

Ejemplos

la variable no existe
$ echo "$VAR1"

$ VAR1="${VAR1:-default value}"
$ echo "$VAR1"
default value
la variable existe
$ VAR1="has value"
$ echo "$VAR1"
has value

$ VAR1="${VAR1:-default value}"
$ echo "$VAR1"
has value

Lo mismo se puede hacer evaluando otras variables o ejecutando comandos dentro de la parte del valor predeterminado de la notación.

$ VAR2="has another value"
$ echo "$VAR2"
has another value
$ echo "$VAR1"

$

$ VAR1="${VAR1:-$VAR2}"
$ echo "$VAR1"
has another value

Más ejemplos

También puede usar una notación ligeramente diferente donde es justo VARX=${VARX-<def. value>}.

$ echo "${VAR1-0}"
has another value
$ echo "${VAR2-0}"
has another value
$ echo "${VAR3-0}"
0

En lo anterior $VAR1y $VAR2ya estaban definidos por la cadena "tiene otro valor", pero $VAR3no estaba definido, por lo que se utilizó el valor predeterminado en su lugar, 0.

Otro ejemplo

$ VARX="${VAR3-0}"
$ echo "$VARX"
0

Comprobación y asignación mediante :=notación

Por último voy a mencionar el operador práctico, :=. Esto hará una verificación y asignará un valor si la variable bajo prueba está vacía o indefinida.

Ejemplo

Observe que $VAR1ahora está configurado. El operador :=realizó la prueba y la asignación en una sola operación.

$ unset VAR1
$ echo "$VAR1"

$ echo "${VAR1:=default}"
default
$ echo "$VAR1"
default

Sin embargo, si el valor se establece antes, entonces se deja solo.

$ VAR1="some value"
$ echo "${VAR1:=default}"
some value
$ echo "$VAR1"
some value

Handy Dandy Reference Table

    ss de mesa

Referencias

slm
fuente
8
Tenga en cuenta que no todas estas expansiones están documentadas en bash. El ${var:-word}de la Q es, pero no el ${var-word}anterior. Sin embargo, la documentación POSIX tiene una buena tabla, podría valer la pena copiarla en esta respuesta - pubs.opengroup.org/onlinepubs/9699919799/utilities/…
Graeme
55
@Graeme, está documentado, solo debe prestar atención a los resultados de Omitir el colon en una prueba solo para un parámetro que no está configurado.
Stéphane Chazelas
77
Usar echo "${FOO:=default}"es genial si realmente quieres el echo. Pero si no lo hace, intente con el :incorporado ... : ${FOO:=default} Su $FOOestá configurado defaultcomo arriba (es decir, si no está configurado). Pero no hay eco $FOOen el proceso.
fbicknel
2
Ok, encontré una respuesta: stackoverflow.com/q/24405606/1172302 . Usar el ${4:-$VAR}will funcionará.
Nikos Alexandris
1
Aquí, tiene un +1 para llevarte a 500. ¡Felicidades! :)
XtraSimplicity
17

@slm ya ha incluido los documentos POSIX , que son muy útiles, pero en realidad no amplían la forma en que estos parámetros se pueden combinar para afectarse entre sí. Todavía no hay ninguna mención aquí de este formulario:

${var?if unset parent shell dies and this message is output to stderr}

Este es un extracto de otra respuesta mía, y creo que demuestra muy bien cómo funcionan:

    sh <<-\CMD
    _input_fn() { set -- "$@" #redundant
            echo ${*?WHERES MY DATA?}
            #echo is not necessary though
            shift #sure hope we have more than $1 parameter
            : ${*?WHERES MY DATA?} #: do nothing, gracefully
    }
    _input_fn heres some stuff
    _input_fn one #here
    # shell dies - third try doesnt run
    _input_fn you there?
    # END
    CMD
heres some stuff
one
sh: line :5 *: WHERES MY DATA?

Otro ejemplo de lo mismo :

    sh <<-\CMD
    N= #N is NULL
    _test=$N #_test is also NULL and
    v="something you would rather do without"    
    ( #this subshell dies
        echo "v is ${v+set}: and its value is ${v:+not NULL}"
        echo "So this ${_test:-"\$_test:="} will equal ${_test:="$v"}"
        ${_test:+${N:?so you test for it with a little nesting}}
        echo "sure wish we could do some other things"
    )
    ( #this subshell does some other things 
        unset v #to ensure it is definitely unset
        echo "But here v is ${v-unset}: ${v:+you certainly wont see this}"
        echo "So this ${_test:-"\$_test:="} will equal NULL ${_test:="$v"}"
        ${_test:+${N:?is never substituted}}
        echo "so now we can do some other things" 
    )
    #and even though we set _test and unset v in the subshell
    echo "_test is still ${_test:-"NULL"} and ${v:+"v is still $v"}"
    # END
    CMD
v is set: and its value is not NULL
So this $_test:= will equal something you would rather do without
sh: line 7: N: so you test for it with a little nesting
But here v is unset:
So this $_test:= will equal NULL
so now we can do some other things
_test is still NULL and v is still something you would rather do without

El ejemplo anterior aprovecha las 4 formas de sustitución de parámetros POSIX y sus diversas :colon nullo not nullpruebas. Hay más información en el enlace de arriba, y aquí está de nuevo .

Otra cosa que la gente a menudo no considera ${parameter:+expansion}es cuán útil puede ser en un documento aquí. Aquí hay otro extracto de una respuesta diferente :

PARTE SUPERIOR

Aquí establecerá algunos valores predeterminados y se preparará para imprimirlos cuando se llame ...

#!/bin/sh
    _top_of_script_pr() ( 
        IFS="$nl" ; set -f #only split at newlines and don't expand paths
        printf %s\\n ${strings}
    ) 3<<-TEMPLATES
        ${nl=
}
        ${PLACE:="your mother's house"}
        ${EVENT:="the unspeakable."}
        ${ACTION:="heroin"}
        ${RESULT:="succeed."}
        ${strings:="
            I went to ${PLACE} and saw ${EVENT}
            If you do ${ACTION} you will ${RESULT}
        "}
    #END
    TEMPLATES

MEDIO

Aquí es donde define otras funciones para llamar a su función de impresión en función de sus resultados ...

    EVENT="Disney on Ice."
    _more_important_function() { #...some logic...
        [ $((1+one)) -ne 2 ] && ACTION="remedial mathematics"
            _top_of_script_pr
    }
    _less_important_function() { #...more logic...
        one=2
        : "${ACTION:="calligraphy"}"
        _top_of_script_pr
    }

FONDO

Ya lo tiene todo configurado, así que aquí es donde ejecutará y extraerá sus resultados.

    _less_important_function
    : "${PLACE:="the cemetery"}" 
    _more_important_function
    : "${RESULT:="regret it."}" 
    _less_important_function    

RESULTADOS

Analizaré por qué en un momento, pero ejecutar lo anterior produce los siguientes resultados:

_less_important_function()'s primer intento:

Fui a la casa de tu madre y vi Disney on Ice.

Si haces caligrafía tendrás éxito.

entonces _more_important_function():

Fui al cementerio y vi Disney on Ice.

Si haces matemáticas correctivas tendrás éxito.

_less_important_function() de nuevo:

Fui al cementerio y vi Disney on Ice.

Si haces matemáticas correctivas te arrepentirás.

CÓMO FUNCIONA:

La característica clave aquí es el concepto de conditional ${parameter} expansion.Puede establecer una variable en un valor solo si no está establecida o es nula utilizando el formulario:

${var_name: =desired_value}

Si, en cambio, desea establecer solo una variable no establecida, omitirá los :colonvalores nulos y permanecerá como está.

EN ALCANCE:

Puede notar eso en el ejemplo anterior $PLACEy $RESULTcambiarlo cuando se configura a través de parameter expansionaunque _top_of_script_pr()ya se haya llamado, presumiblemente configurándolos cuando se ejecuta. La razón por la que esto funciona es que _top_of_script_pr()es una ( subshelled )función: la incluí en parenslugar de la { curly braces }utilizada para los demás. Debido a que se llama en una subshell, cada variable que establece es locally scopedy cuando regresa a su shell principal esos valores desaparecen.

Pero cuando se _more_important_function()establece $ACTIONes globally scopedasí, afecta a la _less_important_function()'ssegunda evaluación de $ACTIONporque _less_important_function()establece $ACTIONsolo a través de${parameter:=expansion}.

mikeserv
fuente
1
Es bueno ver que el valor predeterminado de la taquigrafía funciona en Bourne Shell
Sridhar Sarnobat
8

Experiencia personal.

A veces utilizo este formato en mis scripts para anular los valores ad-hoc, por ejemplo, si tengo:

$ cat script.sh
SOMETHING="${SOMETHING:-something}"; echo "$SOMETHING"; 

Puedo correr:

$ env SOMETHING="something other than the default value" ./script.sh` 

sin tener que cambiar el valor predeterminado original de SOMETHING.

hjk
fuente