Como posso saber programaticamente se um nome de arquivo corresponde a um padrão de shell glob?


13

Gostaria de dizer se uma string $stringseria correspondida por um padrão glob $pattern. $stringpode ou não ser o nome de um arquivo existente. Como posso fazer isso?

Suponha os seguintes formatos para minhas seqüências de caracteres de entrada:

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

Eu gostaria de encontrar uma linguagem bash que determina se $stringseria acompanhado por $pattern1, $pattern2ou qualquer outro padrão glob arbitrária. Aqui está o que eu tentei até agora:

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

    Isso quase funciona, exceto que $patterné interpretado como um padrão de cadeia e não como um padrão glob.

  2. [ "$string" = $pattern ]

    O problema com essa abordagem é que $patterné expandido e a comparação de cadeias é executada entre $stringe a expansão de $pattern.

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

    Este funciona, mas somente se $stringcontém um arquivo que existe.

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

    Isso não funciona porque o =~operador faz $patterncom que seja interpretado como uma expressão regular estendida, não como um padrão glob ou curinga.


2
O problema que você encontrará é que {bar,baz}não é um padrão. É expansão de parâmetros. A diferença sutil, porém crítica, {bar,baz}é expandida muito cedo em vários argumentos, bare baz.
Patrick Patrick

Se o shell pode expandir parâmetros, certamente pode dizer se uma string é uma expansão potencial de uma glob.
Jayhendren #

tentar dar um presente a = ls /foo/* agora você pode combinar em um
Hackaholic

1
@ Patrick: depois de ler a página de manual do bash, aprendi que foo/{bar,baz}na verdade é uma expansão entre chaves (não uma expansão de parâmetro) enquanto foo/*é uma expansão de nome de caminho. $stringé expansão de parâmetro. Tudo isso é feito em momentos diferentes e por mecanismos diferentes.
Jayhendren #

@jayhendren @Patrick está certo, e então você aprendeu que sua pergunta não é no que o título leva a acreditar. Em vez disso, você deseja combinar uma sequência com vários tipos de padrões. Se você deseja corresponder estritamente a um padrão glob, a caseinstrução executa Expansão do nome do caminho ("globbing") conforme o manual do Bash.
Mike S

Respostas:


8

Não há solução geral para esse problema. O motivo é que, no bash, a expansão de chaves (ou seja, {pattern1,pattern2,...}expansão de nome de arquivo (também conhecida como padrões glob)) é considerada uma coisa separada e expandida sob diferentes condições e em diferentes momentos.Aqui está a lista completa das expansões executadas pelo bash:

  • cinta de expansão
  • expansão de til
  • expansão de parâmetro e variável
  • substituição de comando
  • expansão aritmética
  • divisão de palavras
  • expansão do nome do caminho

Como nos preocupamos apenas com um subconjunto deles (talvez expansão entre chaves, til e nome do caminho), é possível usar certos padrões e mecanismos para restringir a expansão de maneira controlável. Por exemplo:

#!/bin/bash
set -f

string=/foo/bar

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

A execução desse script gera a seguinte saída:

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

Isso funciona porque set -fa expansão desativa caminho, portanto, apenas a expansão cinta e expansão til ocorrer na declaração for pattern in /foo/{*,foo*,bar*,**,**/*}. Em seguida, podemos usar a operação [[ $string == $pattern ]]de teste para testar a expansão do nome do caminho após a expansão da chave já ter sido executada.


7

Eu não acredito que esse {bar,baz} seja um padrão shell glob (embora certamente /foo/ba[rz]seja), mas se você quiser saber se $stringcorresponde, $patternpode:

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

Você pode fazer quantas quiser:

case "$string" in
($pattern1) do something;;
($pattern2) do differently;;
(*)         still no match;;
esac

1
Olá @mikeserv, conforme indicado nos comentários e na resposta que forneci acima, eu já aprendi que o que você diz é verdade - {bar,baz}não é um padrão global. Eu já criei uma solução para minha pergunta que leva isso em consideração.
Jayhendren #

3
+1 Isso responde à pergunta exatamente como indicado no título e na primeira frase. embora a questão posteriormente funde outros padrões com padrões de globos de casca.
Mike S

3

Como Patrick apontou, você precisa de um "tipo diferente" de padrão:

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


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

Citações não são necessárias lá.


Ok, isso funciona, mas estritamente falando, não responde à minha pergunta. Por exemplo, gostaria de considerar padrões provenientes de outra fonte, ou seja, os padrões estão fora de meu controle.
Jayhendren #

@jayhendren Então você provavelmente precisará primeiro converter o padrão de entrada para aqueles que o bash aceita.
Hauke ​​Laging

Então, tudo o que você realmente fez foi transformar minha pergunta de "como saber se um nome de arquivo é uma expansão potencial de uma expressão" para "como converter padrões normais de nome de arquivo no estilo bash para padrões glob estendidos no estilo bash".
Jayhendren #

@jayhendren Considerando que o que você quer parece impossível "tudo o que você realmente fez" me parece um pouco estranho, mas talvez isso seja apenas uma coisa de língua estrangeira. Se você quiser fazer esta nova pergunta, deverá explicar como são os padrões de entrada. Talvez seja uma sedoperação simples .
Hauke ​​Laging 4/11/14

1
@HaukeLaging está correto. As pessoas que vêm aqui para descobrir como se adequar aos padrões glob (também conhecido como "expansão do nome do caminho") podem ficar confusas, como eu, porque o título diz "padrão glob shell", mas o conteúdo de sua pergunta usa um padrão não glob. . Em algum momento, Jayhendren aprendeu sobre a diferença, mas sua confusão inicial foi o que levou Hauke ​​a responder da maneira que ele fez.
Mike S

1

Eu usaria o grep assim:

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

O que fornece a saída:

./test2.bsh 
/foo/bar matches /foo/*

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.