Comment puis-je déterminer par programme si un nom de fichier correspond à un modèle glob de shell?

13

Je voudrais dire si une chaîne $stringcorrespondrait à un modèle glob $pattern. $stringpeut ou peut ne pas être le nom d'un fichier existant. Comment puis-je faire ceci?

Supposons les formats suivants pour mes chaînes d'entrée:

string="/foo/bar"
pattern1="/foo/*"
pattern2="/foo/{bar,baz}"

Je voudrais trouver un idiome bash qui détermine si $stringserait compensée par $pattern1, $pattern2ou tout autre motif glob arbitraire. Voici ce que j'ai essayé jusqu'à présent:

  1. [[ "$string" = $pattern ]]

    Cela fonctionne presque, sauf qu'il $patternest interprété comme un modèle de chaîne et non comme un modèle global.

  2. [ "$string" = $pattern ]

    Le problème avec cette approche est qu'elle $patternest étendue et que la comparaison des chaînes est effectuée entre $stringet l'expansion de $pattern.

  3. [[ "$(find $pattern -print0 -maxdepth 0 2>/dev/null)" =~ "$string" ]]

    Celui-ci fonctionne, mais uniquement s'il $stringcontient un fichier qui existe.

  4. [[ $string =~ $pattern ]]

    Cela ne fonctionne pas car l' =~opérateur fait $patternen sorte qu'il soit interprété comme une expression régulière étendue et non comme un modèle glob ou générique.

jayhendren
la source
2
Le problème que vous allez rencontrer est que ce {bar,baz}n'est pas un modèle. C'est l'expansion des paramètres. Une différence subtile mais critique en cela {bar,baz}est développée très tôt dans de multiples arguments, baret baz.
Patrick
Si le shell peut développer des paramètres, il peut sûrement dire si une chaîne est une expansion potentielle d'un glob.
jayhendren
essayez ceci a = ls /foo/* maintenant vous pouvez égaler dans un
Hackaholic
1
@Patrick: après avoir lu la page de manuel de bash, j'ai appris qu'il foo/{bar,baz}s'agit en fait d'une extension d'accolade (pas d'une extension de paramètre) alors que foo/*c'est une extension de nom de chemin. $stringest l'expansion des paramètres. Tout cela se fait à différents moments et par différents mécanismes.
jayhendren
@jayhendren @Patrick a raison, puis vous avez appris que votre question n'est finalement pas ce que le titre laisse croire. Vous souhaitez plutôt faire correspondre une chaîne à différents types de modèles. Si vous souhaitez faire une correspondance stricte par rapport à un modèle de glob, l' caseinstruction exécute l'extension de nom de chemin ("globbing") conformément au manuel de Bash.
Mike S

Réponses:

8

Il n'y a pas de solution générale à ce problème. La raison en est que, dans bash, l'expansion d'accolade (c'est-à-dire, {pattern1,pattern2,...}et l'expansion de nom de fichier (alias les modèles glob)) sont considérées comme des choses distinctes et développées dans différentes conditions et à différents moments. Voici la liste complète des extensions que bash effectue:

  • expansion de l'accolade
  • expansion tilde
  • expansion des paramètres et des variables
  • substitution de commande
  • expansion arithmétique
  • division de mot
  • extension du nom de chemin

Étant donné que nous ne nous soucions que d'un sous-ensemble de ces derniers (peut-être l'expansion d'accolade, de tilde et de chemin d'accès), il est possible d'utiliser certains modèles et mécanismes pour restreindre l'expansion de manière contrôlable. Par exemple:

#!/bin/bash
set -f

string=/foo/bar

for pattern in /foo/{*,foo*,bar*,**,**/*}; do
    [[ $string == $pattern ]] && echo "$pattern matches $string"
done

L'exécution de ce script génère la sortie suivante:

