Bash-Iterationsdatei-Liste, außer wenn leer

33

Ich dachte, das wäre einfach - aber es erweist sich als komplexer als ich erwartet hatte.

Ich möchte alle Dateien eines bestimmten Typs in einem Verzeichnis durchlaufen, also schreibe ich Folgendes:

#!/bin/bash

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

Dies funktioniert , solange sich mindestens eine passende Datei im Verzeichnis befindet. Wenn es jedoch keine passenden Dateien gibt, erhalte ich Folgendes:

current file is *.zip

Ich habe dann versucht:

#!/bin/bash

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

Während der Körper der Schleife nicht ausgeführt wird, wenn keine Dateien vorhanden sind, erhalte ich eine Fehlermeldung von ls:

ls: *.zip: No such file or directory

Wie schreibe ich eine Schleife, die keine übereinstimmenden Dateien sauber verarbeitet?

symcbean
quelle
7
Fügen Sie shopt -s nullglobvor dem Ausführen der for-Schleife hinzu.
Donnerstag,
@cuolnglm: spookily diese Ergebnisse in ls den Namen des ausgeführten Skripts , anstatt eine leere Liste auf dieser RHEL5 - Box (bash 3.2.25) Rückkehr , wenn ich FILES=ls * .zip ; for fname in "${FILES}"...aber es funktioniert wie erwartetfor fname in *.zip ; do....
symcbean
3
Verwenden Sie for file in *.zipnicht `ls ...`. @ cuonglms Vorschlag ist, dass er *.zipsich zu nichts erweitert, wenn das Muster keiner Datei entspricht. lsohne Argumente listet das aktuelle Verzeichnis auf.
Stéphane Chazelas
1
In dieser Frage wird erläutert, warum das Parsen der Ausgabe von lsim Allgemeinen vermieden werden sollte: Warum nicht analysieren ls? ; Siehe auch den Link oben auf dieser Seite zum Artikel ParsingLs von BashGuide .
PM 2Ring

Antworten:

34

In bashkönnen Sie die nullglobOption so einstellen , dass ein Muster, das mit nichts übereinstimmt, "verschwindet", anstatt als Literalzeichenfolge behandelt zu werden:

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

Im POSIX-Shell-Skript müssen Sie nur überprüfen, ob es fnameexistiert (und gleichzeitig mit [ -f ]prüfen, ob es sich um eine reguläre Datei (oder einen Symlink zu einer regulären Datei) handelt und nicht um andere Typen wie das Verzeichnis / fifo / device ...):

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

Ersetzen Sie [ -f "$fname" ]durch, [ -e "$fname" ] || [ -L "$fname ]wenn Sie alle (nicht ausgeblendeten) Dateien durchlaufen möchten, deren Name .zipunabhängig vom Typ auf endet .

Ersetzen Sie *.zipdurch, .*.zip .zip *.zipwenn Sie auch versteckte Dateien berücksichtigen möchten, deren Name auf endet .zip.

chepner
quelle
1
shopt -s nullglobhat bei mir unter Ubuntu 17.04 nicht funktioniert, hat aber [ -f "$fname" ] || continuegut funktioniert.
koppor
@koppor Es hört sich so an, als würden Sie nicht wirklich verwenden 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                                  

In einem Kommentar erwähnen Sie hier das Aufrufen einer Funktion ...

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 *
mikeserv
quelle
2

Verwenden Sie find

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

Sie MÜSSEN Ihre Shell-Funktion mit exportieren, export -fdamit dies funktioniert. Jetzt wird findausgeführt, bashwodurch Ihre Shell-Funktion ausgeführt wird, und es wird nur die aktuelle Verzeichnisebene beibehalten.

Dani_l
quelle
Was durch Unterverzeichnisse rekursiert, und ich möchte eine Bash-Funktion (kein Skript) für die Übereinstimmungen aufrufen.
symcbean
@symcbean Ich habe bearbeitet, um auf einzelne Dir und Bash-Funktionen zu beschränken
Dani_l
-3

Anstatt:

FILES=`ls *.zip`

Versuchen:

FILES=`ls * | grep *.zip`

Wenn ls fehlschlägt (was in Ihrem Fall der Fall ist), wird die fehlgeschlagene Ausgabe überprüft und als leere Variable zurückgegeben.

current file is      <---Blank Here

Sie können dem eine Logik hinzufügen, damit es "No File Found" zurückgibt.

#!/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

Auf diese Weise wird, wenn der vorherige Befehl erfolgreich war (mit einem Wert von 0 beendet), die aktuelle Datei gedruckt, andernfalls wird "Keine Dateien gefunden" gedruckt.

Kip K
quelle
1
Ich halte es für eine schlechte Idee, einen weiteren Prozess hinzuzufügen ( grep), anstatt zu versuchen, das Problem mit einem besseren Tool zu beheben ( find) oder die relevante Einstellung für die aktuelle Lösung zu ändern (mit shopt -s nullglob)
Thomas Baruchel
1
Laut dem Kommentar des OP zu ihrem ursprünglichen Beitrag shopt -s nullglobfunktioniert das nicht. Ich habe es versucht, findwährend ich meine Antwort überprüft habe. Ich denke wegen der Exportsache, die Dani gesagt hat.
Kip K