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.
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 .
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
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.
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).
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.
find -print0
exargs -0
.Respostas:
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:
Lembre-se de citar também
$i
ao usá-lo, para evitar que outras coisas interpretem os espaços posteriormente. Lembre-se também de$IFS
voltar atrás depois de usá-lo, pois isso não causará erros desconcertantes posteriormente.Isso tem uma outra ressalva: o que acontece dentro do
while
loop pode ocorrer em um subshell, dependendo do shell exato que você está usando, portanto, as configurações variáveis podem não persistir. Afor
versão em loop evita isso, mas a um preço que, mesmo que você aplique a$IFS
solução para evitar problemas com espaços, você terá problemas sefind
retornar 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.
fonte
Use-o
find -print0
exargs -0
escreva-o ou escreva seu próprio programa C e encaminhe-o ao seu pequeno programa C. É para isso-print0
e-0
foi 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.
fonte
Você pode definir o "separador de campo interno" (
IFS
) como algo diferente de espaço para a divisão do argumento do loop, por exemploEu redefinir
IFS
apó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-exec
com ofind
comando ou usá-print0
-loxargs -0
. No primeiro caso,find
cuida do nome do arquivo que está escapando. No-print0
caso,find
imprime sua saída com um separador nulo e depoisxargs
divide-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 umfor
loop completo .fonte
Usando
find -print0
comxargs -0
O uso
find -print0
combinado comxargs -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 escreverEle encontrará todos os PDFs (via
-iname '*.pdf'
) no diretório atual (.
) e em qualquer subdiretório e passará cada um deles como argumento para oecho
comando. Como especificamos a-n 1
opção,xargs
passamos apenas um argumento de cada vez paraecho
. Se tivéssemos omitido essa opção,xargs
teria passado o maior número possívelecho
. (Você podeecho short input | xargs --show-limits
ver quantos bytes são permitidos em uma linha de comando.)O que
xargs
faz exatamente?Podemos ver claramente o efeito que
xargs
tem sobre a sua entrada - e o efeito de-n
em particular - usando um script que ecoa seus argumentos de uma maneira mais precisa do queecho
.Observe que ele lida com espaços e novas linhas perfeitamente bem,
o que seria especialmente problemático com a seguinte solução comum:
Notasfonte
Não concordo com os
bash
bashers, porquebash
, 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,
find
oferece um controle minucioso sobre a escolha dos arquivos a serem processados ... No lado do bash, você realmente só precisa perceber que deve fazer as coisasbash 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).E mais dois exemplos
'encontre
also allows you to pass multiple filenames as args to you script ..(if it suits your need: use
+instead
\; `)fonte