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

12

Usando um loop como

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

será interrompido se alguns nomes de arquivos tiverem espaços.

Que técnica eu posso usar para evitar esse problema?

Scott C Wilson
fonte
1
Note-se que os arquivos também pode ter novas linhas em seu nome de arquivo. É por isso que há find -print0e xargs -0.
Daniel Beck

Respostas:

12

O ideal é que você não faça dessa maneira, porque analisar nomes de arquivos corretamente em um shell script é sempre difícil (corrija-o por espaços, você ainda terá problemas com outros caracteres incorporados, em particular a nova linha). Isso é listado como a primeira entrada na página BashPitfalls.

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

oIFS=$IFS
IFS=$'\n'

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

IFS=$oIFS

Lembre-se de citar também $iao usá-lo, para evitar que outras coisas interpretem os espaços posteriormente. Lembre-se também de $IFSvoltar atrás depois de usá-lo, pois isso não causará erros desconcertantes posteriormente.

Isso tem uma outra ressalva: o que acontece dentro do whileloop pode ocorrer em um subshell, dependendo do shell exato que você está usando, portanto, as configurações variáveis ​​podem não persistir. A forversão em loop evita isso, mas a um preço que, mesmo que você aplique a $IFSsolução para evitar problemas com espaços, você terá problemas se findretornar muitos arquivos.

Em algum momento, a correção correta para tudo isso passa a ser feita em uma linguagem como Perl ou Python, em vez de shell.

geekosaur
fonte
1
Eu gosto da ideia de usar o Python para evitar tudo isso.
22675 Scott C Wilson
12

Use-o find -print0e xargs -0escreva-o ou escreva seu próprio programa C e encaminhe-o ao seu pequeno programa C. É para isso -print0e -0foi inventado.

Os scripts de shell não são a melhor maneira de lidar com nomes de arquivos com espaços: você pode fazê-lo, mas fica complicado.

DW
fonte
Funciona na minha máquina ^ TM!
mcandre
2

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

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

Eu redefinir IFSapós o seu uso em find, principalmente porque parece bom, eu acho. Não vi nenhum problema em configurá-lo para nova linha, mas acho que isso é "mais limpo".

Outro método, dependendo do que você deseja fazer com a saída find, é usar diretamente -execcom o findcomando ou usá -print0-lo xargs -0. No primeiro caso, findcuida do nome do arquivo que está escapando. No -print0caso, findimprime sua saída com um separador nulo e depois xargsdivide-o. Como nenhum nome de arquivo pode conter esse caractere (o que eu sei), isso também é sempre seguro. Isso é útil principalmente em casos simples; e geralmente não é um ótimo substituto para um forloop completo .

Daniel Andersson
fonte
1

Usando find -print0comxargs -0

O uso find -print0combinado com xargs -0é totalmente robusto contra nomes de arquivos legais e é um dos métodos mais extensíveis disponíveis. Por exemplo, suponha que você queira uma lista de todos os arquivos PDF no diretório atual. Você poderia escrever

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

Ele encontrará todos os PDFs (via -iname '*.pdf') no diretório atual ( .) e em qualquer subdiretório e passará cada um deles como argumento para o echocomando. Como especificamos a -n 1opção, xargspassamos apenas um argumento de cada vez para echo. Se tivéssemos omitido essa opção, xargsteria passado o maior número possível echo. (Você pode echo short input | xargs --show-limitsver quantos bytes são permitidos em uma linha de comando.)

O que xargsfaz exatamente?

Podemos ver claramente o efeito que xargstem sobre a sua entrada - e o efeito de -nem 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

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

$ 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
jpaugh
fonte
1

Não concordo com os bashbashers, porque bash, junto com o conjunto de ferramentas * nix, é bastante hábil em lidar com arquivos (incluindo aqueles cujos nomes incorporaram espaços em branco).

Na verdade, findoferece um controle minucioso sobre a escolha dos arquivos a serem processados ​​... No lado do bash, você realmente só precisa perceber que deve fazer as coisas bash words; normalmente usando "aspas duplas" ou algum outro mecanismo como o IFS ou{}

Observe que na maioria / muitas situações você não precisa definir e redefinir o IFS; basta usar o IFS localmente, como mostrado nos exemplos abaixo. Todos os três lidam bem com o espaço em branco. Além disso, você não precisa de uma estrutura de loop "padrão", porque o find \; é 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//'  

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

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