Como passar arquivos encontrados por find como argumentos?

9

Primeiro, para interromper respostas triviais, mas inaplicáveis: não posso usar nem o truque find + xargs nem suas variantes (como find com -exec ) porque preciso usar poucas dessas expressões por chamada. Eu voltarei a isso no final.

Agora, para um melhor exemplo, vamos considerar:

$ find -L some/dir -name \*.abc | sort
some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

Como faço para passar esses argumentos para program ?

Apenas fazendo isso não faz o truque

$ ./program $(find -L some/dir -name \*.abc | sort)

falha porque program recebe os seguintes argumentos:

[0]: ./program
[1]: some/dir/1.abc
[2]: some/dir/2.abc
[3]: some/dir/a
[4]: space.abc

Como pode ser visto, o caminho com espaço foi dividido e program considera dois argumentos diferentes.

Citar até que funcione

Parece que usuários iniciantes como eu, quando se deparam com esses problemas, tendem a adicionar citações aleatoriamente até que finalmente funcione - somente aqui isso não parece ajudar…

"$(…)"

$ ./program "$(find -L some/dir -name \*.abc | sort)"
[0]: ./program
[1]: some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

Como as aspas impedem a divisão de palavras, todos os arquivos são passados como um único argumento.

Citando caminhos individuais

Uma abordagem promissora:

$ ./program $(find -L some/dir -name \*.abc -printf '"%p"\n' | sort)
[1]: "some/dir/1.abc"
[2]: "some/dir/2.abc"
[3]: "some/dir/a
[4]: space.abc"

As citações estão aí, com certeza. Mas eles não são mais interpretados. Eles são apenas parte das cordas. Então, não só não impediram a divisão de palavras, mas também entraram em discussões!

Alterar o IFS

Então eu tentei brincar com IFS . Eu preferiria find com -print0 e sort com -z de qualquer forma - para que eles mesmos não tivessem problemas com "caminhos com fio". Então, por que não forçar a divisão de palavras no caractere null e ter tudo isso?

