Por que verificar se uma pasta existe usando -d em uma string vazia retorna true?

8

Eu estava escrevendo alguns scripts e escrevi algo como

ARTIFACTS="/SOME/PATH"
[ -d $ARTIFCATS ] && rm -rf $ARTIFACTS/*

O que aconteceu é que, por estupidez, executei a segunda linha sem executar a primeira. Descobriu-se que [-d ""] retorna true e a expressão se tornou

rm -rf /*

Por sorte, era apenas uma máquina de teste e eu não era um sudo, mas perdi alguns dados

Minha pergunta é, por que [-d ""] retorna verdadeiro ?? a documentação afirma claramente que verifica se um caminho existe e é uma pasta

Eu resolvi o problema usando

[ -e $ARTIFACTS ]
o que parece funcionar

Felicidades

    
por Moataz Elmasry 08.10.2013 / 12:56

4 respostas

9

1. Estes dois testes retornam verdadeiro :

# [ -d ] && echo true || echo false
true
# [ -d $SOME_UNSET_VAR ] && echo true || echo false
true

de acordo com POSIX (como explicado por @Tim).

2. Mas isso retorna falso ( não true como indicado na pergunta)

# [ -d "" ] && echo true || echo false
false

porque test é chamado com dois argumentos (embora o segundo seja uma string vazia).

3. É por isso que é uma boa prática usar [[ … ]] em vez de test ( [ … ] ), que a maioria dos (todos?) Shells atuais fornecem. Esta construção verifica se você fornece argumentos suficientes (caso contrário, lança um erro e aborta)

# [[ -d ]] && echo true || echo false
bash: unexpected argument ']]' to conditional unary operator
bash: syntax error near ']]'

ou simplesmente se comporta como se esperaria:

# [[ -d $SOME_UNSET_VAR ]] && echo true || echo false
false

4. E, como apontado por @Gilles, ainda mais importante é a duplicação das substituições. Portanto, -d "$SOME_UNSET_VAR" expande para -d "" e retorna falso mesmo com test (igual ao caso 2). Portanto, isso também é compatível com o shell Bourne sh :

# [ -d "$SOME_UNSET_VAR" ] && echo true || echo false
false

testado com bash 3.00.16 (1) e 4.1.5 (1)

    
por 08.10.2013 / 13:31
6

Esta questão já foi respondida no StackOverflow. Ele diz que, de acordo com o padrão POSIX, test sempre deve retornar com êxito se for chamado com exatamente um argumento não vazio (e nenhum outro argumento).

Este também deve ser o caso de test -e (e, de fato, está no meu sistema), então tenha cuidado.

Em vez disso, use:

[ -d "$ARTIFACTS" ]

test será então chamado com dois argumentos, mesmo que a variável esteja vazia e retorne false neste caso.

    
por 08.10.2013 / 13:09
4

I solved the problem by using [ -e $ARTIFACTS ] which seems to work

Você está errado . Funciona porque $ARTIFACTS está agora definido para algo.

Quando uma variável não está definida, então diz

[ -d $SOMEVAR ]

ou

[ -e $SOMEVAR ]

ambos avaliam para true porque isso implica dizer

[ -d ]

e

[ -e ]

respectivamente. (Dizer que [ foobar ] seria sempre avaliado como verdadeiro.)

Dizendo

set -u

vem a calhar em tais situações. help set diria a você:

  -u  Treat unset variables as an error when substituting.
    
por 08.10.2013 / 13:09
4

Veja que você definiu a variável ARTIFACTS e estava verificando ARTIFCATS. Provavelmente erro de digitação?

De qualquer forma, -d e -e produziriam os mesmos resultados em variáveis não definidas.

Por isso, use aspas duplas e isso ajudará você.

ARTIFACTS="/SOME/PATH"
[ -d "$ARTIFACTS" ] && rm -rf -- "$ARTIFACTS/"*

NOTA: Se o seu "/ SOME / PATH" tiver qualquer pasta com espaço, o script que você mencionou romperá com o erro "operador binário esperado".

Exemplo:

ARTIFACTS="/home backup/"

1) [ -d $ARTIFACTS ] && rm -rf $ARTIFACTS/*
bash: [: /home: binary operator expected

2) [ -d "$ARTIFACTS" ] && rm -rf "$ARTIFACTS"/*

vai fazer bem. Não se esqueça de colocar aspas na chamada rm também ( rm -rf $ARTIFACTS iria remover alegremente /home e reclamar sobre backup/* não existente).

Além disso, incluir -L check garantirá que seja um diretório e não apenas um link simbólico para um diretório.

Então, basicamente,

[ -d "$ARTIFACTS" && ! -L "$ARTIFACTS" ] && rm -rf -- "$ARTIFACTS"/*
    
por 08.10.2013 / 16:34

Tags