
Primeiro, para eliminar respostas triviais, mas inaplicáveis: não posso usar o truque find
+ xargs
nem suas variantes (como find
with -exec
) porque preciso usar poucas dessas expressões 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 isso como argumentos para program
?
Apenas fazer isso não resolve
$ ./program $(find -L some/dir -name \*.abc | sort)
falha porque program
obté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 program
considera que são dois argumentos diferentes.
Cite até que funcione
Parece que usuários novatos como eu, quando confrontados com tais problemas, tendem a adicionar aspas aleatoriamente até que finalmente funcione - só que 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 evitam 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í, claro. Mas eles não são mais interpretados. Eles são apenas parte das cordas. Portanto, eles não apenas não impediram a divisão das palavras, mas também entraram em discussões!
Alterar IFS
Então tentei brincar com IFS
. Eu preferiria find
with -print0
e sort
with -z
de qualquer maneira - para que eles próprios não tenham problemas em "caminhos conectados". Então, por que não forçar a divisão de palavras no null
personagem e ficar com 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 IFS
tarefa em $(…)
(como mostrado acima) e antes ./program
. Também tentei outra sintaxe como \0
, \x0
, \x00
ambas citadas com '
e "
bem como com e sem o $
. Nada disso parecia fazer qualquer 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? Isso é factível?
Claro, eu poderia aceitar program
os padrões e fazer as próprias pesquisas. Mas é muito trabalho duplo fixá-lo em uma sintaxe específica. (Que tal fornecer arquivos por grep
exemplo?).
Também eu poderia aceitar program
um arquivo com uma lista de caminhos. Então, posso facilmente despejar find
a expressão em algum arquivo temporário e fornecer o caminho apenas para esse arquivo. Isso pode ser suportado ao longo de caminhos diretos, de modo que, se o usuário tiver apenas um caminho simples, ele possa ser fornecido sem arquivo intermediário. Mas isso não parece legal - é preciso criar arquivos extras e cuidar deles, sem mencionar a necessidade de implementação extra. (No entanto, o lado positivo pode ser um resgate para casos em que o número de arquivos como argumentos começa a causar problemas com o comprimento da linha de comando…)
No final, deixe-me lembrá-lo novamente que truques find
+ xargs
(e similares) não funcionarão no meu caso. Para simplificar a descrição, estou mostrando apenas um argumento. Mas meu verdadeiro caso é mais parecido com este:
$ 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
Então, fazer uma xargs
pesquisa ainda me deixa sabendo como lidar com a outra…
Responder1
Use matrizes.
Se você não precisa lidar com a possibilidade de novas linhas em seus nomes de arquivos, então você pode usar
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êfazerprecisa lidar com novas linhas em nomes de arquivos e tem bash >= 4.4, você pode usar -print0
e -d ''
para encerrar os nomes com nulo durante a construção do array:
mapfile -td '' ABC_FILES < <(find -L some/dir -name \*.abc -print0 | sort -z)
(e da mesma forma para o XYZ_FILES
). Se vocênãotiver o bash mais recente, então você pode usar um loop de leitura terminado em nulo 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)
Responder2
Você pode usar IFS=newline (assumindo que nenhum nome de arquivo contenha nova linha), mas deve defini-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 você também bash
pode usar null . $'\0'
Mesmo bash
você poderia lidar com a nova linha se houver um caractere suficientemente estranho que nunca seja usado como
IFS=$'\1'; ... $(find ... -print0 | tr '\0' '\1') ...
No entanto, esta abordagem não atende à solicitação adicional feita nos comentários na resposta do @steeldriver para omitir --afiles se find a estiver vazio.
Responder3
Não tenho certeza se entendi por que você desistiu xargs
.
Então, fazer uma
xargs
pesquisa ainda me deixa sabendo como lidar com a outra…
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. Acho que você pode passar xargs
entre os dois find
resultados:
{ 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