Como passar o curinga '*' para o parâmetro path do comando find por meio de uma variável no script?

Como passar o curinga '*' para o parâmetro path do comando find por meio de uma variável no script?

Quero usar findpara localizar arquivos em um conjunto de pastas restritas por curingas, mas onde há espaços no nome do caminho.

Na linha de comando, isso é fácil. Todos os exemplos a seguir funcionam.

find  te*/my\ files/more   -print
find  te*/'my files'/more  -print
find  te*/my' 'files/more  -print

Eles encontrarão arquivos em, por exemplo, terminal/my files/moree tepid/my files/more.

No entanto, preciso que isso faça parte de um script; o que eu preciso é algo assim:

SEARCH='te*/my\ files/more'
find ${SEARCH} -print

Infelizmente, faça o que fizer, não consigo misturar curingas e espaços em um findcomando dentro de um script. O exemplo acima retorna os seguintes erros (observe a duplicação inesperada da barra invertida):

find: ‘te*/my\\’: No such file or directory
find: ‘files/more’: No such file or directory

Tentar usar aspas também falha.

SEARCH="te*/'my files'/more"
find ${SEARCH} -print

Isso retorna os seguintes erros, ignorando o significado das aspas:

find: ‘te*/'my’: No such file or directory
find: ‘files'/more’: No such file or directory

Aqui está mais um exemplo.

SEARCH='te*/my files/more'
find ${SEARCH} -print

Como esperado:

find: ‘te*/my’: No such file or directory
find: ‘files/more’: No such file or directory

Cada variação que tentei retorna um erro.

Eu tenho uma solução alternativa que é potencialmente perigosa porque retorna muitas pastas. Eu converto todos os espaços em um ponto de interrogação (curinga de um caractere) assim:

SEARCH='te*/my files/more'
SEARCH=${SEARCH// /?}       # Convert every space to a question mark.
find ${SEARCH} -print

Isso é equivalente a:

find te*/my?files/more -print

Isso retorna não apenas as pastas corretas terse/myxfiles/more, mas também o que não deveria.

Como posso conseguir o que estou tentando fazer? O Google não me ajudou :(

Responder1

Exatamente o mesmo comando deve funcionar bem em um script:

#!/usr/bin/env bash
find  te*/my\ files/ -print

Se vocêprecisarpara tê-lo como variável fica um pouco mais complexo:

#!/usr/bin/env bash
search='te*/my\ files/'
eval find "$search" -print

AVISO:

Usar evalassim não é seguro e pode resultar na execução de código arbitrário e possivelmente prejudicial se os nomes dos arquivos puderem conter determinados caracteres. VerPerguntas frequentes sobre bash 48para detalhes.

É melhor passar o caminho como argumento:

#!/usr/bin/env bash
find "$@" -name "file*"

Outra abordagem é evitar findcompletamente e usar os recursos e globs estendidos do bash:

#!/usr/bin/env bash
shopt -s globstar
for file in te*/my\ files/**; do echo "$file"; done

A globstaropção bash permite usar **para combinar recursivamente:

globstar
      If set, the pattern ** used in a pathname expansion con‐
      text will match all files and zero or  more  directories
      and  subdirectories.  If the pattern is followed by a /,
      only directories and subdirectories match.

Para fazê-lo funcionar 100% como localizar e incluir dotfiles (arquivos ocultos), use

#!/usr/bin/env bash
shopt -s globstar
shopt -s dotglob
for file in te*/my\ files/**; do echo "$file"; done

Você pode nivelá echo-los diretamente sem o loop:

echo te*/my\ files/**

Responder2

Que tal matrizes?

$ tree Desktop/ Documents/
Desktop/
└── my folder
    └── more
        └── file
Documents/
└── my folder
    ├── folder
    └── more

5 directories, 1 file
$ SEARCH=(D*/my\ folder)
$ find "${SEARCH[@]}" 
Desktop/my folder
Desktop/my folder/more
Desktop/my folder/more/file
Documents/my folder
Documents/my folder/more
Documents/my folder/folder

(*)se expande em uma matriz de tudo o que corresponde ao curinga. E "${SEARCH[@]}"se expande para todos os elementos da matriz ( [@]), com cada um deles citado individualmente.

Tardiamente, percebi que deveria ser capaz disso. Algo como:

find . -path 'D*/my folder/more/'

Responder3

Está um pouco desatualizado agora, mas, se isso puder ajudar alguém com esta questão, usar o símbolo de agrupamento do RE [[.space.]]sem citar $SEARCHa variável nos argumentos da linha de comando find está funcionando, desde que não haja caracteres especiais nos nomes de caminho expandidos no lugar do asterisco.

set -x
mkdir -p te{flon,nnis,rrine}/my\ files/more
SEARCH=te*/my[[.space.]]files/more
find $SEARCH

dê os seguintes resultados:

+ mkdir -p 'teflon/my files/more' 'tennis/my files/more' 'terrine/my files/more'
+ SEARCH='te*/my[[.space.]]files/more'
+ find 'teflon/my files/more' 'tennis/my files/more' 'terrine/my files/more'
teflon/my files/more
tennis/my files/more
terrine/my files/more

Para evitar o agrupamento de caracteres indesejados, pode-se substituir o asterisco ( *) por qualquer elemento de agrupamento (caracteres):

set -x
mkdir -p -- te{,-,_}{flon,nnis,rrine}/my\ files/more
SEARCH=te[[:alnum:][.space.][.hyphen.][.underscore.]]*/my[[.space.]]files/more
find $SEARCH

dando os seguintes resultados:

+ mkdir -p -- 'teflon/my files/more' 'tennis/my files/more' 'terrine/my files/more' \
'te-flon/my files/more' 'te-nnis/my files/more' 'te-rrine/my files/more' \
'te_flon/my files/more' 'te_nnis/my files/more' 'te_rrine/my files/more'
+ SEARCH='te[[:alnum:][.space.][.hyphen.][.underscore.]]*/my[[.space.]]files/more'
+ find 'te-flon/my files/more' 'te_flon/my files/more' 'teflon/my files/more' \
'te-nnis/my files/more' 'te_nnis/my files/more' 'tennis/my files/more' \
'te-rrine/my files/more' 'te_rrine/my files/more' 'terrine/my files/more'
te-flon/my files/more
te_flon/my files/more
teflon/my files/more
te-nnis/my files/more
te_nnis/my files/more
tennis/my files/more
te-rrine/my files/more
te_rrine/my files/more
terrine/my files/more

Observe que para encurtar a linha, [.hyphen.]pode ser substituído por [.-.]ou - e [.underscore.]pode ser substituído por [._.]ou _.



Truncando linhas com barras invertidas ( \) adicionadas por mim para fins de legibilidade.

Responder4

Finalmente descobri a resposta.

Adicione uma barra invertida a todos os espaços:

SEARCH='te*/my files/more'
SEARCH=${SEARCH// /\\ }

Neste ponto, SEARCHcontém te*/my\ files/more.

Então, use eval.

eval find ${SEARCH} -print

É simples assim! O uso evalignora a interpretação que ${SEARCH}vem de uma variável.

informação relacionada