Eu quero iterar sobre uma lista de arquivos. Esta lista é o resultado de um find
comando, então eu vim com:
getlist() {
for f in $(find . -iname "foo*")
do
echo "File found: $f"
# do something useful
done
}
Tudo bem, exceto se um arquivo tiver espaços em seu nome:
$ ls
foo_bar_baz.txt
foo bar baz.txt
$ getlist
File found: foo_bar_baz.txt
File found: foo
File found: bar
File found: baz.txt
O que posso fazer para evitar a divisão de espaços?
Respostas:
Você pode substituir a iteração baseada em palavras por uma iterativa baseada em linhas:
fonte
touch "$(printf "foo\nbar")"
IFS= while read -r f
lugar.find
e um loop while.-exec
vai ser mais limpo do que um loop explícita:find . -iname "foo*" -exec echo "File found: {}" \;
. Além disso, em muitos casos, você pode substituir esse último\;
por+
colocar muitos arquivos no único comando.Existem várias maneiras viáveis de conseguir isso.
Se você quisesse manter sua versão original, isso poderia ser feito da seguinte maneira:
Isso ainda falhará se os nomes dos arquivos tiverem novas linhas literais, mas os espaços não serão interrompidos.
No entanto, não é necessário mexer com o IFS. Aqui está minha maneira preferida de fazer isso:
Se você achar a
< <(command)
sintaxe desconhecida, leia sobre a substituição de processos . A vantagem dissofor file in $(find ...)
é que arquivos com espaços, novas linhas e outros caracteres são manipulados corretamente. Isso funciona porquefind
with-print0
usará umnull
(aka\0
) como terminador para cada nome de arquivo e, diferentemente da nova linha, null não é um caractere legal em um nome de arquivo.A vantagem disso em relação à versão quase equivalente
É que qualquer atribuição de variável no corpo do loop while é preservada. Ou seja, se você canalizar
while
como acima, o corpo dowhile
está em um subshell que pode não ser o que você deseja.A vantagem da versão de substituição de processo
find ... -print0 | xargs -0
é mínima: axargs
versão é boa se tudo o que você precisa é imprimir uma linha ou executar uma única operação no arquivo, mas se você precisar executar várias etapas, a versão do loop será mais fácil.EDIT : Aqui está um bom script de teste para que você possa ter uma idéia da diferença entre diferentes tentativas de resolver esse problema
fonte
$IFS
e a< <(cmd)
sintaxe. Ainda uma coisa permanece obscura para mim, porque o$
em$'\0'
? Muito obrigado.while IFS= read
... para lidar com arquivos que iniciam ou terminam com espaço em branco.bash
então me senti seguro usando recursos específicos do bash. A substituição do processo não é portável para outros shells (o próprio sh provavelmente não receberá uma atualização tão significativa).IFS=$'\n'
comfor
impede a divisão interna das palavras, mas ainda sujeita as linhas resultantes a globbing, portanto essa abordagem não é totalmente robusta (a menos que você também desative o globbing primeiro). Enquantoread -d $'\0'
funciona, é um pouco enganador, pois sugere que você pode usar$'\0'
para criar NULs - você não pode: a\0
em uma seqüência de caracteres citada por ANSI C efetivamente termina a sequência, de modo que-d $'\0'
é efetivamente o mesmo que-d ''
.Há também uma solução muito simples: confie no globing bash
Observe que não tenho certeza de que esse comportamento seja o padrão, mas não vejo nenhuma configuração especial no meu shopt; portanto, diria que deveria ser "seguro" (testado no osx e no ubuntu).
fonte
fonte
Veja
man xargs
.fonte
Como você não está fazendo nenhum outro tipo de filtragem
find
, é possível usar o seguinte a partir dabash
4.0:Ele
**/
corresponderá a zero ou mais diretórios; portanto, o padrão completo corresponderáfoo*
ao diretório atual ou a qualquer subdiretório.fonte
Eu realmente gosto de loops e iteração de matriz, então acho que vou adicionar esta resposta à mistura ...
Também gostei do exemplo de arquivo estúpido de marchelbling. :)
Dentro do diretório de teste:
Isso adiciona cada linha de listagem de arquivos a uma matriz bash denominada
arr
com qualquer nova linha à direita removida.Digamos que queremos dar um nome melhor a esses arquivos ...
$ {! arr [@]} se expande para 0 1 2, então "$ {arr [$ i]}" é o i- ésimo elemento da matriz. As aspas ao redor das variáveis são importantes para preservar os espaços.
O resultado são três arquivos renomeados:
fonte
find
tem um-exec
argumento que faz um loop sobre os resultados da busca e executa um comando arbitrário. Por exemplo:Aqui
{}
representa os arquivos encontrados, e envolvê-los""
permite que o comando shell resultante lide com espaços no nome do arquivo.Em muitos casos, você pode substituir o último
\;
(que inicia um novo comando) por a\+
, que colocará vários arquivos em um comando (embora não necessariamente todos de uma só vez, vejaman find
mais detalhes).fonte
Em alguns casos, aqui, se você apenas precisar copiar ou mover uma lista de arquivos, poderá canalizar essa lista para despertar também.
Importante ao
\"" "\"
redor do campo$0
(em resumo, seus arquivos, uma lista de linhas = um arquivo).fonte
Ok - meu primeiro post no Stack Overflow!
Embora meus problemas com isso sempre tenham sido no csh, não bash, a solução que apresento funcionará, tenho certeza, em ambos. O problema está na interpretação do shell dos retornos "ls". Podemos remover "ls" do problema simplesmente usando a expansão do
*
curinga - mas isso gera um erro "sem correspondência" se não houver arquivos na pasta atual (ou na pasta especificada) - para contornar isso, simplesmente estendemos o arquivo expansão para incluir arquivos de ponto assim:* .*
- isso sempre produzirá resultados desde os arquivos. e .. estará sempre presente. Assim, no csh, podemos usar essa construção ...se você deseja filtrar os arquivos ponto padrão, isso é fácil o suficiente ...
O código no primeiro post deste tópico seria escrito assim: -
Espero que isto ajude!
fonte
Outra solução para o trabalho ...
O objetivo era:
fonte