Comment passer le caractère générique '*' au paramètre path de la commande find via une variable dans le script?

9

Je veux utiliser findpour rechercher des fichiers dans un ensemble de dossiers restreints par des caractères génériques, mais où il y a des espaces dans le nom du chemin.

Depuis la ligne de commande, c'est facile. Les exemples suivants fonctionnent tous.

find  te*/my\ files/more   -print
find  te*/'my files'/more  -print
find  te*/my' 'files/more  -print

Ceux-ci trouveront des fichiers dans, par exemple, terminal/my files/moreet tepid/my files/more.

Cependant, j'ai besoin que cela fasse partie d'un script; ce dont j'ai besoin est quelque chose comme ceci:

SEARCH='te*/my\ files/more'
find ${SEARCH} -print

Malheureusement, quoi que je fasse, je ne semble pas être capable de mélanger les caractères génériques et les espaces dans une findcommande au sein d'un script. L'exemple ci-dessus renvoie les erreurs suivantes (notez le doublement inattendu de la barre oblique inverse):

find: te*/my\\’: No such file or directory
find: files/more’: No such file or directory

Essayer d'utiliser des guillemets échoue également.

SEARCH="te*/'my files'/more"
find ${SEARCH} -print

Cela renvoie les erreurs suivantes, après avoir ignoré la signification des guillemets:

find: te*/'my’: No such file or directory
find: ‘files'/more’: No such file or directory

Voici un autre exemple.

SEARCH='te*/my files/more'
find ${SEARCH} -print

Comme prévu:

find: te*/my’: No such file or directory
find: files/more’: No such file or directory

Chaque variation que j'ai essayée renvoie une erreur.

J'ai une solution de contournement, qui est potentiellement dangereuse car elle renvoie trop de dossiers. Je convertis tous les espaces en point d'interrogation (caractère générique à un caractère) comme ceci:

