web-dev-qa-db-fra.com

Vérifier si un fichier existe avec un caractère générique dans un script Shell

J'essaie de vérifier si un fichier existe, mais avec un caractère générique. Voici mon exemple:

if [ -f "xorg-x11-fonts*" ]; then
    printf "BLAH"
fi

J'ai également essayé sans les guillemets.

253
Danny

Le plus simple devrait être de s'appuyer sur la valeur de retour ls (elle renvoie une valeur différente de zéro lorsque les fichiers n'existent pas):

if ls /path/to/your/files* 1> /dev/null 2>&1; then
    echo "files do exist"
else
    echo "files do not exist"
fi

J'ai redirigé la sortie ls pour la rendre complètement silencieuse.


EDIT: Comme cette réponse a suscité un peu d’attention (et des remarques critiques très utiles sous forme de commentaires), voici une optimisation qui repose également sur une expansion globale, mais évite l’utilisation de ls:

for f in /path/to/your/files*; do

    ## Check if the glob gets expanded to existing files.
    ## If not, f here will be exactly the pattern above
    ## and the exists test will evaluate to false.
    [ -e "$f" ] && echo "files do exist" || echo "files do not exist"

    ## This is all we needed to know, so we can break after the first iteration
    break
done

Ceci est très similaire à la réponse de @ grok12, mais évite les itérations inutiles à travers toute la liste.

379
Costi Ciudatu

Si votre Shell dispose d'une option nullglob et qu'il est activé, un modèle de caractère générique qui ne correspond à aucun fichier ne sera totalement supprimé de la ligne de commande. Cela fera ls ne voir aucun argument de chemin d'accès, lister le contenu du répertoire courant et réussir, ce qui est faux. GNU _ stat , qui échoue toujours si aucun argument n'est attribué ou si un argument nomme un fichier inexistant, serait plus robuste. De plus, l'opérateur de redirection &> est un bashism.

if stat --printf='' /path/to/your/files* 2>/dev/null
then
    echo found
else
    echo not found
fi

Mieux vaut toujours GNU find , qui peut gérer une recherche générique en interne et se terminer dès qu'il trouve un fichier correspondant, plutôt que perdre du temps à traiter une liste potentiellement très longue d’entre eux développée par Shell; cela évite également le risque que le shell ne déborde de sa mémoire tampon de ligne de commande.

if test -n "$(find /dir/to/search -maxdepth 1 -name 'files*' -print -quit)"
then
    echo found
else
    echo not found
fi

Les versions non-GNU de find peuvent ne pas avoir l'option - maxdepth utilisée ici pour faire find rechercher uniquement le /dir/to/search à la place de toute l'arborescence de répertoires qui y est enracinée.

56
flabdablet

Voici ma réponse -

files=(xorg-x11-fonts*)

if [ -e "${files[0]}" ];
then
    printf "BLAH"
fi
36
Pankaj Parashar

Vous pouvez faire ce qui suit:

set -- xorg-x11-fonts*
if [ -f "$1" ]; then
    printf "BLAH"
fi

Cela fonctionne avec sh et dérivés: ksh et bash. Cela ne crée pas de sous-shell. Les commandes $ (..) et `...` créent un sous-shell: elles lancent un processus et sont inefficaces. Bien sûr, cela fonctionne avec plusieurs fichiers, et cette solution peut être la plus rapide, ou la seconde après la plus rapide.

Cela fonctionne aussi quand il n'y a pas d'allumettes. Il n'est pas nécessaire d'utiliser nullglob comme le dit l'un des commentateurs. $ 1 contiendra le nom du test d'origine, donc le test -f $ 1 ne réussira pas, car le fichier $ 1 n'existe pas.

21
joseyluis
for i in xorg-x11-fonts*; do
  if [ -f "$i" ]; then printf "BLAH"; fi
done

Cela fonctionnera avec plusieurs fichiers et avec un espace dans les noms de fichiers.

19
grok12

PDATE:

Ok, maintenant j'ai définitivement la solution:

files=$(ls xorg-x11-fonts* 2> /dev/null | wc -l)
if [ "$files" != "0" ]
then
   echo "Exists"
else
    echo "None found."
fi

> Exists
14
Swift

Peut-être que cela aidera quelqu'un:

if [ "`echo xorg-x11-fonts*`" != "xorg-x11-fonts*" ]; then
    printf "BLAH"
fi
13
Marian

La question n'étant pas spécifique à Linux/Bash, j'ai donc pensé ajouter la méthode Powershell - qui traite les caractères génériques de manière différente - vous le mettez en les citations comme ci-dessous:

