Wie kann ich programmgesteuert feststellen, ob ein Dateiname mit einem Shell-Glob-Muster übereinstimmt?

13

Ich würde gerne sagen, ob ein String $stringmit einem Glob-Muster übereinstimmt $pattern. $stringkann oder kann nicht der Name einer vorhandenen Datei sein. Wie kann ich das machen?

Angenommen, die folgenden Formate für meine Eingabezeichenfolgen:

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

Ich möchte eine bash Idiom finden, wenn bestimmt $stringwürde durch abgestimmt werden $pattern1, $pattern2oder jedem anderen beliebigen glob Muster. Folgendes habe ich bisher versucht:

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

    Dies funktioniert fast, außer dass dies $patternals String-Muster und nicht als Glob-Muster interpretiert wird.

  2. [ "$string" = $pattern ]

    Das Problem bei diesem Ansatz ist, dass $patternerweitert wird und dann ein Zeichenfolgenvergleich zwischen $stringund der Erweiterung von durchgeführt wird $pattern.

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

    Dieser funktioniert, aber nur, wenn er $stringeine Datei enthält, die existiert.

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

    Dies funktioniert nicht, da der =~Operator bewirkt $pattern, dass er als erweiterter regulärer Ausdruck interpretiert wird, nicht als Glob- oder Platzhaltermuster.

Jayhendren
quelle
2
Das Problem, auf das Sie stoßen werden, ist, dass {bar,baz}es sich nicht um ein Muster handelt. Es ist eine Parametererweiterung. Dezent , aber entscheidender Unterschied, dass {bar,baz}sehr früh erweitert auf in mehrere Argumente, barund baz.
Patrick
Wenn die Shell Parameter erweitern kann, kann sie sicher erkennen, ob ein String eine potenzielle Erweiterung eines Glob ist.
Jayhendren
ls /foo/*
Probieren
1
@Patrick: Nachdem ich die Bash-Manpage durchgelesen habe, habe ich erfahren, dass foo/{bar,baz}es sich tatsächlich um eine Klammererweiterung (keine Parametererweiterung) handelt, während foo/*es sich um eine Pfadnamenerweiterung handelt. $stringist Parametererweiterung. Dies alles geschieht zu unterschiedlichen Zeiten und durch unterschiedliche Mechanismen.
Jayhendren
@jayhendren @Patrick ist richtig, und dann haben Sie gelernt, dass Ihre Frage letztendlich nicht das ist, was der Titel dazu führt, dass man glaubt. Vielmehr möchten Sie eine Zeichenfolge mit verschiedenen Arten von Mustern abgleichen. Wenn Sie eine strikte Übereinstimmung mit einem Glob-Muster caseerzielen möchten , führt die Anweisung die Pfadnamenerweiterung ("Globbing") gemäß dem Bash-Handbuch aus.
Mike S

Antworten:

8

Für dieses Problem gibt es keine allgemeine Lösung. Der Grund dafür ist, dass geschweifte Klammern (dh {pattern1,pattern2,...}und Dateinamen (auch Glob-Muster genannt)) unter verschiedenen Bedingungen und zu verschiedenen Zeiten als separate Elemente betrachtet werden. Hier ist die vollständige Liste der von bash durchgeführten Erweiterungen:

  • Klammererweiterung
  • Tilde-Erweiterung
  • Parameter- und Variablenerweiterung
  • Befehlsersetzung
  • arithmetische Erweiterung
  • Worttrennung
  • Pfadnamenerweiterung

Da wir uns nur um eine Teilmenge dieser Elemente kümmern (z. B. die Erweiterung von Klammern, Tilden und Pfadnamen), können bestimmte Muster und Mechanismen verwendet werden, um die Erweiterung auf kontrollierbare Weise einzuschränken. Zum Beispiel:

#!/bin/bash
set -f

string=/foo/bar

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

Das Ausführen dieses Skripts generiert die folgende Ausgabe:

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

Dies funktioniert, weil set -fdie Pfadnamenerweiterung deaktiviert ist, sodass in der Anweisung nur die Klammererweiterung und die Tildeerweiterung erfolgen for pattern in /foo/{*,foo*,bar*,**,**/*}. Mit der Testoperation können wir dann [[ $string == $pattern ]]gegen die Pfadnamenerweiterung testen, nachdem die geschweifte Klammer bereits ausgeführt wurde.

Jayhendren
quelle
7

Ich glaube nicht , dass {bar,baz} ist ein Shell - Glob - Muster (obwohl sicher /foo/ba[rz]ist) , aber wenn Sie wissen wollen , ob $stringStreichhölzer $patternSie tun können:

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

Sie können so viele machen, wie Sie möchten:

case "$string" in
($pattern1) do something;;
($pattern2) do differently;;
(*)         still no match;;
esac
mikeserv
quelle
1
Hallo @mikeserv, wie in den Kommentaren und der Antwort, die ich oben gegeben habe, angegeben, habe ich bereits erfahren, dass das, was Sie sagen, wahr ist - {bar,baz}kein Glob-Muster ist. Ich habe bereits eine Lösung für meine Frage gefunden, die dies berücksichtigt.
Jayhendren
3
+1 Hiermit wird die Frage genau so beantwortet, wie sie im Titel und im ersten Satz angegeben ist. obwohl die Frage später andere Muster mit Shell-Glob-Mustern in Konflikt bringt.
Mike S
3

Wie Patrick betonte, brauchst du eine "andere Art" von Muster:

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


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

Anführungszeichen sind dort nicht erforderlich.

Hauke ​​Laging
quelle
Ok, das funktioniert, aber genau genommen beantwortet es meine Frage nicht. Zum Beispiel möchte ich Muster berücksichtigen, die von einer anderen Quelle stammen, dh die Muster liegen außerhalb meiner Kontrolle.
Jayhendren
@jayhendren Dann musst du wohl erst das eingehende Muster konvertieren, um das bash akzeptiert.
Hauke ​​Laging
Alles, was Sie wirklich getan haben, ist, meine Frage von "Wie kann ich feststellen, ob ein Dateiname eine potenzielle Erweiterung eines Ausdrucks darstellt?" In "Wie kann ich normale Dateinamenmuster im Bash-Stil in erweiterte Glob-Muster im Bash-Stil konvertieren?"
Jayhendren
@jayhendren Wenn man bedenkt, dass das, was man will, unmöglich erscheint "alles, was man wirklich getan hat", hört sich für mich etwas seltsam an, aber vielleicht ist das nur eine fremdsprachige Sache. Wenn Sie diese neue Frage stellen möchten, müssen Sie erklären, wie die Eingabemuster aussehen. Vielleicht ist es eine einfache sedOperation.
Hauke ​​Laging
1
@HaukeLaging ist korrekt. Leute, die hierher kommen, um herauszufinden, wie man mit Glob-Mustern übereinstimmt (auch bekannt als "Pathname Expansion"), können verwirrt werden, da der Titel "Shell Glob Pattern" lautet, der Inhalt seiner Frage jedoch ein Nicht-Glob-Muster verwendet . Irgendwann lernte Jayhendren den Unterschied kennen, aber seine anfängliche Verwirrung veranlasste Hauke, so zu antworten, wie er es tat.
Mike S
1

Ich würde grep so benutzen:

#!/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

Welches gibt die Ausgabe:

./test2.bsh 
/foo/bar matches /foo/*
Spitzmaus
quelle
-2

in zsh:

[[ $string = $~pattern ]] && print true
llua
quelle
1
Die Frage besagt, dass die Bash-Shell verwendet wird.
Jayhendren