SEARCH='te*/my files/more'
SEARCH=${SEARCH// /?}       # Convert every space to a question mark.
find ${SEARCH} -print

C'est l'équivalent de:

find te*/my?files/more -print

Cela renvoie non seulement les dossiers corrects mais aussi terse/myxfiles/more, ce qui n'est pas censé le faire.

Comment puis-je réaliser ce que j'essaie de faire? Google ne m'a pas aidé :(

Paddy Landau
la source
@KasiyA J'utilise bash; vous devez utiliser autre chose, car je n'ai jamais vu cette construction auparavant. La commande entraîne SEARCH: command not foundl' find -printexécution de la commande .
Paddy Landau, le
Un coup dans le noir, mais qu'en est-il de citer? find "${SEARCH}" -print?
Alaa Ali
@AlaaAli Non, cela ne fonctionne pas, car les guillemets empêchent Bash d'utiliser les caractères génériques. Il recherchera un chemin spécifique avec le nom (dans mon exemple) te*/'my files'/more.
Paddy Landau

Réponses:

9

La même commande devrait fonctionner correctement dans un script:

#!/usr/bin/env bash
find  te*/my\ files/ -print

Si vous devez l' avoir comme variable, cela devient un peu plus complexe:

#!/usr/bin/env bash
search='te*/my\ files/'
eval find "$search" -print

ATTENTION:

L'utilisation de ce evaltype n'est pas sûre et peut entraîner l'exécution de code arbitraire et potentiellement dangereux si les noms de vos fichiers peuvent contenir certains caractères. Voir bash FAQ 48 pour plus de détails.

Il vaut mieux passer le chemin en argument:

#!/usr/bin/env bash
find "$@" -name "file*"

Une autre approche consiste à éviter findcomplètement et à utiliser les fonctionnalités de globbing étendues et les globs de bash:

#!/usr/bin/env bash
shopt -s globstar
for file in te*/my\ files/**; do echo "$file"; done

L' globstaroption bash vous permet d'utiliser **pour faire correspondre de manière récursive:

globstar
      If set, the pattern ** used in a pathname expansion con
      text will match all files and zero or  more  directories
      and  subdirectories.  If the pattern is followed by a /,
      only directories and subdirectories match.

Pour le faire agir à 100% comme trouver et inclure des fichiers dot (fichiers cachés), utilisez

#!/usr/bin/env bash
shopt -s globstar
shopt -s dotglob
for file in te*/my\ files/**; do echo "$file"; done

Vous pouvez même echoles directement sans la boucle:

echo te*/my\ files/**
terdon
la source
2
J'ai mis cela comme réponse en raison des commentaires utiles que Terdon a faits (sans oublier les commentaires utiles des autres). J'ai utilisé la capacité de globalisation de Bash sur la ligne de commande pour passer plusieurs chemins vers mon script, au lieu que le script essaie de le trier. Ça marche bien.
Paddy Landau
2

Que diriez-vous des tableaux?

$ tree Desktop/ Documents/
Desktop/
└── my folder
    └── more
        └── file
Documents/
└── my folder
    ├── folder
    └── more

5 directories, 1 file
$ SEARCH=(D*/my\ folder)
$ find "${SEARCH[@]}" 
Desktop/my folder
Desktop/my folder/more
Desktop/my folder/more/file
Documents/my folder
Documents/my folder/more
Documents/my folder/folder

(*)se développe dans un tableau de tout ce qui correspond au caractère générique. Et se "${SEARCH[@]}"développe dans tous les éléments du tableau ( [@]), chacun étant cité individuellement.

Tardivement, je me rends compte qu’elle devrait en être capable. Quelque chose comme:

find . -path 'D*/my folder/more/'
muru
la source
Idée intelligente, mais hélas, ça ne marche pas. Pourquoi? Parce que le chemin lui-même est maintenu dans une variable; par conséquent, INPUTPATH='te*/my files/moreet SEARCH=(${INPUTPATH}). Peu importe comment je modifie ma façon de procéder, je me retrouve toujours avec un résultat non fonctionnel. Cela semble impossible!
Paddy Landau
C'est vrai, bien sûr, mais l'OP doit le faire dans un script. Cela change les choses puisque l'expansion des caractères génériques devient beaucoup plus complexe et cela ne fonctionne pas.
terdon
@PaddyLandau Dans quel cas, pourquoi ne pouvez-vous pas utiliser findle -pathfiltre de? Il utilise des caractères génériques et n'a certainement pas besoin d'extension.
muru
@muru C'est intéressant; Je n'en savais rien -path. Au cours des 10 dernières minutes, j'ai trouvé la réponse: utilisez eval! Cela semble être plus simple que -path.
Paddy Landau
2
@terdon et muru et tout le monde: Merci. J'ai entendu ce que vous avez tous dit et je me suis rendu compte que je devrais faire mon script faire une chose et laisser Bash globbing passer plusieurs fichiers ou chemins vers le script. J'ai ainsi modifié mon script. Il fonctionne bien et correspond mieux à la philosophie Linux. Merci encore!
Paddy Landau
0

J'ai enfin trouvé la réponse.

Ajoutez une barre oblique inverse à tous les espaces:

SEARCH='te*/my files/more'
SEARCH=${SEARCH// /\\ }

À ce stade, SEARCHcontient te*/my\ files/more.

Ensuite, utilisez eval.

eval find ${SEARCH} -print

C'est si simple! L'utilisation evalcontourne l'interprétation qui ${SEARCH}provient d'une variable.

Paddy Landau
la source
@terdon Merci pour l'avertissement. Retour à la planche à dessin!
Paddy Landau
Ouais, c'est étonnamment délicat. Je viens de mettre à jour ma réponse avec une autre approche, pourquoi ne pas utiliser la globalisation à la place? Si cela ne fonctionne toujours pas, je vous suggère de poster une nouvelle question sur Unix et Linux expliquant quel est votre objectif final et pourquoi vous devez avoir le modèle comme variable. Ce type de chose est plus susceptible d'obtenir une meilleure réponse.
terdon