bash iteruj listę plików, chyba że jest pusta


33

Myślałem, że to będzie proste - ale okazuje się bardziej złożone niż się spodziewałem.

Chcę iterować wszystkie pliki określonego typu w katalogu, więc piszę:

#!/bin/bash

for fname in *.zip ; do
   echo current file is ${fname}
done

Działa to tak długo, jak w katalogu jest co najmniej jeden pasujący plik . Jeśli jednak nie ma pasujących plików, otrzymuję to:

current file is *.zip

Potem spróbowałem:

#!/bin/bash

FILES=`ls *.zip`
for fname in "${FILES}" ; do
    echo current file is ${fname}
done

Chociaż ciało pętli nie wykonuje się, gdy nie ma plików, otrzymuję błąd z ls:

ls: *.zip: No such file or directory

Jak napisać pętlę, która czysto nie obsługuje pasujących plików?


7
Dodaj shopt -s nullglobprzed uruchomieniem pętli for.
cuonglm

@cuolnglm: strasznie powoduje to, że ls zwraca nazwę skryptu wykonawczego zamiast pustej listy na tym polu RHEL5 (bash 3.2.25), jeśli FILES=; for fname in "${FILES}"...for fname in *.zip ; do....
wykonam

3
Użyj for file in *.zip, nie `ls ...`. Sugestia @ cuonglm jest taka, że *.ziprozwija się do zera, gdy wzorzec nie pasuje do żadnego pliku. lsbez argumentów wyświetla bieżący katalog.
Stéphane Chazelas

1
To pytanie omawia, dlaczego na lsogół należy unikać analizowania wyniku : Dlaczego nie parsować ls? ; zobacz także link u góry tej strony do artykułu ParsingLs BashGuide .
PM

Odpowiedzi:


34

W bashmożesz ustawić tę nullglobopcję, aby wzorzec, który nie pasuje do niczego, „znikał”, zamiast traktować go jako ciąg literalny:

shopt -s nullglob
for fname in *.zip ; do
   echo "current file is ${fname}"
done

W skrypcie powłoki POSIX wystarczy sprawdzić, czy fnameistnieje (i jednocześnie [ -f ]sprawdzić, czy jest to zwykły plik (lub dowiązanie symboliczne do zwykłego pliku), a nie inne typy, takie jak katalog / fifo / device ...):

for fname in *.zip; do
    [ -f "$fname" ] || continue
    printf '%s\n' "current file is $fname"
done

Wymień [ -f "$fname" ]się [ -e "$fname" ] || [ -L "$fname ]jeśli chcesz pętli nad wszystkimi (nie-ukryty) pliki, których nazwa kończy się .zipniezależnie od ich rodzaju.

Wymień *.zipsię .*.zip .zip *.zipjeśli również rozważyć ukryte pliki, których nazwa kończy się .zip.


1
shopt -s nullglobnie działało dla mnie na Ubuntu 17.04, ale [ -f "$fname" ] || continuedziałało dobrze.
koppor

@koppor Wygląda na to, że tak naprawdę nie używasz bash.
chepner

2
set ./*                               #set the arg array to glob results
${2+":"} [ -e "$1" ] &&               #if more than one result skip the stat "$1"
printf "current file is %s\n" "$@"    #print the whole array at once

###or###

${2+":"} [ -e "$1" ] &&               #same kind of test
for    fname                          #iterate singly on $fname var for array
do     printf "file is %s\n" "$fname" #print each $fname for each iteration
done                                  

W komentarzu tutaj wspominasz o wywołaniu funkcji ...

file_fn()
    if     [ -e "$1" ] ||               #check if first argument exists
           [ -L "$1" ]                  #or else if it is at least a broken link
    then   for  f                       #if so iterate on "$f"
           do : something w/ "$f"
           done
    else   command <"${1-/dev/null}"    #only fail w/ error if at least one arg
    fi

 file_fn *

2

Użyj znajdź

export -f myshellfunc
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec bash -c 'myshellfunc "$0"' {} \;

Musisz to wyeksportować swoją funkcję powłoki, export -faby to zadziałało. Teraz findwykonuje, bashktóra wykonuje twoją funkcję powłoki i pozostaje tylko na bieżącym poziomie katalogu.


Który powtarza się w podkatalogach i chcę wywołać funkcję bash (nie skrypt) dla dopasowań.
symcbean

@symcbean Edytowałem, aby ograniczyć do pojedynczego
katalogu

-3

Zamiast:

FILES=`ls *.zip`

Próbować:

FILES=`ls * | grep *.zip`

W ten sposób, jeśli ls zawiedzie (co ma miejsce w twoim przypadku), grepuje nieudane wyjście i zwraca jako pustą zmienną.

current file is      <---Blank Here

Możesz dodać do tego logikę, aby zwracała „Nie znaleziono pliku”

#!/bin/bash

FILES=`ls * | grep *.zip`
if [[ $? == "0" ]]; then
    for fname in "$FILES" ; do
        echo current file is $fname
    done
else
    echo "No Files Found"
fi

W ten sposób, jeśli poprzednie polecenie zakończyło się powodzeniem (zostało zakończone z wartością 0), wydrukuje bieżący plik, w przeciwnym razie wypisze „Nie znaleziono plików”


1
Myślę, że złym pomysłem jest dodanie jeszcze jednego procesu ( grep) zamiast próby rozwiązania problemu za pomocą lepszego narzędzia ( find) lub zmiany odpowiedniego ustawienia dla obecnego rozwiązania (z shopt -s nullglob)
Thomas Baruchel

1
Zgodnie z komentarzem OP do ich oryginalnego postu shopt -s nullglobnie działa. Próbowałem findpodczas sprawdzania mojej odpowiedzi i nadal nie udało mi się. Myślę, że z powodu wywozu powiedział Dani.
Kip K
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.