If (Test-Path "./output/test-pdf-docx/Text-Book-Part-I*"){
  Remove-Item -force -v -path ./output/test-pdf-docx/*.pdf
  Remove-Item -force -v -path ./output/test-pdf-docx/*.docx
}

Je pense que cela est utile car le concept de la question initiale englobe les "shells" en général, pas seulement Bash ou Linux, et s’appliquerait également aux utilisateurs de Powershell.

7
Jeremy Hajek

À proprement parler, si vous voulez seulement imprimer "Blah", voici la solution:

find . -maxdepth 1 -name 'xorg-x11-fonts*' -printf 'BLAH' -quit

Voici un autre moyen:

doesFirstFileExist(){
    test -e "$1"
}

if doesFirstFileExist xorg-x11-fonts*
then printf "BLAH"
fi

Mais je pense que le plus optimal est le suivant, car il ne tentera pas de trier les noms de fichiers:

if [ -z `find . -maxdepth 1 -name 'xorg-x11-fonts*' -printf 1 -quit` ]
then printf "BLAH"
fi
4
Vouze

Le code bash que j'utilise

if ls /syslog/*.log > /dev/null 2>&1; then 
   echo "Log files are present in /syslog/; 
fi

Merci!

3
CapitanBlack

Voici une solution pour votre spécifique problème qui ne nécessite pas de boucles for ou de commandes externes telles que ls, find, etc.

if [ "$(echo xorg-x11-fonts*)" != "xorg-x11-fonts*" ]; then
    printf "BLAH"
fi

Comme vous pouvez le constater, c’est un peu plus compliqué que ce que vous espériez et repose sur le fait que si le shell n’est pas en mesure d’agrandir le glob, cela signifie qu’il n’existe aucun fichier avec ce glob et que echo le sera affiche le glob tel quel , ce qui nous permet de faire une simple comparaison de chaîne pour vérifier si l'un de ces fichiers existe.

Si nous devions généraliser la procédure, cependant, nous devrions prendre en compte le fait que les fichiers peuvent contenir des espaces dans leurs noms et/ou leurs chemins et que le caractère global pourrait à juste titre développez à rien (dans votre exemple, ce serait le cas d'un fichier dont le nom est exactement xorg-x11-fonts).

Cela pourrait être réalisé par la fonction suivante, dans bash.

function doesAnyFileExist {
   local arg="$*"
   local files=($arg)
   [ ${#files[@]} -gt 1 ] || [ ${#files[@]} -eq 1 ] && [ -e "${files[0]}" ]
}

Pour revenir à votre exemple, il pourrait être invoqué comme ceci.

if doesAnyFileExist "xorg-x11-fonts*"; then
    printf "BLAH"
fi

L’expansion globale doit se produire au sein de la fonction elle-même pour que celle-ci fonctionne correctement, c’est pourquoi je mets l’argument entre guillemets et c’est la raison pour laquelle la première ligne du corps de la fonction existe: afin que tous les arguments (qui pourraient expansion en dehors de la fonction, ainsi qu’un paramètre parasite) seraient fusionnés en un seul. Une autre approche pourrait consister à générer une erreur s'il y a plus d'un argument, mais une autre solution consiste à ignorer tout sauf le premier argument.

La deuxième ligne du corps de la fonction définit la variable files var en un tablea constitué de tous les noms de fichiers développés par le glob, un pour chaque élément du tableau. C'est bien si les noms de fichiers contiennent des espaces, chaque élément du tableau contiendra les noms tels quels , y compris les espaces.

La troisième ligne du corps de la fonction fait deux choses:

  1. Il vérifie d'abord s'il y a plus d'un élément dans le tableau. Si tel est le cas, cela signifie que le glob sûrement a été étendu à quelque chose (à cause de ce que nous avons fait sur la 1ère ligne), ce qui implique qu’au moins un fichier correspondant au glob existe, ce qui est tout ce que nous voulions à savoir.

  2. Si à l'étape 1. nous avons découvert que nous avions moins de 2 éléments dans le tableau, nous vérifions si nous en avions un et si oui, nous vérifions si celui-ci existe, de la manière habituelle. Nous devons faire cette vérification supplémentaire afin de prendre en compte les arguments de la fonction sans glob chars, auquel cas le tableau ne contient qu'un seul élément, non développé.

2
Fabio A.

J'utilise ceci:

filescount=`ls xorg-x11-fonts* | awk 'END { print NR }'`  
if [ $filescount -gt 0 ]; then  
    blah  
fi
1
Alexandre Vr.

IMHO, il est préférable d'utiliser find toujours lors du test de fichiers, de fichiers globaux ou de répertoires. La pierre d'achoppement est alors l'état de sortie de find: 0 si tous les chemins ont été parcourus avec succès,> 0 sinon. L'expression que vous avez transmise à find ne crée pas d'écho dans son code de sortie.

L'exemple suivant teste si un répertoire a des entrées:

$ mkdir A
$ touch A/b
$ find A -maxdepth 0 -not -empty -print | head -n1 | grep -q . && echo 'not empty'
not empty

Lorsque A n'a pas de fichier grep échoue:

$ rm A/b
$ find A -maxdepth 0 -not -empty -print | head -n1 | grep -q . || echo 'empty'
empty

Lorsque A n'existe pas grep échoue à nouveau car find s'imprime uniquement sur stderr:

$ rmdir A
$ find A -maxdepth 0 -not -empty -print | head -n1 | grep -q . && echo 'not empty' || echo 'empty'
find: 'A': No such file or directory
empty

Remplacez -not -empty par une autre expression find, mais soyez prudent si vous -exec une commande qui affiche sur la sortie standard. Vous voudrez peut-être rechercher une expression plus spécifique dans de tels cas.

Cette approche fonctionne bien dans les scripts Shell. La question à l'origine était de rechercher le glob xorg-x11-fonts*:

if find -maxdepth 0 -name 'xorg-x11-fonts*' -print | head -n1 | grep -q .
then
    : the glob matched
else
    : ...not
fi

Notez que la branche "else" est atteinte si xorg-x11-fonts* ne correspondait pas ou si find rencontrait une erreur. Pour distinguer le cas, utilisez $?.

1
Andreas Spindler
if [ `ls path1/* path2/* 2> /dev/null | wc -l` -ne 0 ]; then echo ok; else echo no; fi
1
Dan Elias

Vous pouvez également couper d'autres fichiers

if [ -e $( echo $1 | cut -d" " -f1 ) ] ; then
   ...
fi
0
McCloud

Que diriez-vous

if ls -l  | grep -q 'xorg-x11-fonts.*' # grep needs a regex, not a Shell glob
then
     # do something
else
     # do something else
fi 
0
abc

Utilisation de nouvelles fonctionnalités fantaisistes dans les shsh ksh, bash et zsh (cet exemple ne gère pas les espaces dans les noms de fichiers):

# Declare a regular array (-A will declare an associative array. Kewl!)
declare -a myarray=( /mydir/tmp*.txt )
array_length=${#myarray[@]}

# Not found if the 1st element of the array is the unexpanded string
# (ie, if it contains a "*")
if [[ ${myarray[0]} =~ [*] ]] ; then
   echo "No files not found"
Elif [ $array_length -eq 1 ] ; then
   echo "File was found"
else
   echo "Files were found"
fi

for myfile in ${myarray[@]}
do
  echo "$myfile"
done

Oui, ça sent le Perl. Heureux de ne pas être intervenu;)

0
Ben Slade

Essaye ça

fileTarget="xorg-x11-fonts*"

filesFound=$(ls $fileTarget)  # 2014-04-03 edit 2: removed dbl-qts around $(...)

edit 2014-04-03 (suppression des citations de liens et ajout du fichier test 'Charlie 22.html' (2 espaces)

case ${filesFound} in
  "" ) printf "NO files found for target=${fileTarget}\n" ;;
   * ) printf "FileTarget Files found=${filesFound}\n" ;;
esac 

Test

fileTarget="*.html"  # where I have some html docs in the current dir

FileTarget Files found=Baby21.html
baby22.html
charlie  22.html
charlie21.html
charlie22.html
charlie23.html

fileTarget="xorg-x11-fonts*"

NO files found for target=xorg-x11-fonts*

Notez que cela ne fonctionne que dans le répertoire actuel ou lorsque varfileTarget inclut le chemin que vous voulez inspecter.

0
shellter

Nous avons trouvé deux solutions intéressantes à partager. Le premier souffre toujours du problème "cela va casser s'il y a trop de correspondances":

pat="yourpattern*" matches=($pat) ; [[ "$matches" != "$pat" ]] && echo "found"

(Rappelez-vous que si vous utilisez un tableau sans la syntaxe [ ], vous obtenez le premier élément du tableau.)

Si vous avez "shopt -s nullglob" dans votre script, vous pouvez simplement faire:

matches=(yourpattern*) ; [[ "$matches" ]] && echo "found"

Maintenant, s’il est possible d’avoir une tonne de fichiers dans un répertoire, vous êtes plutôt coincé avec find:

find /path/to/dir -maxdepth 1 -type f -name 'yourpattern*' | grep -q '.' && echo 'found'
0
Chris Cogdon

S'il existe une grande quantité de fichiers sur un dossier réseau, l'utilisation du caractère générique est discutable (débordement d'arguments en vitesse ou en ligne de commande).

J'ai fini avec:

if [ -n "$(find somedir/that_may_not_exist_yet -maxdepth 1 -name \*.ext -print -quit)" ] ; then
  echo Such file exists
fi
0
luxigo