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

13

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

Suponha os seguintes formatos para minhas strings de entrada:

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

Gostaria de encontrar uma linguagem bash que determine se $string seria correspondido por $pattern1 , $pattern2 ou qualquer outro padrão glob arbitrário. Aqui está o que eu tentei até agora:

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

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

  2. [ "$string" = $pattern ]

    O problema com essa abordagem é que $pattern é expandido e, em seguida, a comparação de cadeia é executada entre $string e a expansão de $pattern .

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

    Este funciona, mas somente se $string contiver um arquivo que exista.

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

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

por jayhendren 04.11.2014 / 21:10

5 respostas

7

Não há solução geral para esse problema. A razão é que, no bash, a expansão de chave (por exemplo, {pattern1,pattern2,...} e expansão de nome de arquivo (padrões glob) são consideradas coisas separadas e expandidas sob diferentes condições e em diferentes momentos. Aqui está a lista completa de expansões que o bash executa: / p>

  • expansão de chave
  • expansão de til
  • parâmetro e expansão de variáveis
  • substituição de comandos
  • expansão aritmética
  • divisão de palavras
  • expansão do nome do caminho

Como nos preocupamos apenas com um subconjunto desses (talvez a expansão de chave, til e 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 deste script gera a seguinte saída:

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

Isso funciona porque set -f desabilita a expansão do nome do caminho, portanto, apenas expansão de expansão e expansão de til ocorrem na instrução for pattern in /foo/{*,foo*,bar*,**,**/*} . Podemos então usar a operação de teste [[ $string == $pattern ]] para testar a expansão do nome do caminho após a expansão da chave já ter sido executada.

    
por 04.11.2014 / 22:34
7

Eu não acredito que {bar,baz} seja um padrão shell glob (embora certamente /foo/ba[rz] é) mas se você quiser saber se $string corresponde $pattern você pode fazer:

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

Você pode fazer quantos quiser:

case "$string" in
($pattern1) do something;;
($pattern2) do differently;;
(*)         still no match;;
esac
    
por 05.11.2014 / 00:37
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 ]]

As citações não são necessárias aqui.

    
por 04.11.2014 / 21:15
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 dá a saída:

./test2.bsh 
/foo/bar matches /foo/*
    
por 05.11.2014 / 01:47
-2

no zsh:

[[ $string = $~pattern ]] && print true
    
por 05.11.2014 / 07:08