/foo/* matches /foo/bar
/foo/bar* matches /foo/bar
/foo/** matches /foo/bar

Cela fonctionne car set -fdésactive l'expansion des noms de chemin, donc seules l'expansion entre accolades et l'expansion tilde se produisent dans l'instruction for pattern in /foo/{*,foo*,bar*,**,**/*}. Nous pouvons ensuite utiliser l'opération [[ $string == $pattern ]]de test pour tester l'expansion du nom de chemin une fois que l'extension d'accolade a déjà été effectuée.

jayhendren
la source
7

Je ne crois pas que ce {bar,baz} soit un modèle de glob de shell (bien que ce soit certainement le cas /foo/ba[rz]) mais si vous voulez savoir si des $stringcorrespondances $patternvous pouvez le faire:

case "$string" in 
($pattern) put your successful execution statement here;;
(*)        this is where your failure case should be   ;;
esac

Vous pouvez en faire autant que vous le souhaitez:

case "$string" in
($pattern1) do something;;
($pattern2) do differently;;
(*)         still no match;;
esac
mikeserv
la source
1
Salut @mikeserv, comme indiqué dans les commentaires et la réponse que j'ai fournie ci-dessus, j'ai déjà appris que ce que vous dites est vrai - {bar,baz}n'est pas un modèle global. J'ai déjà trouvé une solution à ma question qui en tient compte.
jayhendren
3
+1 Cela répond à la question exactement comme indiqué dans le titre et la première phrase. bien que la question confonde plus tard d'autres modèles avec des modèles glob globulaires.
Mike S
3

Comme Patrick l'a souligné, vous avez besoin d'un "type différent" de motif:

[[ /foo/bar == /foo/@(bar|baz) ]]


string="/foo/bar"
pattern="/foo/@(bar|baz)"
[[ $string == $pattern ]]

Les citations n'y sont pas nécessaires.

Hauke ​​Laging
la source
D'accord, cela fonctionne, mais à proprement parler, cela ne répond pas à ma question. Par exemple, je voudrais considérer les modèles qui proviennent d'une autre source, c'est-à-dire que les modèles sont hors de mon contrôle.
jayhendren
@jayhendren Ensuite, vous devrez probablement d'abord convertir le modèle entrant en ces acceptations bash.
Hauke ​​Laging
Donc, tout ce que vous avez vraiment fait est de transformer ma question de "comment savoir si un nom de fichier est une extension potentielle d'une expression" en "comment convertir des modèles de nom de fichier de style bash normaux en modèles de glob étendus de style bash".
jayhendren
@jayhendren Considérant que ce que vous voulez semble impossible "tout ce que vous avez vraiment fait" me semble un peu étrange mais peut-être que c'est juste une langue étrangère. Si vous souhaitez poser cette nouvelle question, vous devez expliquer à quoi ressemblent les modèles d'entrée. C'est peut-être une sedopération simple .
Hauke ​​Laging
1
@HaukeLaging est correct. Les gens qui viennent ici pour comprendre comment se confronter aux modèles globaux (alias "Expansion du nom de chemin") sont susceptibles de devenir confus, comme je l'ai fait, parce que le titre dit "modèle glob globulaire" mais le contenu de sa question utilise un modèle non global . À un moment donné, Jayhendren a appris la différence, mais sa confusion initiale est ce qui a amené Hauke ​​à répondre comme il l'a fait.
Mike S
1

J'utiliserais ainsi grep:

#!/bin/bash
string="/foo/bar"
pattern1="/foo/*"
pattern2="/foo/{bar,baz}"

if echo $string | grep -e "$pattern1" > /dev/null; then
    echo $string matches $pattern1
fi

if echo $string | grep -e "$pattern2" > /dev/null; then
    echo $string matches $pattern2
fi

Ce qui donne la sortie:

./test2.bsh 
/foo/bar matches /foo/*
musaraigne
la source
-2

en zsh:

[[ $string = $~pattern ]] && print true
llua
la source
1
La question indique que le shell bash sera utilisé.
jayhendren