web-dev-qa-db-fra.com

Teste si un glob a des correspondances dans bash

Si je veux vérifier l'existence d'un seul fichier, je peux le tester en utilisant test -e filename ou [ -e filename ].

Supposons que j'ai un glob et que je veux savoir s'il existe des fichiers dont le nom correspond au glob. Le glob peut correspondre à 0 fichiers (auquel cas je n'ai rien à faire), ou à 1 ou plusieurs fichiers (dans ce cas, je dois faire quelque chose). Comment puis-je tester si un glob a des correspondances? (Je me moque du nombre de correspondances et il serait préférable de pouvoir le faire avec une déclaration if et aucune boucle (tout simplement parce que je trouve cela très lisible).

(test -e glob* échoue si le glob correspond à plus d'un fichier.)

184
Ken Bloom

Bash solution spécifique:

compgen -G "<glob-pattern>"

Echappez au modèle ou il sera pré-développé en allumettes. 

Le statut de sortie est:

  • 1 pour aucun match, 
  • 0 pour 'un ou plusieurs résultats'

stdout est une liste de fichiers correspondant au glob .
Je pense que c'est la meilleure option en termes de concision et de minimisation des effets secondaires potentiels.

UPDATE: Exemple d'utilisation demandée.

if compgen -G "/tmp/someFiles*" > /dev/null; then
    echo "Some files exist."
fi
127
Brian Chrisman

L’option shell nullglob est en effet un bashisme.

Pour éviter la nécessité d'une sauvegarde fastidieuse et de la restauration de l'état nullglob, je ne l'aurais placé que dans le sous-shell qui étend le glob:

if test -n "$(shopt -s nullglob; echo glob*)"
then
    echo found
else
    echo not found
fi

Pour une meilleure portabilité et une navigation plus flexible, utilisez find:

if test -n "$(find . -maxdepth 1 -name 'glob*' -print -quit)"
then
    echo found
else
    echo not found
fi

Les actions -print -quit explicites sont utilisées pour find au lieu de l'action implicite -print de sorte que find se ferme dès qu'il trouve le premier fichier correspondant à la recherche Critères. Lorsque beaucoup de fichiers correspondent, cela devrait être beaucoup plus rapide que echo glob* ou ls glob* et éviter la possibilité de surcharger la ligne de commande étendue (certains shells ont une limite de longueur de 4 Ko).

Si find a l’impression d’être un peu exagéré et que le nombre de fichiers susceptibles de correspondre est faible, utilisez stat:

if stat -t glob* >/dev/null 2>&1
then
    echo found
else
    echo not found
fi
148
flabdablet
#!/usr/bin/env bash

# If it is set, then an unmatched glob is swept away entirely -- 
# replaced with a set of zero words -- 
# instead of remaining in place as a single Word.
shopt -s nullglob

M=(*px)

if [ "${#M[*]}" -ge 1 ]; then
    echo "${#M[*]} matches."
else
    echo "No such files."
fi
20
miku

J'aime

exists() {
    [ -e "$1" ]
}

if exists glob*; then
    echo found
else
    echo not found
fi

C’est à la fois lisible et efficace (sauf s’il existe un très grand nombre de fichiers).
Le principal inconvénient est que c'est beaucoup plus subtil qu'il n'y paraît, et je me sens parfois obligé d'ajouter un long commentaire.
S'il existe une correspondance, "glob*" est développé par le shell et toutes les correspondances sont passées à exists(), qui vérifie la première et ignore le reste.
S'il n'y a pas de correspondance, "glob*" est passé à exists() et n'est pas non plus présent.

Edit: il peut y avoir un faux positif, voir commentaire

16
Dan Bloch

test -e a la mise en garde malheureuse qu'il considère que les liens symboliques brisés n'existent pas. Donc, vous voudrez peut-être vérifier pour ceux-là aussi. 

function globexists {
  test -e "$1" -o -L "$1"
}

if globexists glob*; then
    echo found
else
    echo not found
fi
6
NerdMachine

Si vous avez globfail réglé, vous pouvez utiliser ce fou (ce que vous ne devriez vraiment pas faire)

shopt -s failglob # exit if * does not match 
( : * ) && echo 0 || echo 1

ou 

q=( * ) && echo 0 || echo 1
4
Arcabard

J'ai encore une autre solution:

if [ "$(echo glob*)" != 'glob*' ]

Cela fonctionne bien pour moi. Y a-t-il des cas de coin qui me manquent?

4
SaschaZorn

D'après la réponse de flabdablet , il me semble que le plus simple (pas nécessairement le plus rapide) consiste simplement à utiliser find lui-même, tout en laissant l'expansion globale sur Shell, comme:

find /some/{p,long-p}ath/with/*globs* -quit &> /dev/null && echo "MATCH"

Ou dans if comme:

if find $yourGlob -quit &> /dev/null; then
    echo "MATCH"
else
    echo "NOT-FOUND"
fi
3
queria

Pour simplifier quelque peu la réponse du MYYN, basée sur son idée:

M=(*py)
if [ -e ${M[0]} ]; then
  echo Found
else
  echo Not Found
fi
3
Ken Bloom

Cette abomination semble fonctionner:

#!/usr/bin/env bash
shopt -s nullglob
if [ "`echo *py`" != "" ]; then
    echo "Glob matched"
else
    echo "Glob did not match"
fi

Cela nécessite probablement bash, pas sh.

Cela fonctionne parce que l'option nullglob amène le glob à se transformer en chaîne vide s'il n'y a pas de correspondance. Ainsi, toute sortie non vide de la commande echo indique que le glob correspond à quelque chose.

1
Ryan Thompson

Dans Bash, vous pouvez effectuer une sélection dans un tableau. si le glob ne correspond pas, votre tableau contiendra une seule entrée qui ne correspond à aucun fichier existant:

#!/bin/bash

shellglob='*.sh'

scripts=($shellglob)

if [ -e "${scripts[0]}" ]
then stat "${scripts[@]}"
fi

Remarque: si vous avez défini nullglob, scripts sera un tableau vide, et vous devez tester avec [ "${scripts[*]}" ] ou avec [ "${#scripts[*]}" != 0 ] à la place. Si vous écrivez une bibliothèque qui doit fonctionner avec ou sans nullglob, vous voudrez

if [ "${scripts[*]}" ] && [ -e "${scripts[0]}" ]

Un avantage de cette approche est que vous disposez alors de la liste des fichiers avec lesquels vous souhaitez travailler, plutôt que de devoir répéter l'opération glob.

1
Toby Speight

Je n'ai pas vu cette réponse, alors j'ai pensé la publier:

set -- glob*
[ -f "$1" ] && echo "found $@"
1
Brad Howes
#!/bin/bash
set nullglob
touch /tmp/foo1 /tmp/foo2 /tmp/foo3
FOUND=0
for FILE in /tmp/foo*
do
    FOUND=$((${FOUND} + 1))
done
if [ ${FOUND} -gt 0 ]; then
    echo "I found ${FOUND} matches"
else
    echo "No matches found"
fi
0
Peter Lyons
if ls -d $glob > /dev/null 2>&1; then
  echo Found.
else
  echo Not found.
fi

Notez que cela peut prendre beaucoup de temps s'il y a beaucoup de correspondances ou que l'accès aux fichiers est lent.

0
Florian Diesch

[ls glob* 2>/dev/null | head -n 1] && echo true

0
otocan
set -- glob*
if [ -f "$1" ]; then
  echo "It matched"
fi

Explication

Lorsqu'il n'y a pas de correspondance pour glob*, alors $1 contiendra 'glob*'. Le test -f "$1" ne sera pas vrai car le fichier glob* n'existe pas.

Pourquoi c'est mieux que les alternatives

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; ils lancent un processus et sont donc plus lents que cette solution.

0
joseyluis