bash itérer la liste des fichiers, sauf lorsqu'il est vide

33

Je pensais que ce serait simple - mais cela s'avère plus complexe que ce à quoi je m'attendais.

Je veux parcourir tous les fichiers d'un type particulier dans un répertoire, alors j'écris ceci:

#!/bin/bash

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

Cela fonctionne tant qu'il y a au moins un fichier correspondant dans le répertoire. Cependant s'il n'y a pas de fichiers correspondants, j'obtiens ceci:

current file is *.zip

J'ai ensuite essayé:

#!/bin/bash

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

Bien que le corps de la boucle ne s'exécute pas lorsqu'il n'y a pas de fichiers, j'obtiens une erreur de ls:

ls: *.zip: No such file or directory

Comment puis-je écrire une boucle qui ne gère correctement aucun fichier correspondant?

symcbean
la source
7
Ajoutez shopt -s nullglobavant d'exécuter la boucle for.
cuonglm
@cuolnglm: de façon effrayante, ls renvoie le nom du script en cours d'exécution plutôt qu'une liste vide sur cette boîte RHEL5 (bash 3.2.25) si je fais FILES=ls * .zip ; for fname in "${FILES}"...mais cela fonctionne comme prévu avecfor fname in *.zip ; do....
symcbean
3
Utilisez for file in *.zippas `ls ...`. La suggestion de @ cuonglm est de faire en sorte que *.ziprien ne se développe lorsque le modèle ne correspond à aucun fichier. lssans arguments répertorie le répertoire courant.
Stéphane Chazelas
1
Cette question explique pourquoi l'analyse de la sortie de lsest généralement à éviter: pourquoi ne pas analyser ls? ; voir également le lien en haut de cette page vers l' article ParsingLs de BashGuide .
PM 2Ring

Réponses:

34

Dans bash, vous pouvez définir l' nullgloboption de sorte qu'un modèle qui ne correspond à rien "disparaisse", plutôt que traité comme une chaîne littérale:

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

Dans le script shell POSIX, vous vérifiez simplement qu'il fnameexiste (et en même temps avec [ -f ], vérifiez qu'il s'agit d'un fichier normal (ou d'un lien symbolique vers un fichier normal) et pas d'autres types comme répertoire / fifo / device ...):

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

Remplacez [ -f "$fname" ]par [ -e "$fname" ] || [ -L "$fname ]si vous souhaitez parcourir tous les fichiers (non masqués) dont le nom se termine .zipquel que soit leur type.

Remplacez *.zippar .*.zip .zip *.zipsi vous souhaitez également prendre en compte les fichiers cachés dont le nom se termine par .zip.

chepner
la source
1
shopt -s nullglobn'a pas fonctionné pour moi sur Ubuntu 17.04, mais [ -f "$fname" ] || continuea bien fonctionné.
koppor
@koppor Il semble que vous n'utilisiez pas réellement 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                                  

Dans un commentaire, vous mentionnez ici l'invocation d'une fonction ...

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
la source
2

Utilisez find

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

Vous DEVEZ exporter votre fonction shell avec export -fpour que cela fonctionne. findExécute maintenant bashce qui exécute votre fonction shell, et reste au niveau dir actuel seulement.

Dani_l
la source
Ce qui revient dans les sous-répertoires et je veux invoquer une fonction bash (pas un script) pour les correspondances.
symcbean
@symcbean que j'ai édité pour limiter à un seul dir et gérer les fonctions bash
Dani_l
-3

Au lieu de:

FILES=`ls *.zip`

Essayer:

FILES=`ls * | grep *.zip`

De cette façon, si ls échoue (ce qui se produit dans votre cas), il récupérera la sortie ayant échoué et retournera sous forme de variable vide.

current file is      <---Blank Here

Vous pouvez ajouter un peu de logique à cela pour lui faire retourner "No File Found"

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

De cette façon, si la commande précédente a réussi (sortie avec une valeur 0), elle imprimera le fichier actuel, sinon elle affichera "Aucun fichier trouvé"

Kip K
la source
1
Je pense que c'est une mauvaise idée d'ajouter un processus de plus ( grep) plutôt que d'essayer de résoudre le problème en utilisant un meilleur outil ( find) ou en modifiant le paramètre pertinent pour la solution actuelle (avec shopt -s nullglob)
Thomas Baruchel
1
Selon le commentaire du PO sur leur message d'origine, shopt -s nullglobcela ne fonctionne pas. J'ai essayé finden vérifiant ma réponse et elle a échoué. Je pense à cause de l'export dit par Dani.
Kip K du