Usando "$ {a: -b}" para atribuição de variável em scripts

427

Eu estive observando alguns scripts que outras pessoas escreveram (especificamente Red Hat), e muitas de suas variáveis ​​são atribuídas usando a seguinte notação VARIABLE1="${VARIABLE1:-some_val}" ou algumas outras variáveis ​​de expansão VARIABLE2="${VARIABLE2:-`echo $VARIABLE1`}"

Qual é o ponto de usar esta notação em vez de apenas declarar os valores diretamente (por exemplo, VARIABLE1=some_val)?

Existem benefícios para esta notação ou possíveis erros que seriam evitados?

Isso :-tem significado específico nesse contexto?

Rothgar
fonte
Ter um olhar para man bash; procure o bloco "Expansão de parâmetros" (em cerca de 28%). Essas atribuições são, por exemplo, funções padrão: "Use o valor padrão apenas se ainda não houver nenhum valor definido".
Hauke ​​Laging

Respostas:

698

Essa técnica permite que uma variável receba um valor se outra variável estiver vazia ou indefinida. NOTA: Essa "outra variável" pode ser a mesma ou outra variável.

excerto

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

NOTA: Este formulário também funciona ${parameter-word},. Se você quiser ver uma lista completa de todas as formas de expansão de parâmetros disponíveis no Bash, sugiro que você dê uma olhada neste tópico no wiki do Bash Hacker intitulado: " Expansão de parâmetros ".

Exemplos

variável não existe
$ echo "$VAR1"

$ VAR1="${VAR1:-default value}"
$ echo "$VAR1"
default value
variável existe
$ VAR1="has value"
$ echo "$VAR1"
has value

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

O mesmo pode ser feito avaliando outras variáveis ​​ou executando comandos na parte do valor padrão da notação.

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

$

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

Mais exemplos

Você também pode usar uma notação ligeiramente diferente, onde é justa VARX=${VARX-<def. value>}.

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

No exemplo acima $VAR1e $VAR2já foram definidas com a string "tem outro valor", mas $VAR3era indefinido, de modo que o valor padrão foi usado em vez, 0.

Outro exemplo

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

Verificando e atribuindo usando :=notação

Por fim, mencionarei o operador prático :=,. Isso fará uma verificação e atribuirá um valor se a variável em teste estiver vazia ou indefinida.

Exemplo

Observe que $VAR1agora está definido. O operador :=fez o teste e a atribuição em uma única operação.

$ unset VAR1
$ echo "$VAR1"

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

No entanto, se o valor for definido anteriormente, será deixado sozinho.

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

Tabela de referência do Handy Dandy

    ss da tabela

Referências

slm
fonte
8
Observe que nem todas essas expansões estão documentadas bash. O ${var:-word}do Q é, mas não o ${var-word}acima. A documentação POSIX tem uma bela mesa, porém, pode valer a pena copiá-lo para esta resposta - pubs.opengroup.org/onlinepubs/9699919799/utilities/...
Graeme
5
@ Graeme, está documentado, você só precisa prestar atenção ao Omitir os resultados do cólon em um teste apenas para um parâmetro não definido.
Stéphane Chazelas
7
Usar echo "${FOO:=default}"é ótimo se você realmente deseja o echo. Mas se não, tente o :built-in ... : ${FOO:=default} Seu $FOOestá definido defaultcomo acima (ou seja, se ainda não estiver definido). Mas não há eco $FOOno processo.
Fbicknel
2
Ok, encontrei uma resposta: stackoverflow.com/q/24405606/1172302 . Usando o ${4:-$VAR}irá funcionar.
Nikos Alexandris
1
Aqui, marque +1 para chegar a 500. Parabéns! :)
XtraSimplicity
17

O @slm já incluiu os documentos POSIX - que são muito úteis -, mas eles não expandem realmente como esses parâmetros podem ser combinados para afetar um ao outro. Ainda não há menção aqui deste formulário:

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

Este é um trecho de outra resposta minha, e acho que demonstra muito bem como isso funciona:

    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?

Outro exemplo do mesmo :

    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

O exemplo acima aproveita todas as quatro formas de substituição de parâmetro POSIX e seus vários :colon nullou not nulltestes. Há mais informações no link acima e aqui está novamente .

Outra coisa que as pessoas geralmente não consideram ${parameter:+expansion}é o quão útil pode ser em um documento aqui. Aqui está outro trecho de uma resposta diferente :

TOPO

Aqui você definirá alguns padrões e se preparará para imprimi-los quando chamados ...

#!/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

MEIO

É aqui que você define outras funções para chamar sua função de impressão com base em seus 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
    }

INFERIOR

Agora você tem tudo configurado, então aqui é onde você executa e obtém seus resultados.

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

RESULTADOS

Vou explicar o porquê daqui a pouco, mas a execução do procedimento acima produz os seguintes resultados:

_less_important_function()'s Primeira corrida:

Fui à casa de sua mãe e vi Disney on Ice.

Se você fizer caligrafia você terá sucesso.

então _more_important_function():

Fui ao cemitério e vi Disney on Ice.

Se você praticar matemática corretiva, terá sucesso.

_less_important_function() novamente:

Fui ao cemitério e vi Disney on Ice.

Se você pratica matemática corretiva, vai se arrepender.

COMO FUNCIONA:

O principal recurso aqui é o conceito de conditional ${parameter} expansion.Você pode definir uma variável para um valor somente se estiver desconfigurada ou nula usando o formulário:

${var_name: =desired_value}

Se, em vez disso, você desejar definir apenas uma variável não configurada, você omitirá os :colonvalores nulos e permanecerá como está.

NO ÂMBITO DE APLICAÇÃO:

Você pode perceber isso no exemplo acima $PLACEe $RESULTser alterado quando definido via parameter expansionmesmo que _top_of_script_pr()já tenha sido chamado, presumivelmente configurando-os quando é executado. A razão pela qual isso funciona é que _top_of_script_pr()é uma ( subshelled )função - eu a incluí em parensvez da { curly braces }usada para as outras. Como é chamado em um subshell, todas as variáveis ​​que ele define são locally scopede, quando retornam ao shell pai, esses valores desaparecem.

Mas quando _more_important_function()conjuntos $ACTION, globally scopedisso afeta a _less_important_function()'ssegunda avaliação, $ACTIONporque _less_important_function()conjuntos $ACTIONsomente via${parameter:=expansion}.

mikeserv
fonte
1
É bom ver que o valor abreviada padrão funciona no Bourne Shell
Sridhar Sarnobat
8

Experiência pessoal.

Às vezes, uso esse formato nos meus scripts para fazer uma substituição ad-hoc de valores, por exemplo, se eu tiver:

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

Eu posso correr:

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

sem precisar alterar o valor padrão original de SOMETHING.

hjk
fonte