Como passar arquivos encontrados por find como argumentos?

9

Primeiro, para eliminar respostas triviais, mas inaplicáveis: não posso usar o truque find+ xargsnem suas variantes (como findem -exec) porque preciso usar poucas expressões desse tipo por chamada. Voltarei a isso no final.


Agora, para um exemplo melhor, vamos considerar:

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

Como passo esses argumentos para program?

Apenas fazê-lo não faz o truque

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

falha desde que programobtém 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 programconsidera dois argumentos diferentes.

Cite até que funcione

Parece que usuários iniciantes como eu, quando enfrentamos esses problemas, tendem a adicionar aspas aleatoriamente até que finalmente funcione - só que aqui 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. Portanto, não apenas eles não impediram a divisão de palavras, mas também entraram em discussões!

Alterar IFS

Então eu tentei brincar com IFS. Eu preferiria findcom -print0e sortcom -zqualquer maneira - para que eles não tenham problemas nos "caminhos com fio". Então, por que não forçar a divisão de palavras no nullpersonagem e ter tudo?

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

Portanto, ele ainda se divide no espaço e não se divide no null.

Tentei colocar a IFStarefa tanto em $(…)(como mostrado acima) quanto antes ./program. Também eu tentei outra sintaxe como \0, \x0, \x00ambos citados com 'e ", assim como com e sem o $. Nenhum deles parecia fazer diferença ...


E aqui estou sem idéias. Tentei mais algumas coisas, mas tudo parecia ter os mesmos problemas listados.

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

Claro, eu poderia programaceitar os padrões e fazer pesquisas em si. Mas é muito trabalho duplo, enquanto corrige-o para uma sintaxe específica. (Que tal fornecer arquivos com um greppor exemplo?).

Também eu poderia fazer o programaceitar um arquivo com uma lista de caminhos. Em seguida, posso despejar facilmente a findexpressão em algum arquivo temporário e fornecer o caminho apenas para esse arquivo. Isso pode ser suportado em caminhos diretos, para que, se o usuário tiver apenas um caminho simples, ele possa ser fornecido sem arquivo intermediário. Mas isso não parece bom - é preciso criar arquivos extras e cuidar deles, sem mencionar a implementação extra necessária. (No lado positivo, no entanto, pode ser um resgate para os casos em que o número de arquivos como argumentos começa a causar problemas com o comprimento da linha de comando ...)


No final, gostaria de lembrá-lo novamente que find+ xargs(e similares) truques não funcionarão no meu caso. Para simplificar a descrição, estou mostrando apenas um argumento. Mas meu caso verdadeiro se parece mais com isso:

$ 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

Fazer xargsuma pesquisa a partir de uma ainda me deixa com como lidar com a outra…

Adam Badura
fonte

Respostas:

13

Use matrizes.

Se você não precisar lidar com a possibilidade de novas linhas nos seus nomes de arquivos, poderá se safar

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ê fazer necessidade de novas linhas punho dentro de nomes de arquivos, e têm o bash> = 4.4, você pode usar -print0e -d ''como nulo-terminar os nomes durante a construção array:

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

(e da mesma forma para 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 às matrizes, por exemplo

ABC_FILES=()
while IFS= read -rd '' f; do ABC_FILES+=( "$f" ); done < <(find -L some/dir -name \*.abc -print0 | sort -z)
chave de aço
fonte
Excelente! Eu estava pensando em arrays. Mas de alguma forma eu não encontrei nada sobre isso mapfile(ou seu sinônimo readarray). Mas funciona!
Adam Badura 24/10
No entanto, você poderia melhorar um pouco. A versão do Bash <4.4 (que por acaso tenho ...) com um whileloop não limpa a matriz. O que significa que, se nenhum arquivo for encontrado, a matriz será indefinida. Enquanto se já estiver definido, novos arquivos serão anexados (em vez de substituir os antigos). Parece que adicionar declare -a ABC_FILES='()';antes whilefaz o truque. (Embora apenas adicionando ABC_FILES='()';não.)
Adam Badura
Também o que < <significa aqui? É o mesmo que <<? Eu não acho que alterá-lo para <<gerar erro de sintaxe ("token inesperado` ('")). Então, o que é e como ele funciona?
Adam Badura
Outra melhoria (ao longo do meu uso específico) é construir mais uma matriz. Então nós temos isso ABC_FILES. Está bem. Mas é útil também criar ABS_ARGSqual é uma matriz vazia se ABC_FILESestiver vazia ou então é uma matriz ('--abc-files' "${ABC_FILES[@]}"). Dessa forma, posteriormente, eu posso usá-lo assim: ./program "${ABC_ARGS[@]}" "${XYZ_ARGS[@]}"e certifique-se de que funcione corretamente, independentemente de qual (se houver) dos grupos estiver vazio. Ou, para dizer de maneira diferente: dessa maneira --abc-files(e --xyz-files) será fornecida apenas se for seguida por algum caminho real.
Adam Badura 24/10
1
@AdamBadura: while read ... done < <(find blah)é o redirecionamento normal do shell <de um arquivo especial criado pelo PROCESS SUBSTITUTION . Isso difere da tubulação find blah | while read ... doneporque o pipeline executa o whileloop em um subshell para que os vars definidos nele não sejam retidos para comandos subseqüentes.
Dave_thompson_085
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 zshmas não bashvocê pode usar nulo $'\0'também. Mesmo em bashvocê , você pode lidar com nova linha se houver um personagem suficientemente estranho que nunca seja usado como

 IFS=$'\1'; ... $(find ... -print0 | tr '\0' '\1') ...

