Converter glob para 'localizar'

10

De novo e de novo tive esse problema: Eu tenho um glob, que corresponde exatamente aos arquivos corretos, mas causa Command line too long . Toda vez que o converti em alguma combinação de find e grep que funciona para a situação específica, mas que não é 100% equivalente.

Por exemplo:

./foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg

Existe uma ferramenta para converter globs em find expressões de que não conheço? Ou existe uma opção para que find corresponda à glob sem corresponder a mesma glob em um subdir (por exemplo, foo/*.jpg não pode corresponder a bar/foo/*.jpg )?

    
por Ole Tange 18.08.2017 / 10:40

4 respostas

14

Se o problema é que você obtém um erro de lista de argumentos-é-muito-longo, use um loop ou um shell embutido. Enquanto command glob-that-matches-too-much pode cometer erros, for f in glob-that-matches-too-much não, então você pode apenas fazer:

for f in foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg
do
    something "$f"
done

O loop pode ser terrivelmente lento, mas deve funcionar.

Ou:

printf "%s
$ cat /usr/share/**/* > /dev/null
zsh: argument list too long: cat
$ printf "%s\n" /usr/share/**/* | wc -l
165606
" foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg | xargs -r0 something

( printf está embutido na maioria dos shells, o acima funciona em torno da limitação da chamada do sistema execve() )

for f in foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg
do
    something "$f"
done

Também trabalha com o bash. Não sei exatamente onde isso está documentado.

glob2regpat() e do Python fnmatch.translate() pode converter globs para regexes, mas ambos também usam .* para * , correspondendo em / .

    
por 18.08.2017 / 11:42
8

find (para os predicados padrão -name / -path ) usa padrões curinga assim como globs (observe que {a,b} não é um operador glob; após a expansão, você obtém dois globs). A principal diferença é o tratamento de barras (e os arquivos de ponto e diretórios não sendo tratados especialmente em find ). * em globs não abrangerá vários diretórios. */*/* fará com que até 2 níveis de diretórios sejam listados. Adicionar -path './*/*/*' corresponderá a todos os arquivos com pelo menos 3 níveis de profundidade e não impedirá que find liste o conteúdo de qualquer diretório em qualquer profundidade.

Para esse particular

./foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg

Par de globs, é fácil de traduzir, você está querendo diretórios na profundidade 3, então você pode usar:

find . -mindepth 3 -maxdepth 3 \
       \( -path './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' -o \
          -path './foo*bar/quux[A-Z]/pic[0-9][0-9][0-9][0-9]?.jpg' \) \
       -exec cmd {} +

(ou -depth 3 com algumas implementações find ). Ou POSIXly:

find . -path './*/*/*' -prune \
       \( -path './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' -o \
          -path './foo*bar/quux[A-Z]/pic[0-9][0-9][0-9][0-9]?.jpg' \) \
       -exec cmd {} +

O que garantiria que os * e ? não poderiam corresponder a / caracteres.

( find , ao contrário de globs, leria o conteúdo de diretórios diferentes de foo*bar no diretório atual¹, e não ordenaria a lista de arquivos. Mas se deixarmos de lado o problema de que o que é correspondido por [A-Z] ou o comportamento de * / ? em relação a caracteres inválidos não é especificado, você obterá a mesma lista de arquivos).

Mas, em qualquer caso, como @muru mostrou , não é necessário recorrer a find se for apenas dividir a lista de arquivos em várias execuções para contornar o limite da chamada do sistema execve() . Alguns shells como zsh (com zargs ) ou ksh93 (com command -x ) ainda têm suporte embutido para isso.

Com zsh (cujas globs também têm o equivalente a -type f e a maioria dos outros find predicados), por exemplo:

autoload zargs # if not already in ~/.zshrc
zargs ./foo*bar/quux[A-Z](|.bak)/pic[0-9][0-9][0-9][0-9]?.jpg(.) -- cmd

( (|.bak) é um operador glob ao contrário de {,.bak} , o qualificador (.) glob é o equivalente de find ' -type f , adicione oN lá para ignorar a classificação como find , D para incluir arquivos de pontos (não se aplica a este glob))

¹ Para find rastrear a árvore de diretórios, como acontece com globs, você precisa de algo como:

find . ! -name . \( \
  \( -path './*/*' -o -name 'foo*bar' -o -prune \) \
  -path './*/*/*' -prune -name 'pic[0-9][0-9][0-9][0-9]?.jpg' -exec cmd {} + -o \
  \( ! -path './*/*' -o -name 'quux[A-Z]' -o -name 'quux[A-Z].bak' -o -prune \) \)

Isso é podar todos os diretórios no nível 1, exceto os foo*bar e todos no nível 2, exceto os quux[A-Z] ou quux[A-Z].bak e, em seguida, selecionar pic... em nível 3 (e podar todos os diretórios a esse nível).

    
por 18.08.2017 / 12:19
3

Você pode escrever um regex para encontrar correspondendo aos seus requisitos:

find . -regextype egrep -regex './foo[^/]*bar/quux[A-Z](\.bak)?/pic[0-9][0-9][0-9][0-9][^/]?\.jpg'
    
por 18.08.2017 / 11:20
0

Ao generalizar a nota na minha outra resposta , como uma resposta mais direta à sua pergunta, você pode usar esse script POSIX sh para converter o glob para uma expressão find :

#! /bin/sh -
glob=${1#./}
shift
n=$#
p='./*'

while true; do
  case $glob in
    (*/*)
      set -- "$@" \( ! -path "$p" -o -path "$p/*" -o -name "${glob%%/*}" -o -prune \)
      glob=${glob#*/} p=$p/*;;
    (*)
      set -- "$@" -path "$p" -prune -name "$glob"
      while [ "$n" -gt 0 ]; do
        set -- "$@" "$1"
        shift
        n=$((n - 1))
      done
      break;;
  esac
done
find . "$@"

Para ser usado com um padrão sh glob (portanto, não os dois globs do seu exemplo que usam expansão de chave ):

glob2find './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' \
  -type f -exec cmd {} +

(isso não ignora os arquivos de ponto nem os dot-dirs, exceto . e .. , e não classifica a lista de arquivos).

Essa só funciona com globs em relação ao diretório atual, sem componentes . ou .. . Com algum esforço, você poderia estendê-lo para qualquer glob, mais do que um glob ... Isso também poderia ser otimizado para que glob2find 'dir/*' não procure dir o mesmo era como seria para um padrão.

    
por 18.08.2017 / 15:24