Como passar o caractere curinga '*' para o parâmetro path do comando find através de uma variável no script?

9

Eu quero usar findpara encontrar arquivos em um conjunto de pastas restritas por curingas, mas onde existem espaços no nome do caminho.

Na linha de comando, isso é fácil. Os exemplos a seguir todos 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, eu preciso que isso faça parte de um script; o que eu preciso é algo como isto:

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

Infelizmente, não importa o que eu faça, não consigo misturar curingas e espaços em um findcomando em 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

A tentativa de usar aspas também falha.

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

Isso retorna os seguintes erros, tendo ignorado 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

Toda variação que eu tentei retorna um erro.

Eu tenho uma solução alternativa, que é potencialmente perigosa porque retorna muitas pastas. 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, mas também o terse/myxfiles/moreque não deveria.

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

Paddy Landau
fonte
@KasiyA Estou usando o bash; você deve estar usando outra coisa, como eu nunca vi essa construção antes. O comando resulta SEARCH: command not foundcom o comando find -printsendo executado.
Paddy Landau
Um tiro no escuro, mas e as citações? find "${SEARCH}" -print?
precisa
@AlaaAli Não, não funciona, porque a citação impede que o Bash use os curingas. Ele procurará um caminho especificamente com o nome (no meu exemplo) te*/'my files'/more.
Paddy Landau

Respostas:

9

O mesmo comando exato deve funcionar bem em um script:

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

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

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

ATENÇÃO:

Usar evaldessa maneira 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. Consulte o FAQ do bash 48 para obter detalhes.

É melhor seguir 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 corresponder 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 encontrar e incluir arquivos de ponto (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á- echolos diretamente sem o loop:

echo te*/my\ files/**
Terdon
fonte
2
Eu defini isso como a resposta por causa dos comentários úteis que Terdon fez (sem esquecer os comentários úteis de outras pessoas). Eu usei a capacidade globbing de Bash na linha de comando para passar vários caminhos para o meu script, em vez de o script tentar resolvê-lo. Isso funciona bem.
Paddy Landau
2

E 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

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

Tarde, eu percebo que acho que deveria ser capaz disso. Algo como:

find . -path 'D*/my folder/more/'
muru
fonte
Idéia inteligente, mas, infelizmente, não funciona. Por quê? Porque o próprio caminho é mantido em uma variável; daqui, INPUTPATH='te*/my files/moree SEARCH=(${INPUTPATH}). Não importa como eu varie da maneira que faço isso, ainda assim eu acabo com um resultado não funcional. Isso parece impossível!
Paddy Landau
Isso tudo é verdade, é claro, mas o OP precisa fazer isso em um script. Isso muda as coisas, pois a expansão dos curingas fica significativamente mais complexa e isso não funciona.
terdon
@PaddyLandau Nesse caso, por que você não pode usar findo -pathfiltro? Ele usa curingas e definitivamente não precisa de expansão.
Muru
@muru Isso é interessante; Eu não sabia -path. Nos últimos 10 minutos, porém, eu descobri a resposta: use eval! Parece ser mais simples que -path.
Paddy Landau
2
@terdon e muru e todos: Obrigado. Ouvi o que vocês disseram e percebi que deveria fazer o meu script fazer uma coisa e deixar o Bash globbing passar vários arquivos ou caminhos para o script. Eu modifiquei meu script assim. Funciona bem e se encaixa melhor com a filosofia do Linux. Mais uma vez obrigado!
Paddy Landau
0

Eu 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! Usar evalignora a interpretação que ${SEARCH}é de uma variável.

Paddy Landau
fonte
@terdon Obrigado pelo aviso. De volta à prancheta!
Paddy Landau
Sim, isso é surpreendentemente complicado. Acabei de atualizar minha resposta com outra abordagem. Por que não usar globbing? Se isso ainda não funcionar, sugiro que você publique uma nova pergunta no Unix e Linux, explicando qual é seu objetivo final e por que você precisa ter o padrão como variável. Esse tipo de coisa é mais provável de obter uma resposta melhor lá.
terdon