Questão Como faço para analisar a saída do comando find quando nomes de arquivos têm espaços neles?


Usando um loop como

for i in `find . -name \*.txt` 

irá quebrar se alguns nomes de arquivos tiverem espaços neles.

Que técnica posso usar para evitar esse problema?


11


origem


Observe que os arquivos também podem ter novas linhas no nome do arquivo. É por isso que há find -print0 e xargs -0. - Daniel Beck♦


Respostas:


O ideal é que você não faça isso de maneira alguma, porque a análise de nomes de arquivos adequadamente em um script de shell é sempre difícil (conserte-os por espaços, você ainda terá problemas com outros caracteres incorporados, em particular com novas linhas). Isso é listado como o primeira entrada na página BashPitfalls.

Dito isto, há uma maneira de quase fazer o que você quer:

oIFS=$IFS
IFS=$'\n'

find . -name '*.txt' | while read -r i; do
  # use "$i" with whatever you're doing
done

IFS=$oIFS

Lembre-se também de citar $i quando usá-lo, para evitar outras coisas que interpretam os espaços mais tarde. Lembre-se também de definir $IFS voltar depois de usá-lo, porque não fazer isso causará erros desconcertantes mais tarde.

Isto tem uma outra limitação: o que acontece dentro do while loop pode ocorrer em um subshell, dependendo do shell exato que você está usando, portanto, as configurações de variáveis ​​podem não persistir. o for versão de loop evita isso, mas ao preço que, mesmo se você aplicar o $IFS solução para evitar problemas com espaços, você terá problemas se a find retorna muitos arquivos.

Em algum momento, a correção correta para tudo isso é fazê-lo em uma linguagem como Perl ou Python ao invés de shell.


11



Eu gosto da idéia de usar o Python apenas para evitar tudo isso. - Scott C Wilson


Usar find -print0 e canalizá-lo para xargs -0, ou escreva seu próprio pequeno programa em C e canalize-o para seu pequeno programa em C. Isso é o que -print0 e -0 foram inventados para.

Os scripts de shell não são a melhor maneira de lidar com nomes de arquivos com espaços: você pode fazer isso, mas fica desajeitado.


11



Funciona na minha máquina ^ TM! - mcandre


Você pode definir o "separador de campo interno" (IFS) para algo diferente de espaço para a divisão do argumento de loop, e.

ORIGIFS=${IFS}
NL='
'
IFS=${NL}
for i in $(find . -name '*.txt'); do
    IFS=${ORIGIFS}
    #do stuff
done
IFS=${ORIGIFS}

Eu redefinir IFS depois de usá-lo, principalmente porque parece legal, eu acho. Eu não vi nenhum problema em tê-lo definido para nova linha, mas acho que isso é "mais limpo".

Outro método, dependendo do que você quer fazer com a saída de findé usar diretamente -exec com o find comando ou usar -print0 e canalizá-lo para xargs -0. No primeiro caso find cuida do escape do nome do arquivo. No -print0 caso, find imprime sua saída com um separador nulo e, em seguida, xargs divide-se nisso. Como nenhum nome de arquivo pode conter esse caractere (o que eu sei), isso também é seguro. Isso é mais útil em casos simples; e geralmente não é um ótimo substituto para uma for loop.


2





Usando find -print0 com xargs -0

Usando find -print0 combinado com xargs -0 é completamente robusto contra nomes de arquivos legais e é um dos métodos mais extensíveis disponíveis. Por exemplo, digamos que você queria uma listagem de todos os arquivos PDF no diretório atual. Você pode escrever

$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 echo

Isto irá encontrar todos os PDFs (via -iname '*.pdf') no diretório atual (.) e qualquer sub-diretório, e passar cada um deles como um argumento para o echo comando. Porque nós especificamos o -n 1 opção, xargssó vai passar um argumento de cada vez para echo. Se tivéssemos omitido essa opção, xargs teria passado o maior número possível para echo. (Você pode echo short input | xargs --show-limits para ver quantos bytes são permitidos em uma linha de comando.)

O que xargs fazer exatamente?

Podemos ver claramente o efeito xargs tem em sua entrada - eo efeito de -n em particular - usando um script que ecoa seus argumentos de uma maneira mais precisa do que echo.

$ cat > echoArgs.sh <<'EOF'
#!/bin/bash
echo "Number of arguments: $#"

[[ $# -eq 0 ]] && exit

for i in $(seq 1 $#); do
    echo "Arg $i: <$1>"
    shift
done
EOF

$ find . -iname '*.pdf' -print0 | xargs -0 ./echoArgs.sh
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 ./echoArgs.sh

Note que ele lida perfeitamente com espaços e linhas novas,

$ touch 'A space-age
new line of vending machines.pdf'
$ find . -iname '*space*' -print0 | xargs -0 -n 1 ./echoArgs.sh

o que seria especialmente problemático com a seguinte solução comum:

chmod +x ./echoArgs.sh
for file in $(ls *spacey*); do
  ./echoArgs.sh "$file"
done
Notas

1





Eu não concordo com o bash socadores, porque bash, juntamente com o conjunto de ferramentas * nix, é bastante competente no manuseio de arquivos (incluindo aqueles cujos nomes possuem espaço em branco incorporado).

Na realidade, find dá-lhe controle de granulação fina sobre a escolha de quais arquivos para processar ... No lado bash, você realmente só precisa perceber que você deve torná-lo seqüências de caracteres em bash words; tipicamente usando "aspas duplas", ou algum outro mecanismo como usar o IFS, ou encontrar {} 

Observe que na maioria das situações você não precisa definir e redefinir o IFS; basta usar o IFS localmente, conforme mostrado nos exemplos abaixo. Todos os três manipulam os espaços em branco bem. Além disso, você não precisa de uma estrutura de loop "padrão", porque encontrar  \;  é efetivamente um loop; basta colocar sua lógica de loop em uma função bash (se você não estiver chamando uma ferramenta padrão).

IFS=$'\n' find ~/ -name '*.txt' -exec  function-or-util {} \;  

E mais dois exemplos

IFS=$'\n' find ~/ -name '*.txt' -exec  printf 'Hello %s\n' {} \;  
IFS=$'\n' find ~/ -name '*.txt' -exec  echo {} \+ |sed 's/home//'  

'encontraralso allows you to pass multiple filenames as args to you script ..(if it suits your need: use+instead\; `)


1



Há alguma validade para ambas as perspectivas. Quando eu estava trabalhando apenas em meus próprios arquivos, eu usava apenas find e não me preocupava com isso, porque meus arquivos não tinham espaços (ou retornos de carro!) Em seus nomes. Mas quando você começa a trabalhar com arquivos de outras pessoas, você tem que usar técnicas mais robustas. - Scott C Wilson