$ ./program $(IFS=$'
$ ABC_FILES=$(find -L some/dir -name \*.abc | sort)
$ XYZ_FILES=$(find -L other/dir -name \*.xyz | sort)
$ ./program --abc-files $ABC_FILES --xyz-files $XYZ_FILES
' find -L some/dir -name \*.abc -print0 | sort -z) [0]: ./program [1]: some/dir/1.abcsome/dir/2.abcsome/dir/a [2]: space.abc

Por isso, ele ainda se divide no espaço e não é dividido em null .

Eu tentei colocar a atribuição IFS em $(…) (como mostrado acima) e antes de ./program . Também tentei outras sintaxes como \x0 , \x00 , ' , ambas citadas com " e $ , bem como com e sem program . Nada disso pareceu fazer diferença…

E aqui estou sem ideias. Tentei mais algumas coisas, mas todas pareciam ter os mesmos problemas listados.

O que mais eu poderia fazer? É factível?

Claro, consegui que grep aceitasse os padrões e fizesse buscas em si. Mas é muito trabalho duplo enquanto o corrige para uma sintaxe específica. (Que tal fornecer arquivos por um program , por exemplo?).

Além disso, consegui que find aceitasse um arquivo com uma lista de caminhos. Então, posso facilmente copiar find expressão para algum arquivo temporário e fornecer o caminho para esse arquivo apenas. Isso poderia ser suportado por caminhos diretos para que, se o usuário tivesse apenas um caminho simples, ele pudesse ser fornecido sem o arquivo intermediário. Mas isso não parece bom - é necessário criar arquivos extras e cuidar deles, sem mencionar a implementação extra necessária. (No lado positivo, no entanto, pode ser um resgate para casos em que o número de arquivos como argumentos começa a causar problemas com o tamanho da linha de comando ...)

No final, deixe-me lembrá-lo de que os truques xargs + xargs (e similares) não funcionarão no meu caso. Para simplicidade de descrição, estou mostrando apenas um argumento. Mas meu verdadeiro caso parece mais com isso:

$ find -L some/dir -name \*.abc | sort
some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

Portanto, fazer um %code% de uma pesquisa ainda me deixa como lidar com a outra…

    
por Adam Badura 24.10.2016 / 02:01

3 respostas

13

Use matrizes.

Se você não precisa lidar com a possibilidade de novas linhas em seus nomes de arquivos, então você pode se safar com

mapfile -t ABC_FILES < <(find -L some/dir -name \*.abc | sort)
mapfile -t XYZ_FILES < <(find -L other/dir -name \*.xyz | sort)

então

./program --abc-files "${ABC_FILES[@]}" --xyz-files "${XYZ_FILES[@]}"

Se você fizer precisar lidar com novas linhas dentro de nomes de arquivos, e tiver bash > = 4.4, você pode usar -print0 e -d '' para terminar em nulo os nomes durante a construção da matriz:

mapfile -td '' ABC_FILES < <(find -L some/dir -name \*.abc -print0 | sort -z)

(e da mesma forma para o XYZ_FILES ). Se você não tiver o bash mais recente, poderá usar um loop de leitura com terminação nula para anexar nomes de arquivos aos arrays, por exemplo

ABC_FILES=()
while IFS= read -rd '' f; do ABC_FILES+=( "$f" ); done < <(find -L some/dir -name \*.abc -print0 | sort -z)
    
por 24.10.2016 / 02:36
3

Você pode usar IFS = newline (assumindo que nenhum nome de arquivo contenha nova linha), mas você deve configurá-lo no shell externo ANTES da substituição:

$ ls -1
a file with spaces
able
alpha
baker
boo hoo hoo
bravo
$ # note semicolon here; it's not enough to be in the environment passed
$ # to printf, it must be in the environment OF THE SHELL WHILE PARSING
$ IFS=$'\n'; printf '%s\n' --afiles $(find . -name 'a*') --bfiles $(find . -name 'b*')
--afiles
./able
./a file with spaces
./alpha
--bfiles
./bravo
./boo hoo hoo
./baker

Com zsh , mas não bash , você também pode usar nulo $'bash' . Mesmo em %code% você poderia lidar com nova linha se houver um caracter suficientemente estranho que nunca é usado como

 IFS=$''; ... $(find ... -print0 | tr '
$ ls -1
a file with spaces
able
alpha
baker
boo hoo hoo
bravo
$ # note semicolon here; it's not enough to be in the environment passed
$ # to printf, it must be in the environment OF THE SHELL WHILE PARSING
$ IFS=$'\n'; printf '%s\n' --afiles $(find . -name 'a*') --bfiles $(find . -name 'b*')
--afiles
./able
./a file with spaces
./alpha
--bfiles
./bravo
./boo hoo hoo
./baker
' '') ...

No entanto, esta abordagem não suporta o pedido adicional que você fez nos comentários sobre a resposta do @ steeldriver para omitir o --afiles se achar que um está vazio.

    
por 24.10.2016 / 08:42
1

Não sei ao certo por que você desistiu de xargs .

So doing an xargs from one search still leaves me with how to deal with the other one…

A string --xyz-files é apenas um dos muitos argumentos e não há razão para considerá-la especial antes de ser interpretada pelo seu programa. Eu acho que você pode passar por xargs entre os dois find results:

{ find -L some/dir -name \*.abc -print0 | sort -z; echo -ne "--xyz-files
{ find -L some/dir -name \*.abc -print0 | sort -z; echo -ne "--xyz-files%pre%"; find -L other/dir -name \*.xyz -print0 | sort -z; } | xargs -0 ./program --abc-files
"; find -L other/dir -name \*.xyz -print0 | sort -z; } | xargs -0 ./program --abc-files
    
por 24.10.2016 / 15:02