Há algum mal em usar variáveis que não estão definidas?

6

Digamos que eu tenha o seguinte código:

# Check if the color prompt is enabled and supported on this system
if [ -n "$force_color_prompt" ] && [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
    GREEN="3[1;32m"
    DIM="3[2m"
    RESET="3[00m"
fi

echo -e "Oh, ${GREEN}green${RESET} world, ${DIM}don't desert me now...${RESET}"

Se o suporte de cores estiver ativado, ele ecoará uma linha bonita e colorida. Se o suporte de cores não estiver ativado, valores como ${GREEN} não serão definidos e o texto será impresso no branco usual com fundo preto.

O código se baseia no fato de que variáveis que não são definidas simplesmente serão avaliadas como uma string vazia (o que, em meus testes, elas fazem). Isso causará bugs ou problemas em alguns sistemas, ou todas as variáveis inexistentes sempre serão avaliadas como uma string vazia? Existe alguma razão para eu não confiar neste mecânico?

    
por IQAndreas 23.06.2014 / 16:47

3 respostas

7

Variáveis inexistentes sempre serão avaliadas como uma string vazia quando expandidas como $FOO ou (equivalentemente) ${FOO} , e não há problema em depender disso, exceto em um caso específico:

Se alguém chamou set -u no shell atual antes de tentar usar essa variável, eles ativaram essa configuração:

              -u      Treat unset variables as an error when performing param-
                      eter  expansion.   If expansion is attempted on an unset
                      variable, the shell prints an error message, and, if not
                      interactive, exits with a non-zero status.

Isso significa que, se você estiver escrevendo uma função projetada para ser originada em um script controlado por outra pessoa, talvez seja necessário paranóico quanto ao uso de variáveis não definidas. Caso contrário, use set -u antes de Ao chamar sua função, o script sairia com uma mensagem de erro na primeira vez que você tentasse expandir uma variável não definida.

Se você está escrevendo seu próprio script, não há mal nenhum em contar com variáveis não definidas expandindo para a string vazia.

EDITAR - Além disso, apenas um pensamento - já que você está tornando a coisa toda condicional se os recursos de cor terminfo estão disponíveis para o seu terminal, por que não usar o terminfo para gerar as sequências, em vez de codificar os valores vt100? Algo como:

if [ -n "$force_color_prompt" ] && type tput &>/dev/null; then
    GREEN="$(tput setaf 2)$(tput bold)"
    DIM="$(tput dim)"
    RESET="$(tput sgr0)"
fi

Isso pode ganhar alguma portabilidade em outros terminais (embora, reconhecidamente, o número de terminais que não usam os códigos mostrados seja pequeno e encolhimento). Também pode perder alguma portabilidade, pois alguns recursos podem não existir em algumas plataformas, dependendo da correção das definições do terminfo. YMMV.

    
por 23.06.2014 / 16:56
1

Um dos recursos mais exclusivos da linguagem de script shell compatível com POSIX é expansão de parâmetro . Ele pode ser usado de várias maneiras para realizar tarefas que não são comumente associadas a valores de variáveis. No shell, uma variável tem o potencial de ser mais do que apenas um valor - pode ser um item acionável. Tem o potencial para se testar. E isso vem explicitamente - sem necessidade de definir opções de shell.

Por exemplo, seu código poderia ser semelhante:

N= ERR='error encountered - exiting' 
: ${force_color_prompt?"$ERR"}
/usr/bin/tput setaf >/dev/null 2>&1 || ${N:?"$ERR"}
: "${GREEN:=3[1;32m}" "${DIM:=3[2m}" "${RESET:=3[00m}"
printf %b\n \
    "Oh, ${GREEN}green${RESET} world, ${DIM}don't desert me now...${RESET}"

A variável $N é definida explicitamente para a cadeia nula e, portanto, quando é avaliada com a forma ${N:?} da expansão de parâmetro, seu shell pai é automaticamente encerrado e a instrução após ? é avaliada para expansão bem, os resultados são produzidos em stderr . O mesmo vale para $force_color_prompt - se não estiver definido, o script sairá com um erro e gerará $ERR para stderr - todos automaticamente.

Os $GREEN $RESET e $DIM são definidos para os valores que você definiu, caso eles não estejam atualmente definidos ou sejam definidos como a string '' null. Isso permite que você passe seus valores para o script como variáveis de ambiente. Por exemplo, se o snippet acima estivesse em um script chamado greenworld.sh e eu o chamasse assim:

GREEN="$(tput setaf 2)$(tput bold)" greenworld.sh

Em seguida, $GREEN não seria redefinido no conteúdo do script e, em vez disso, herdaria o valor explícito que defini para ele. Isso torna os scripts de shell flexíveis.

E usar tput dessa maneira, como godlygeek recomendou, é uma recomendação eu, por um segundo.

No shell, uma variável não definida pode às vezes ser tão útil quanto um conjunto. Aqui está um exemplo diferente:

set -- * 
while ${1+:} false ; do
    #do stuff until all positionals are shifted away
shift ; done

Nesse exemplo, desde que o primeiro parâmetro seja definido, ele será expandido para o co-on% null, que, consequentemente, renderizará a seguinte invocação de : como no-op. Mas assim que todos os parâmetros posicionais forem false ed away, shift não se expandirá dessa maneira, e ${1} será invocado, e o loop false será encerrado. Você pode fazer inúmeras variações sobre isso.

    
por 24.06.2014 / 05:27
-1

No uso comum, ou seja, quando você está apenas expandindo variáveis, uma variável não definida é tratada como vazia em todos os shells do tipo Bourne / POSIX. A exceção é quando set -o unset aka set -u está em vigor e, nesse caso, o shell gerará um erro se você tentar acessar o valor de uma variável não definida (consulte resposta de godlygeek ).

Existem maneiras de testar se uma variável não está definida ou está vazia; por exemplo, a construção ${foo-bar} se expande para o valor de foo se estiver definido (mesmo se estiver vazio) e para bar se foo não estiver definido.
${foo:-bar} (com um dois pontos extras) trata uma variável vazia como se fosse não definida. Outras construções de expansão semelhantes ${foo+bar} , ${foo?bar} , ${foo=bar} se comportam de maneira semelhante. Uma variável vazia também aparecerá na saída de set e export (se exportada), entre outras diferenças. Você só vai encontrar essas diferenças se quiser.

No entanto, existe uma razão diferente para sempre inicializar as variáveis : como você sabe que a variável está realmente indefinida? O chamador pode ter definido uma variável semelhante para seus próprios propósitos. Se o chamador for alguma outra parte do mesmo script, seu uso em uma função sobrescreverá o chamador, a menos que você declare a variável como local (o que só é possível em ksh / bash / zsh), então conflitos de nome de variável são um problema de qualquer forma. Mas também pode acontecer que a variável esteja presente no ambiente, porque alguém escolheu o mesmo nome de variável que você. Existe um tipo de convenção para usar nomes de letras minúsculas para variáveis de shell e nomes de letras maiúsculas para variáveis de ambiente, mas isso não resolve todos os conflitos e não é seguido universalmente.

$ export DIM=1 SUM=42
$ bash
Oh, green world, 1don't desert me now...
    
por 24.06.2014 / 03:18