No entanto, essa abordagem não trata da solicitação adicional que você fez nos comentários na resposta do @ steeldriver para omitir os --afiles se encontrar um estiver vazio.

dave_thompson_085
fonte
Então, como eu entendo no Bash, não há como forçar IFSa divisão null?
Adam Badura
@ AdamBadura: Tenho certeza que não; O bash não permite byte nulo em nenhuma variável, incluindo o IFS. Observe que o read -d ''método usado no steeldriver é uma string vazia e não uma contendo byte nulo. (E uma opção de comando não é uma var, como tal, de qualquer maneira.)
dave_thompson_085
Você também deve desativar globbing ( set -o noglob) antes de usar esse operador split + glob (exceto em zsh).
Stéphane Chazelas
@AdamBadura Sim, no bash, um nulo é exatamente o mesmo $'\0'e também como ''.
Isaac
1

Não sei se entendi por que você desistiu xargs.

Fazer xargsuma pesquisa a partir de uma ainda me deixa com como lidar com a outra…

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

{ find -L some/dir -name \*.abc -print0 | sort -z; echo -ne "--xyz-files\0"; find -L other/dir -name \*.xyz -print0 | sort -z; } | xargs -0 ./program --abc-files
Kamil Maciorowski
fonte
Você está certo! Isso também funciona! No entanto, observe que você perdeu -print0em segundo find. Além disso, se vai desta forma eu iria colocar o --abc-filescomo um echobem - apenas para a consistência.
Adam Badura
Essa abordagem parece mais simples e um pouco mais de uma linha do que a abordagem de matriz. No entanto, seria necessária alguma lógica extra para cobrir o caso de que, se não houver .abcarquivos, também não deveria haver --abc-files(o mesmo com .xyz). A solução baseada em array da steeldriver também exige lógica extra para isso, mas essa lógica é trivial lá, embora possa não ser tão trivial aqui, destruindo a principal vantagem dessa solução - a simplicidade.
Adam Badura
Também eu não tenho certeza, mas presumo que xargsnunca vai tentar dividir argumentos e fazer alguns comandos em vez de um, a menos que ele é explicitamente instruído a fazê-lo com -L, --max-lines( -l), --max-args( -n) ou --max-chars( -sargumentos). Estou certo? Ou existem alguns padrões? Como meu programa não iria lidar com esta divisão corretamente e eu preferiria ter uma falha de chamá-lo ...
Adam Badura
1
@AdamBadura Missing -print0- reparado, obrigado. Não sei todas as respostas, mas concordo que minha solução dificulta a inclusão de lógica extra. Eu provavelmente iria com matrizes, agora quando conheço essa abordagem. Minha resposta não foi realmente para você. Você já aceitou a outra resposta e presumi que seu problema estivesse resolvido. Eu só queria ressaltar que você pode passar argumentos de várias fontes xargs, o que não era óbvio à primeira vista. Você pode tratá-lo como uma prova de conceito. Agora todos conhecemos poucas abordagens diferentes e podemos escolher conscientemente o que se encaixa em todos os casos em particular.
Kamil Maciorowski
Sim, eu já implementei a solução baseada em array e funciona como um encanto. Estou especialmente orgulhoso de como ele lida com a opção de forma limpa (se não houver arquivos, não --abc-files). Mas você está certo - é bom conhecer suas alternativas! Especialmente que pensei erroneamente que não é possível.
Adam Badura