Dlaczego sprawdzanie, czy folder istnieje za pomocą opcji -d na pustym łańcuchu zwraca wartość true?

8

Pisałem kilka scenariuszy i napisałem coś podobnego

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

Stało się tak, że z głupoty wykonałem drugą linię bez wykonania pierwszej. Okazało się, że [-d ""] zwraca true, a wyrażenie się stało

rm -rf /*

Na szczęście była to tylko maszyna testowa i nie byłem sudo, ale straciłem trochę danych

Moje pytanie brzmi: dlaczego [-d ""] zwraca true ?? dokumentacja wyraźnie stwierdza, że ​​sprawdza, czy ścieżka istnieje i czy jest folderem

Rozwiązałem problem za pomocą

[ -e $ARTIFACTS ]
który wydaje się działać

Twoje zdrowie

Moataz Elmasry
źródło
5
A może wykonałeś obie linie. W powyższym przykładzie kodu nigdy nie ustawiasz ARTIFCATS.
Buhb,
2
Po prostu napisałbym to tak, jak rm -rf $ARTIFACTSbez /*. Spowoduje to również usunięcie $ARTIFACTSkatalogu, co jest w porządku, ponieważ jeśli chcę mieć pewność, że istnieje przed włożeniem czegoś do niego, i mkdir -p $ARTIFACTStak wykonam . Usunie również ukryte pliki w środku $ARTIFACTS, co również jest w porządku, ponieważ nie pisałbym, rm -rf $ARTIFACTS/*gdyby $ARTIFACTSzawierał coś, co chciałem zapisać.
Christoffer Hammarström
@ ChristofferHammarström bardzo prawdziwa
Moataz Elmasry

Odpowiedzi:

9

1. Te dwa testy zwracają wartość true :

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

zgodnie z POSIX (jak wyjaśniono @Tim).

2. Ale ta powraca fałszywa ( nie prawda, jak wskazano w pytaniu)

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

ponieważ testjest wywoływany z dwoma argumentami (chociaż drugi jest pustym ciągiem).

3. Dlatego dobrą praktyką jest używanie [[ … ]]zamiast test( [ … ]), które zapewnia większość (wszystkich?) Obecnych powłok. Ta konstrukcja sprawdza, czy podasz wystarczającą liczbę argumentów (w przeciwnym razie generuje błąd i przerywa)

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

lub po prostu zachowuje się tak, jak można się spodziewać:

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

4. I, jak wskazał @Gilles, jeszcze ważniejsze jest podwójne cytowanie podstawień. Więc -d "$SOME_UNSET_VAR"rozwija się -d ""i zwraca false nawet przy test(równa się przypadkowi 2). Dlatego jest to również zgodne z powłoką Bourne'a sh:

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

testowane przy użyciu bash 3.00.16 (1) i 4.1.5 (1)

mpy
źródło
1
Bash, ksh i zsh zapewniają, [[ … ]]ale nie zwykły sh. Naprawdę ważne praktyką jest umieścić w cudzysłowie podstawień komend : [ -d "$ARTIFACTS" ].
Gilles 'SO - przestań być zły'
6

Ta kwestia została już odpowiedział na StackOverflow. Mówi, że zgodnie ze standardem POSIX testpowinien zawsze zwracać sukces, jeśli jest wywoływany z dokładnie jednym niepustym argumentem (i bez innych argumentów).

Tak też powinno być w przypadku test -e(i tak naprawdę jest w moim systemie), więc bądź ostrożny.

Zamiast tego użyj:

[ -d "$ARTIFACTS" ]

test zostanie następnie wywołany z dwoma argumentami, nawet jeśli zmienna jest pusta i w tym przypadku zwróci false.

Tim
źródło
„dokładnie jeden niepusty argument”. - jest to wprowadzające w błąd streszczenie - strona, do której prowadzi łącze, jest wywoływana z dokładnie jednym argumentem, a argument ten jest niepusty; nic o przypadku niepustego argumentu, po którym następuje pusty argument. Prawidłowym rozwiązaniem powinno być otaczanie nazwy zmiennej w cudzysłowie.
Random832
@ Random832 Dziękujemy! Wdrożyłem obie Twoje sugerowane zmiany.
Tim
[ ! -z $ARTIFACTS ] && [ -d $ARTIFACTS ]nie jest lepszy: obsługuje tylko konkretny przypadek, gdy $ARTIFACTSjest pusty, ale zawodzi, gdy $ARTIFACTSmoże zawierać białe znaki lub \[?*. [ -d "$ARTIFACTS" ]jest właściwym sposobem na zrobienie tego (lub [[ -d $ARTIFACTS ]]w powłokach, które mają [[ … ]]).
Gilles 'SO - przestań być zły'
@Gilles Masz rację, usunąłem go. Dziękuję!
Tim
4

Rozwiązałem problem, używając [-e $ ARTIFACTS], która wydaje się działać

Jesteś zły . Działa, ponieważ $ARTIFACTSjest teraz ustawiony na coś.

Gdy zmienna nie jest ustawiona, wtedy powiedz

[ -d $SOMEVAR ]

lub

[ -e $SOMEVAR ]

będzie zarówno ocenia się true, ponieważ implikuje mówiąc

[ -d ]

i

[ -e ]

odpowiednio. (Mówienie [ foobar ]zawsze ocenia się jako prawdziwe).

Powiedzenie

set -u

przydaje się w takich sytuacjach. help setpowiedziałbym ci:

  -u  Treat unset variables as an error when substituting.
diabelnie
źródło
[-e ”„] i & echo „DRUKUJ COŚ” nic nie drukuje, więc jak to może być źle?
Moataz Elmasry
2
ahh bzdury [-e $ ARTIFACTS] z pustymi artefaktami daje [-e] nie [-e ""],
rozumiem
4

Sprawdź, czy ustawiłeś zmienną ARTIFACTS i sprawdzałeś ARTIFCATS. Prawdopodobnie źle wpisujesz?

W każdym razie, -d oraz -e dawałyby takie same wyniki dla zmiennych nieustawionych.

Dlatego używaj podwójnych cudzysłowów, a to ci pomoże.

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

UWAGA: Jeśli twój „/ SOME / PATH” ma jakiś folder ze spacją, skrypt, o którym wspomniałeś, zepsuje się z błędem „oczekiwany operator binarny”.

Przykład:

ARTIFACTS="/home backup/"

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

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

da sobie radę. Nie zapomnij również wstawić cytatów w rmwywołaniu ( rm -rf $ARTIFACTSchętnie usunę, /homea następnie narzekasz na backup/*nieistnienie).

Również włączenie -Lsprawdzania sprawi, że będzie to katalog, a nie tylko symboliczne łącze do katalogu.

Zasadniczo

[ -d "$ARTIFACTS" && ! -L "$ARTIFACTS" ] && rm -rf -- "$ARTIFACTS"/*
thiruvenkadam
źródło