Estou no Linux Ubuntu 18.04 e 20.04.
Ripgrep ( rg
) pode gerar uma lista de caminhos para arquivos contendo correspondências como esta:
# search only .txt files
rg 'my pattern to match' -g '*.txt' -l
# long form
rg 'my pattern to match' --glob '*.txt' --files-with-matches
A saída será:
path/to/file1.txt path/to/file2.txt path/to/file3.txt
etc.
Eu gostaria de executar outro comando em cada caminho, como tree $(dirname $PATH)
, para obter uma lista de todos os arquivos no diretório que contém o arquivo correspondente. Como eu posso fazer isso?
Eu sinto que xargs
pode ser parte da resposta, talvez? Mas o pipe para xargs
começar assim parece lidar apenas com o último arquivo impresso:
rg 'my pattern to match' -g '*.txt' -l | xargs -0 -I {} dirname {}
Nota: se você também puder fazer uma demonstração grep
, isso também pode ser útil para aqueles sem ripgrep
, embora o ripgrep seja super fácil de instalar .
Em um sistema GNU, isso poderia ser assim:
Observe que, se ambos
dir/file.txt
edir/subdir/file.txt
corresponderem, você acabará executandotree
em ambosdir
edir/subdir
, portanto, verá o conteúdo dedir/subdir
duas vezes.Você teve a ideia certa usando
xargs
qual é o comando para converter uma string de bytes em uma lista de argumentos para passar para um comando e usar-0
qual é a maneira mais confiável de passar uma lista arbitrária de argumentos, mas:xargs -0
espera a entrada em um formato onde a lista de argumentos é separada por caracteres NUL (0 bytes)¹. Você precisa da opção-0
/ para imprimir a lista de arquivos nesse formato.--null
rg
dirname
pode processar mais de um argumento por invocação, então ao invés de usar-I{}
, nós apenas passamos todos eles². Também queremos-r
não invocar se a lista de arquivos estiver vazia, e a opçãodirname
(também específica do GNU) para imprimir os diretórios delimitados por NUL.-z
dirname
dirname
rg
não adiciona um./
prefixo a cada arquivo, é importante usar o--
delimitador de opções para comandos para os quais passamos a lista de arquivos como argumentos para evitar problemas com-
s iniciais nos nomes dos arquivos.Em resumo, para listas cujos valores podem ser qualquer sequência de bytes não-NUL, como caminhos de arquivo ou argumentos de comando arbitrários, você deseja usar registros delimitados por NUL como formato de intercâmbio, para passar listas programaticamente entre ferramentas e deixar apenas o formato humano para a ferramenta que dá feedback ao usuário (aqui a saída em forma de árvore de
tree
).Em um sistema não GNU, mas com o
zsh
shell, você pode fazer:Ou de uma só vez (supondo que haja pelo menos um arquivo correspondente):
O
u
(parau
nique) é o que substituitypeset -U
. O0
sinalizador de expansão de parâmetro é como dizemoszsh
para dividir em NULs. Alternativamente, poderíamos definirIFS=$'\0'
e confiar na divisão de palavras (feita na expansão de parâmetros sem aspas) com:Se você não tem utilitários GNU nem
zsh
, você sempre pode recorrer aperl
:¹ esse é o único valor de caractere/byte que não pode ocorrer em um argumento de comando (já que os argumentos são passados como strings delimitadas por NUL na
execve()
chamada do sistema), mas pode ocorrer em um fluxo de bytes alimentado por um pipe, portanto, é simples e óbvia maneira de separar argumentos arbitrários lá.-0
é uma extensão não padrão da implementação GNU dexargs
, mas agora é encontrada em muitas outras implementações² ou pelo menos tantos quanto couber em uma invocação, chamando
dirname
várias vezes apenas se necessário.ATUALIZAÇÃO: NOVA RESPOSTA FINAL:
Observe que
sort -zu
classifica e remove duplicatas em uma lista separada por nulos (-z
).DETALHES DA RESPOSTA ANTIGA:
Veja os comentários abaixo desta resposta. Minha resposta aqui não é tão robusta quanto a outra resposta de @Stéphane Chazelas .
Minha resposta abaixo originalmente não lidaria corretamente com nenhum nome de arquivo com espaços ou outro espaço em branco neles, nem lidaria com nomes de arquivo que começam com traço (
-
). Aqui está o meu comentário de resposta abaixo:Então, veja as duas respostas. O dele é tecnicamente melhor, mas para meus propósitos, o meu é "bom o suficiente" por enquanto e aponta que meu exemplo original na pergunta poderia ter funcionado com alterações super mínimas. Ex:
A resposta de @Stéphane Chazelas e os comentários da minha pergunta ( incluindo um do próprio criador do ripgrep !)
As strings do caminho de saída de
rg
NÃO são strings terminadas em nulo, portanto, remova-0
doxargs
comando (ou, inversamente, adicione-o aorg
comando também). É isso! Isso agora funciona :OU, você pode forçar as strings de caminho a serem terminadas em nulo adicionando
-0
ou--null
aorg
comando, então isso também funciona:Agora, por extensão, podemos passar todos os caminhos para
tree
também assim:RESPOSTA FINAL:
É isso! Eu simplesmente precisava adicionar ou subtrair
-0
ou--null
de ambasrg
e todas asxargs
chamadas, para mantê-las consistentes e esperando os mesmos delineadores ao analisar os vários caminhos.Adicionar
-0
ou--null
, no entanto, é melhor, porque permite caminhos com espaços ou outros espaços em branco neles, e adicionar--
também é bom porque permite caminhos que começam com traço (-
). Então, isso é o que eu fiz acima.Novamente, porém, veja a outra resposta também. Ele também classifica, remove duplicatas e lida com outras complexidades.
Palavras-chave: como usar xargs corretamente; analisar caminhos de saída grep ou ripgrep rg com xargs