Tester s'il existe des fichiers correspondant à un modèle afin d'exécuter un script

29

J'essaie d'écrire une ifdéclaration pour tester s'il existe des fichiers correspondant à un certain modèle. S'il y a un fichier texte dans un répertoire, il doit exécuter un script donné.

Mon code actuellement:

if [ -f /*.txt ]; then ./script fi

Veuillez donner quelques idées; Je veux seulement exécuter le script s'il y en a un .txtdans le répertoire.

user40952
la source
3
Êtes-vous sûr que "le répertoire" est censé être /? De plus, il vous manque un point-virgule avant fi.
depquid
La solution la plus propre et la plus robuste que j'ai rencontrée est d'utiliser findcomme expliqué ici sur stackoverflow .
Joshua Goldberg

Réponses:

39
[ -f /*.txt ]

renvoie true uniquement s'il existe un (et un seul) fichier non masqué /dont le nom se termine .txtet si ce fichier est un fichier normal ou un lien symbolique vers un fichier normal.

En effet, les caractères génériques sont développés par le shell avant d'être transmis à la commande (ici [).

Donc , s'il y a un /a.txtet /b.txt, [sera transmis 5 arguments: [, -f, /a.txt, /b.txtet ]. [se plaindrait alors qu'on -flui donne trop d'arguments.

Si vous souhaitez vérifier que le *.txtmodèle se développe en au moins un fichier non caché (normal ou non):

shopt -s nullglob
set -- *.txt
if [ "$#" -gt 0 ]; then
  ./script "$@" # call script with that list of files.
fi
# Or with bash arrays so you can keep the arguments:
files=( *.txt )
# apply C-style boolean on member count
(( ${#files[@]} )) && ./script "${files[@]}"

shopt -s nullglobest bashspécifique, mais des coquilles comme ksh93, zsh, yash, tcshont des déclarations équivalentes.

Notez qu'il trouve ces fichiers en lisant le contenu du répertoire, il n'essaie pas du tout d'accéder à ces fichiers, ce qui le rend plus efficace que les solutions qui appellent des commandes comme lsou statsur cette liste de fichiers calculée par le shell.

L' shéquivalent standard serait:

set -- [*].txt *.txt
case "$1$2" in
  ('[*].txt*.txt') ;;
  (*) shift; script "$@"
esac

Le problème est qu'avec les shells Bourne ou POSIX, si un modèle ne correspond pas, il se développe lui-même. Donc, si se *.txtdéveloppe en *.txt, vous ne savez pas si c'est parce qu'il n'y a pas de .txtfichier dans le répertoire ou parce qu'il y a un fichier appelé *.txt. L'utilisation [*].txt *.txtpermet de distinguer les deux.

Stéphane Chazelas
la source
[ -f /*.txt ]est assez rapide par rapport à compgen.
Daniel Böhmer
@ DanielBöhmer [ -f /*.txt ]aurait tort, mais dans mon test sur un répertoire qui contient des 3425fichiers, 94dont des fichiers txt non cachés, compgen -G "*.txt" > /dev/null 2>&1semble être aussi rapide que set -- *.txt; [ "$#" -gt 0 ](20,5 secondes pour les deux lorsqu'il est répété 10000 fois dans mon cas).
Stéphane Chazelas
11

Vous pouvez toujours utiliser find:

find . -maxdepth 1 -type f -name "*.txt" 2>/dev/null | grep -q . && ./script

Explication:

  • find . : recherche dans le répertoire courant
  • -maxdepth 1: ne recherche pas les sous-répertoires
  • -type f : recherche uniquement les fichiers standard
  • name "*.txt" : rechercher les fichiers se terminant par .txt
  • 2>/dev/null : rediriger les messages d'erreur vers /dev/null
  • | grep -q . : grep pour n'importe quel caractère, retournera false si aucun caractère n'a été trouvé.
  • && ./script: Exécuter ./scriptuniquement si la commande précédente a réussi ( &&)
terdon
la source
2
findne renvoie false que s'il a du mal à rechercher des fichiers, pas s'il ne trouve aucun fichier. Vous voulez diriger la sortie grep -q .pour vérifier si elle trouve quelque chose.
Stéphane Chazelas
@StephaneChazelas, vous avez bien sûr raison. Bizarre cependant, je l'avais testé et cela semblait fonctionner. Doit avoir fait quelque chose d'étrange parce que ça ne l'est plus. Quand trouvera "avoir du mal à trouver des fichiers"?
terdon
@terdon, comme lorsqu'un répertoire est inaccessible, ou des erreurs d'E / S ou toute erreur renvoyée par un appel système qu'il effectue. Dans ce cas, essayez après chmod a-x ..
Stéphane Chazelas
8

Une solution possible est également intégrée à Bash compgen. Cette commande renvoie toutes les correspondances possibles pour un modèle de globalisation et possède un code de sortie indiquant si des fichiers correspondent.

compgen -G "/*.text" > /dev/null && ./script

J'ai trouvé cette question en cherchant des solutions plus rapides.

Daniel Böhmer
la source
1
Bonne trouvaille! Si vous êtes dans un environnement local à plusieurs octets, vous pouvez l'améliorer légèrement avec LC_ALL=C compgen -G "*.txt" > /dev/null.
Stéphane Chazelas
7

Voici une doublure pour le faire:

$ ls
file1.pl  file2.pl

les fichiers existent

$ stat -t *.pl >/dev/null 2>&1 && echo "file exists" || echo "file doesn't exist"
file exists

les fichiers n'existent pas

$ stat -t -- *.txt >/dev/null 2>&1 && echo "file exists" || echo "file don't exist"
file don't exist

Cette approche utilise les opérateurs ||et &&dans bash. Ce sont les opérateurs "ou" et "et".

Donc, si la commande stat renvoie un $?égal à 0, le premier echoest appelé, s'il renvoie un 1, le second echoest appelé.

renvoyer les résultats de stat

# a failure
$ stat -t -- *.txt >/dev/null 2>&1
$ echo "$?"
1

# a success
$ stat -t -- *.pl >/dev/null 2>&1
$ echo "$?"
0

Cette question est largement couverte sur stackoverflow:

slm
la source
1
Pourquoi utiliser le non standard statquand ls -dpeut faire la même chose?
Stéphane Chazelas
Je pensais que ls -drépertorie un répertoire? Cela ne semblait pas fonctionner lorsque j'ai essayé de lister un répertoire avec des fichiers ls -d *.plpar exemple.
slm
Vous pouvez remplacer la déclaration à la gauche &&par ls *.txtet il fonctionnera aussi bien. Assurez-vous d'envoyer la stdout et la stderr /dev/nullcomme suggéré par @slm.
unxnut
1
Si vous utilisez ls *.txtet il n'y a pas de fichiers présents dans le répertoire cela renvoie une $? = 2, qui continuera de fonctionner avec le cas alors, mais ce fut l' une de mes raisons de choisir statplus ls. Je voulais un 0 pour le succès et un 1 pour un échec.
slm
ls -dest de lister les répertoires au lieu de leur contenu. Il en va de ls -dmême lstatpour le fichier, tout comme GNU stat. Les commandes d'état de sortie non nul qui retournent en cas d'échec sont spécifiques au système, il est peu logique de faire des hypothèses à leur sujet.
Stéphane Chazelas
4

Comme le souligne Chazelas, votre script échouerait si l'expansion générique correspond à plusieurs fichiers.

Cependant, il y a une astuce que j'utilise ( même si je ne l'aime pas beaucoup ) pour se déplacer:

PATTERN=(/*.txt)
if [ -f ${PATTERN[0]} ]; then
...
fi

Comment ça marche?

L'expansion des caractères génériques correspondra à un tableau de noms de fichiers, nous obtenons le premier s'il y en a, sinon null si aucune correspondance.

M. Pei
la source
OMI, c'est la moins mauvaise réponse ici. Ils semblent tous assez horribles, comme si une fonctionnalité de base manquait dans la langue.
plugwash
@plugwash c'est intentionnel. Si bash est nul ... c'est parce que les commandes que vous en utilisez sont
nulles
2
C'est la mauvaise logique (et vous manquez des guillemets). Cela vérifie si le premier fichier correspondant est un fichier normal. Il peut s'agir d'un fichier non régulier, mais il peut y avoir plusieurs autres .txtfichiers de type régulier . Essayez par exemple après mkdir a.txt; mkfifo b.txt; echo regular > c.txt.
Stéphane Chazelas
1

Aussi simple que:

cnt=`ls \*.txt 2>/dev/null | wc -l`
if [ "$cnt" != "0" ]; then ./script fi

wc -l compte les lignes du caractère générique étendu.

Kim Johansson
la source
1
Downvote: Cela recueille un nombre incroyable d'anti-modèles débutants dans une petite quantité de code. Vous ne devez pas analyser la lssortie et ne l'examinez presque jamais $?directement, car elle le fait ifdéjà. En outre, utiliser wcpour voir si quelque chose s'est produit est également mal dirigé.
tripleee
0

J'aime la solution de tableau précédente, mais cela pourrait devenir un gaspillage avec un grand nombre de fichiers - le shell utiliserait beaucoup de mémoire pour construire le tableau, et seul le premier élément serait testé.

Voici une structure alternative que j'ai testée hier:

$ cd /etc; if [[ $(echo * | grep passwd) ]];then echo yes;else echo no;fi yes $ cd /etc; if [[ $(echo * | grep password) ]];then echo yes;else echo no;fi no

La valeur de sortie du grep semble déterminer le chemin à travers la structure de contrôle. Cela teste également avec des expressions régulières plutôt qu'avec des modèles de shell. Certains de mes systèmes ont la commande "pcregrep" qui permet des correspondances regex beaucoup plus sophistiquées.

(J'ai modifié cette réponse pour supprimer un "ls" dans la substitution de commande après avoir lu la critique ci-dessus pour l'avoir analysée.)

Charlie
la source
-2

si vous souhaitez utiliser une clause if, évaluez le nombre:

if (( `ls *.txt 2> /dev/null|wc -l` ));then...
Rusty75
la source