Jak mogę programowo stwierdzić, czy nazwa pliku pasuje do wzorca globu powłoki?

13

Chciałbym powiedzieć, czy ciąg $stringpasowałby do wzorca globu $pattern. $stringmoże, ale nie musi, być nazwą istniejącego pliku. W jaki sposób mogę to zrobić?

Przyjmij następujące formaty dla moich ciągów wejściowych:

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

Chciałbym znaleźć idiom bash, który określa, czy $stringbędzie dopasowane $pattern1, $pattern2lub jakiegokolwiek innego arbitralnego glob wzorca. Oto, co próbowałem do tej pory:

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

    To prawie działa, ale $patternjest interpretowane jako wzorzec łańcucha, a nie jako wzorzec globalny.

  2. [ "$string" = $pattern ]

    Problem z tym podejściem polega na tym, że $patternjest on rozwijany, a następnie przeprowadzane jest porównywanie ciągów znaków $stringz rozszerzeniem $pattern.

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

    Ten działa, ale tylko wtedy, gdy $stringzawiera plik, który istnieje.

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

    To nie działa, ponieważ =~operator powoduje $patterninterpretację jako rozszerzone wyrażenie regularne, a nie wzorzec globalny lub symbol wieloznaczny.

jayhendren
źródło
2
Problem, na który wpadniesz, {bar,baz}to nie jest wzór. To rozszerzenie parametrów. Subtelna, ale krytyczna różnica w tym {bar,baz}jest bardzo wcześnie rozszerzona na wiele argumentów bari baz.
Patrick,
Jeśli powłoka może rozszerzać parametry, to z pewnością może stwierdzić, czy łańcuch jest potencjalnym rozszerzeniem globu.
jayhendren
spróbuj a = ls /foo/* teraz możesz dopasować
Hackaholic
1
@Patrick: po przeczytaniu strony podręcznika bash dowiedziałem się, że foo/{bar,baz}tak naprawdę jest to rozwinięcie nawiasów klamrowych (a nie rozszerzenie parametrów), podczas gdy foo/*jest to rozwinięcie nazwy ścieżki. $stringto rozszerzenie parametrów. Wszystko to odbywa się w różnym czasie i za pomocą różnych mechanizmów.
jayhendren
@jayhendren @Patrick ma rację, a potem dowiedziałeś się, że twoje pytanie ostatecznie nie brzmi, w co tytuł wierzy. Zamiast tego chcesz dopasować sznurek do różnego rodzaju wzorów. Jeśli chcesz ściśle dopasować do wzorca globu, caseinstrukcja wykonuje rozszerzenie nazwy ścieżki („globbing”) zgodnie z instrukcją Bash.
Mike S

Odpowiedzi:

8

Nie ma ogólnego rozwiązania tego problemu. Powodem jest to, że w bash, rozwijanie nawiasów (tj. {pattern1,pattern2,...}I rozwijanie nazw plików (inaczej wzorce glob) są uważane za osobne rzeczy i rozwijane w różnych warunkach i w różnych czasach. Oto pełna lista rozszerzeń, które wykonuje bash:

  • ekspansja nawiasów klamrowych
  • ekspansja tyldy
  • ekspansja parametrów i zmiennych
  • zastępowanie poleceń
  • ekspansja arytmetyczna
  • dzielenie słów
  • rozwinięcie nazwy ścieżki

Ponieważ troszczymy się tylko o ich podzbiór (być może rozwinięcie nawiasu klamrowego, tyldy i ścieżki), możliwe jest użycie pewnych wzorców i mechanizmów w celu ograniczenia ekspansji w kontrolowany sposób. Na przykład:

#!/bin/bash
set -f

string=/foo/bar

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

Uruchomienie tego skryptu generuje następujące dane wyjściowe:

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

Działa to, ponieważ set -fwyłącza interpretację nazw ścieżek, dlatego w instrukcji występują tylko interpretacja nawiasów i interpretacja tyldy for pattern in /foo/{*,foo*,bar*,**,**/*}. Następnie możemy użyć operacji testowej, [[ $string == $pattern ]]aby przetestować interpretację ścieżki po tym, jak interpretacja nawiasu została już wykonana.

jayhendren
źródło
7

Nie wierzę, że {bar,baz} jest to wzór globu powłoki (choć z pewnością tak /foo/ba[rz]jest), ale jeśli chcesz wiedzieć, czy $stringdopasowania $patternmożesz zrobić:

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

Możesz zrobić tyle, ile chcesz:

case "$string" in
($pattern1) do something;;
($pattern2) do differently;;
(*)         still no match;;
esac
mikeserv
źródło
1
Cześć @ mikeserv, jak wskazano w komentarzach i odpowiedzi, którą podałem powyżej, już nauczyłem się, że to, co mówisz, jest prawdą - {bar,baz}nie jest globalnym wzorcem. Już znalazłem rozwiązanie mojego pytania, które bierze to pod uwagę.
jayhendren
3
+1 To odpowiada dokładnie pytaniu podanemu w tytule i pierwszym zdaniu. chociaż pytanie to później łączy inne wzorce z wzorcami globu powłoki.
Mike S
3

Jak zauważył Patrick, potrzebujesz „innego rodzaju” wzoru:

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


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

Cytaty nie są tam konieczne.

Hauke ​​Laging
źródło
Ok, to działa, ale ściśle mówiąc, nie odpowiada na moje pytanie. Na przykład chciałbym rozważyć wzorce pochodzące z innego źródła, tj. Wzorce są poza moją kontrolą.
jayhendren
@jayhendren Prawdopodobnie musisz najpierw przekonwertować przychodzący wzorzec na akceptacje bash.
Hauke ​​Laging
Więc wszystko, co tak naprawdę zrobiłeś, przekształciło moje pytanie z „skąd mam wiedzieć, czy nazwa pliku jest potencjalnym rozszerzeniem wyrażenia” na „jak przekonwertować normalne wzorce nazw plików w stylu bash na rozszerzone wzorce globów w stylu bash”.
jayhendren
@jayhendren Biorąc pod uwagę, że to, czego chcesz, wydaje się niemożliwe, „wszystko, co naprawdę zrobiłeś”, brzmi dla mnie trochę dziwnie, ale być może jest to po prostu język obcy. Jeśli chcesz zadać to nowe pytanie, musisz wyjaśnić, jak wyglądają wzorce wejściowe. Może to prosta sedoperacja.
Hauke ​​Laging
1
@HaukeLaging jest poprawny. Ludzie, którzy przychodzą tutaj, aby dowiedzieć się, jak dopasować się do wzorców globu (aka „Rozszerzenie nazwy ścieżki”), podobnie jak ja, mogą się pomylić, ponieważ tytuł mówi „wzorzec globu powłoki”, ale treść jego pytania wykorzystuje wzorzec inny niż glob . W pewnym momencie jayhendren dowiedział się o różnicy, ale jego początkowe zamieszanie spowodowało, że Hauke ​​odpowiedział tak, jak zrobił.
Mike S
1

W ten sposób użyłbym 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

Co daje wynik:

./test2.bsh 
/foo/bar matches /foo/*
ryjówka
źródło
-2

w zsh:

[[ $string = $~pattern ]] && print true
llua
źródło
1
Pytanie mówi, że zostanie użyta powłoka bash.
jayhendren