Como uso o find quando o nome do arquivo contém espaços?

17

Quero canalizar nomes de arquivos para outros programas, mas todos eles sufocam quando os nomes contêm espaços.

Digamos que eu tenho um arquivo chamado.

foo bar

Como posso findretornar o nome correto?

Obviamente eu quero:

foo\ bar

ou:

"foo bar"

Edição : Eu não quero passar xargs, eu quero obter uma seqüência de caracteres formatada corretamente findpara que eu possa canalizar a seqüência de nomes de arquivos diretamente para outro programa.

erro
fonte
5
o que você está fazendo? você está ciente da -execbandeira com find? você pode potencialmente aliviar esse erro e tornar seu comando mais eficiente, ao -execinvés de canalizá-lo para outros comandos. Just my $ .02
h3rrmiller
6
@ bug: findformata os nomes dos arquivos; eles são escritos um nome por linha. (Obviamente, isso é ambíguo se um nome de arquivo contiver um caractere de nova linha.) Portanto, o problema é o final de recebimento "sufocado" quando obtém um espaço, o que significa que você precisa nos dizer qual é o final de recebimento se quiser uma resposta significativa. .
rici
2
O que você chama de "formatado corretamente" é realmente "escapado para consumo pelo shell". A maioria dos utilitários que podem ler vários nomes de arquivos se sufocaria com um nome com escape de shell, mas na verdade faria sentido (digamos) findoferecer uma opção para gerar nomes de arquivos em um formato adequado para o shell. Em geral, porém, a extensão -print0GNU findfunciona bem para muitos outros cenários (também), e você deve aprender a usá-la em qualquer caso.
Tripleee 01/07/2013
2
@ bug: A propósito, ls $(command...)não alimenta a lista stdin. Ele coloca a saída $(command...)diretamente na linha de comando. Nesse caso, é o shell que está lendo a partir de c e usará o valor atual de $IFSpara decidir como dividir a saída em palavras. Em geral, é melhor usar xargs. Você não notará um impacto no desempenho.
rici
2
find -printf '"%p"\n'adicionará aspas duplas ao redor de cada nome encontrado, mas não citará corretamente aspas duplas no nome de um arquivo. Se os nomes de arquivo não tem nenhum aspas duplas incorporadas, você pode ignorar o problema: ou tubo através sed 's/"/&&/g;s/^""/"/;s/""$/"/'. Se os nomes dos arquivos acabarem sendo manipulados pelo shell, provavelmente você deve usar aspas simples em vez de aspas duplas (caso contrário, sweet$HOMEse tornará algo parecido sheet/home/you). E isso ainda não é muito robusto em relação aos nomes de arquivos com novas linhas. Como você quer lidar com isso?
Tripleee

Respostas:

18

POSIXAMENTE:

find . -type f -exec sh -c '
  for f do
    : command "$f"
  done
' sh {} +

Com findsuportes -print0e xargssuportes -0:

find . -type f -print0 | xargs -0 <command>

-0 A opção diz ao xargs para usar o caractere ASCII NUL em vez de espaço para finalizar (separar) os nomes de arquivos.

Exemplo:

find . -maxdepth 1 -type f -print0 | xargs -0 ls -l
cuonglm
fonte
Não funciona Quando eu corro ls $(find . -maxdepth 1 -type f -print0 | xargs -0)eu recebo ls: cannot access ./foo: No such file or directory ls: cannot access bar: No such file or directory
bug
1
Você já tentou da maneira que o Gnouc realmente escreveu? Se você insiste em fazer do seu jeito, tente colocando o $(..)entre aspas"$(..)"
evilsoup
3
@ bug: seu comando está errado. Tente exatamente que eu torço e leia a página de manual de finde xargs.
#
Entendo, novamente quero obter uma string formatada que eu possa canalizar diretamente.
bug
1
@ bug: Basta usar xargs -0 <your program>
cuonglm
10

O uso -print0é uma opção, mas nem todos os programas suportam o uso de fluxos de dados delimitados por nulos, então você terá que usar xargsa -0opção para algumas coisas, como observou a resposta do Gnouc.

Uma alternativa seria usar find's -execou -execdiropções. O primeiro dos itens a seguir alimentará os nomes dos arquivos somecommandum por vez, enquanto o segundo será expandido para uma lista de arquivos:

find . -type f -exec somecommand '{}' \;
find . -type f -exec somecommand '{}' +

Você pode achar que é melhor usar globbing em muitos casos. Se você possui um shell moderno (bash 4+, zsh, ksh), pode obter globbing recursivo com globstar( **). No bash, você deve definir isso:

shopt -s globstar
somecommand ./**/*.txt ## feeds all *.txt files to somecommand, recursively

Eu tenho uma linha dizendo shopt -s globstar extglobno meu .bashrc, então isso está sempre ativado para mim (e também os globs estendidos, que também são úteis).

Se você não quiser recursividade, obviamente use apenas ./*.txtpara usar todos os * .txt no diretório de trabalho. findpossui alguns recursos de pesquisa refinados muito úteis e é obrigatório para dezenas de milhares de arquivos (nesse momento, você encontrará o número máximo de argumentos do shell), mas, para o uso diário, muitas vezes é desnecessário.

evilsoup
fonte
Ei, @evilsoup, o que o {} faz neste script?
Ayusman 03/07
3

Pessoalmente, eu usaria a -execação find para resolver esse tipo de problema. Ou, se necessário xargs, o que permite a execução paralela.

No entanto, existe uma maneira de obter finduma lista de nomes de arquivos legíveis por bash. Sem surpresa, ele usa -exece bash, em particular, uma extensão do printfcomando:

find ... -exec bash -c 'printf "%q " "$@"' printf {} ';'

No entanto, embora isso imprima corretamente palavras com escape de shell, não será utilizável com $(...), porque $(...)não interpreta aspas ou escapes. (O resultado de $(...)está sujeito a divisão de palavras e expansão do nome do caminho, a menos que esteja entre aspas.) Portanto, o seguinte não fará o que você deseja:

ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)

O que você teria que fazer é:

eval "ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)"

(Observe que não fiz nenhuma tentativa real de testar a monstruosidade acima).

Mas então você também pode:

find ... -exec ls {} +
rici
fonte
Não acho que o lscenário capture adequadamente o caso de uso do OP, mas isso é apenas especulação, já que não foram mostrados o que ele está realmente tentando realizar. Esta solução realmente funciona muito bem; Eu obter a saída I (vagamente) esperada para todos os nomes de arquivo engraçadas eu tentei, incluindotouch "$(tr a-z '\001-\026' <<<'the quick brown fox jumped over the lazy dogs')"
tripleee
Triplee @: Eu não tenho idéia do que OP quer fazer também. A única vantagem real de construir a string citada a ser transmitida evalé que você ainda não precisa transmiti-la eval; você pode salvá-lo em um parâmetro e usá-lo mais tarde, talvez várias vezes com comandos diferentes. No entanto, OP não dá nenhuma indicação de que esse é o caso de uso (e se fosse, talvez seja melhor colocar os nomes de arquivos em uma matriz, apesar de que é complicado demais.)
rici
0
find ./  | grep " "

você receberá os arquivos e diretórios contém espaços

find ./ -type f  | grep " " 

você receberá os arquivos contém espaços

find ./ -type d | grep " "

você receberá os diretórios contém espaços

Kannan Kumarasamy
fonte
-2
    find . -type f -name \*\  | sed -e 's/ /<thisisspace>/g'
user283965
fonte
Esta é uma resposta interessante, mas não é uma resposta para esta pergunta.
Scott Scott