Quero usar find
para 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/more
e 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 find
comando 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 eval
assim 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 find
completamente 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 globstar
opçã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 $SEARCH
a 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, SEARCH
contém te*/my\ files/more
.
Então, use eval
.
eval find ${SEARCH} -print
É simples assim! O uso eval
ignora a interpretação que ${SEARCH}
vem de uma variável.