Błąd składniowy Bash, gdy „else” występuje po pustej klauzuli „then”

36

Dlaczego poniższy skrypt nie zostałby wykonany, ale dał błąd składniowy else:

LOGS3_DIR=~/logs
if [ -d "$LOGS3_DIR" ]; then
 cd
 cd "$LOGS3_DIR"
 echo "$LOGS3_DIR"
 for filename in `find "." -mtime 1 -type f`
  do
  if lsof "$filename" > /dev/null
  then
    # file is open
  else
    echo "deleting $filename"
    rm "$filename"
  fi
 done
fi
Początkujący użytkownik
źródło

Odpowiedzi:

23

Nie używaj podstawiania poleceń na wyjściufind . Tutaj wszystko można zrobić za pomocą find:

find . -mtime 1 -type f ! -exec lsof -t {} \; -exec rm -f {} \; > /dev/null

Dzięki kilku findimplementacjom (w tym FreeBSD, findskąd pochodzi i GNU find), możesz użyć -deletezamiast -exec rm....

Powodem, dla którego pojawia się błąd, jest to, że nie ma polecenia pomiędzy theni elseniektóre powłoki (zaczynając od powłoki Bourne'a, z której pochodzi ta składnia) wymagają co najmniej jednej (a komentarz nie jest poleceniem). Zauważ, że jest to całkowicie arbitralne i nie ma powodu, dla którego te powłoki miałyby to robić. yashi zshnie mają tego ograniczenia ( if false; then else echo x; fia nawet if false; then else fidziałają z nimi dobrze).

Jak powiedzieli inni, możesz użyć polecenia noop, takiego jak :(lub for nothing in; do nothing; done), lub odwrócić logikę za pomocą !słowa kluczowego (dostępne w powłokach POSIX, ale nie w powłoce Bourne'a (przekonasz się, że używanie :tego było powszechne w tej powłoce)). mkshi yashzdarzyło się wspierać if false; then () else echo x; fi(nie polegałbym na tym, ponieważ mogłoby to się zmienić w przyszłych wersjach).

Inne podejście polega na:

lsof... || {
  cmd1
  cmd2
}

choć jedną różnicą jest ogólny status wyjścia, który będzie, lsofjeśli się lsofnie powiedzie.

Stéphane Chazelas
źródło
17
Chociaż jest to znacznie lepszy sposób na zrobienie tego, co próbuje @Novice User, w ogóle nie odpowiada na pytanie.
PatrzJayBee
Chociaż -execjest często przydatny, xargsczasem jest potrzebna pętla powłoki. W takim przypadku while read namepętla jest preferowaną opcją (w bash z GNU find możesz użyć opcji -0 dla obu; przenośnie musisz zrezygnować z nowej linii).
Jan Hudec
@JHHec, istnieją sposoby przenośne. -print0jest -exec printf '%s\0' {} +(ale przenośnie nie możesz sobie poradzić z tymi danymi wyjściowymi, chyba że chcesz wziąć pod uwagę perl), a za pomocą find .//.i przetwarzania końcowego możesz uciec od nowych linii dla xargs. Zauważ, że to nie while readjest while IFS= read -r.
Stéphane Chazelas,
@ Chris, dodałem odpowiedź do rzeczywistego pytania, odkąd odpowiedź została zaakceptowana.
Stéphane Chazelas
91

Wygląda na to, że chcesz zrobić brak operacji, jeśli plik jest otwarty, więc powinieneś dodać polecenie :, które jest zerowym poleceniem w bash:

if lsof "$filename" > /dev/null; then
  # file is open
  :
else
  printf 'deleting %s\n' "$filename"
  rm -- "$filename"
fi

Jeśli nie używasz :, bashnie możesz przeanalizować kodu i wyświetli się błąd bash: syntax error near unexpected token 'else'.

Cuonglm
źródło
nigdy nie jest nowy :i jest to pierwsze polecenie wymienione w wbudowanych pakietach bash.
bolov
26

Inna alternatywa: odwróć swoją logikę.

if ! lsof "$filename" >/dev/null;then
    echo "deleting $filename"
    rm "$filename"
fi
Joseph R.
źródło
17

TL; DR

Żadna z pozostałych odpowiedzi nie odpowiada na pierwotne pytanie, dlaczego polecenie powoduje błąd składniowy. Jest to spowodowane brakiem polecenia między tym a innym .

Brakujące polecenie

Twój oryginalny kod wygląda następująco:

if lsof "$filename" > /dev/null
then
  # file is open
else
  echo "deleting $filename"
  rm "$filename"
fi

Problem polega na tym, że trzeba komentarza między wtedy a inny , ale uwaga nie jest traktowany jako polecenia. Krótko mówiąc, możesz przepisać problem (strukturalnie) w następujący sposób:

$ if true; then else echo; fi
bash: syntax error near unexpected token `else'

Napraw swoją składnię za pomocą wbudowanego Bourne'a

Można rozwiązać ten problem poprzez umieszczenie rzeczywiste polecenia przed indziej , ale komentarz sama nie zrobi. Sekcja jeśli-to nie może być pusta; jeśli chcesz symbol zastępczy, możesz użyć wbudowanego dwukropka . Na przykład:

$ if true; then :; else echo; fi

Po prostu umieszczenie :w sekcji pomiędzy tym a innym razem naprawi występujący błąd składni.

CodeGnome
źródło
1
Odpowiedź Gnouca, która jest również najbardziej uprzywilejowana, odnosi się już do pierwotnego pytania.
jlliagre
Odpowiedz tylko, aby rozwiązać błąd składniowy. FWIW, możesz odtworzyć podobny błąd z pojedynczym średnikiem na początku linii. To da mocną wskazówkę. $ ; -bash: syntax error near unexpected token ';'
Matthew Hannigan