Faça xargs executar o comando uma vez para cada linha de entrada

341

Como posso fazer xargs executar o comando exatamente uma vez para cada linha de entrada fornecida? Seu comportamento padrão é dividir as linhas e executar o comando uma vez, passando várias linhas para cada instância.

De http://en.wikipedia.org/wiki/Xargs :

encontrar / caminho-tipo f -print0 | xargs -0 rm

Neste exemplo, find alimenta a entrada de xargs com uma longa lista de nomes de arquivos. O xargs divide essa lista em sublistas e chama rm uma vez para cada sublist. Isso é mais eficiente que esta versão funcionalmente equivalente:

encontre / caminho -type f -exec rm '{}' \;

Eu sei que esse achado tem a bandeira "exec". Estou apenas citando um exemplo ilustrativo de outro recurso.

Somente leitura
fonte
4
No exemplo que você fornecer, find /path -type f -deleteseria ainda mais eficiente :)
tzot
tente não usar xargs ...
Naib
6
OP, eu sei que essa pergunta é muito antiga, mas ainda aparece no Google e no IMHO, a resposta aceita está errada. Veja minha resposta mais longa abaixo.
Tobia
Por favor, considere mudar sua aceitação para a resposta de @ Tobia, que é muito melhor. A resposta aceita não manipula espaços nos nomes e não permite vários argumentos para o comando xargs, que é um dos principais recursos do xargs.
Grey

Respostas:

392

O seguinte funcionará apenas se você não tiver espaços em sua entrada:

xargs -L 1
xargs --max-lines=1 # synonym for the -L option

na página do manual:

-L max-lines
          Use at most max-lines nonblank input lines per command line.
          Trailing blanks cause an input line to be logically continued  on
          the next input line.  Implies -x.
Draemon
fonte
13
Para mim, pode sair como xargs -n 1o que você deu mostrou "lista de argumentos muito longa".
Wernight 20/09/11
19
Se MAX-LINESfor omitido, o padrão será 1, portanto, xargs -lé suficiente. Veja info xargs.
Thor
3
@Wernight: "-n1" não fornece 1 chamada por linha de entrada. talvez sua linha de entrada seja muito longa. demonstração: echo "foo bar" | xargs -n1 echo. portanto, se você digitar coisas como 'ls', ele não lidará bem com espaços.
gatoatigrado
8
Isto está errado. -L 1não responde à pergunta original e o -n 1faz apenas em uma das possíveis interpretações. Veja minha longa resposta abaixo.
Tobia
2
@Tobia: Responde à pergunta original, que era bastante específica sobre linhas de entrada. É exatamente o que -L 1faz. Para mim, o OP parecia estar claramente tentando evitar o comportamento padrão de fragmentação e, como isso foi aceito, presumo que estava certo. Sua resposta aborda um caso de uso ligeiramente diferente, no qual você também deseja o comportamento de chunking.
Draemon
206

Parece-me que todas as respostas existentes nesta página estão incorretas, incluindo a marcada como correta. Isso decorre do fato de que a pergunta é ambígua.

Resumo:   Se você deseja executar o comando "exatamente uma vez para cada linha de entrada fornecida", passando a linha inteira (sem a nova linha) para o comando como um único argumento, esta é a melhor maneira compatível com UNIX:

... | tr '\n' '\0' | xargs -0 -n1 ...

O GNU xargspode ou não ter extensões úteis que permitem eliminar tr, mas elas não estão disponíveis no OS X e em outros sistemas UNIX.

Agora, para a longa explicação…


Há dois problemas a serem considerados ao usar o xargs:

  1. como ele divide a entrada em "argumentos"; e
  2. quantos argumentos para passar o comando filho por vez.

Para testar o comportamento do xargs, precisamos de um utilitário que mostre quantas vezes ele está sendo executado e com quantos argumentos. Não sei se existe um utilitário padrão para fazer isso, mas podemos codificá-lo facilmente no bash:

#!/bin/bash
echo -n "-> "; for a in "$@"; do echo -n "\"$a\" "; done; echo

Supondo que você o salve como showem seu diretório atual e o torne executável, veja como funciona:

$ ./show one two 'three and four'
-> "one" "two" "three and four" 

Agora, se a pergunta original é realmente sobre o ponto 2. acima (como eu acho que é, depois de ler algumas vezes) e deve ser lida assim (alterações em negrito):

Como posso fazer xargs executar o comando exatamente uma vez para cada argumento de entrada fornecido? Seu comportamento padrão é dividir a entrada em argumentos e executar o comando o menor número de vezes possível , passando vários argumentos para cada instância.

então a resposta é -n 1.

Vamos comparar o comportamento padrão do xargs, que divide a entrada em branco e chama o comando o menor número de vezes possível:

$ echo one two 'three and four' | xargs ./show 
-> "one" "two" "three" "and" "four" 

e seu comportamento com -n 1:

$ echo one two 'three and four' | xargs -n 1 ./show 
-> "one" 
-> "two" 
-> "three" 
-> "and" 
-> "four" 

Se, por outro lado, a pergunta original era sobre a divisão de entradas do ponto 1. e era para ser lida assim (muitas pessoas que vêm aqui parecem pensar que é esse o caso, ou estão confundindo as duas questões):

Como posso fazer xargs executar o comando com exatamente um argumento para cada linha de entrada fornecida? Seu comportamento padrão é dividir as linhas em torno do espaço em branco .

então a resposta é mais sutil.

Alguém poderia pensar que isso -L 1poderia ajudar, mas acontece que não muda a análise de argumentos. Ele executa o comando apenas uma vez para cada linha de entrada, com tantos argumentos quanto nessa linha de entrada:

$ echo $'one\ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" 
-> "two" 
-> "three" "and" "four" 

Não apenas isso, mas se uma linha termina com espaço em branco, ela é anexada à seguinte:

$ echo $'one \ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" "two" 
-> "three" "and" "four" 

Claramente, -Lnão se trata de mudar a maneira como o xargs divide a entrada em argumentos.

O único argumento que faz isso de uma forma multiplataforma (excluindo extensões GNU) é -0, que divide a entrada em torno de bytes NUL.

Depois, basta traduzir novas linhas para NUL com a ajuda de tr:

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 ./show 
-> "one " "two" "three and four" 

Agora, a análise do argumento parece correta, incluindo o espaço em branco à direita.

Por fim, se você combinar essa técnica -n 1, obterá exatamente uma execução de comando por linha de entrada, qualquer que seja sua entrada, que pode ser outra maneira de analisar a questão original (possivelmente a mais intuitiva, considerando o título):

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 -n1 ./show 
-> "one " 
-> "two" 
-> "three and four" 
Tobia
fonte
Parece que esta é a melhor resposta. no entanto, ainda não entendo qual é a diferença entre -L e -n ... você pode explicar um pouco mais?
olala
5
@olala -Lexecuta o comando uma vez por linha de entrada (mas um espaço no final de uma linha o une à próxima linha, e a linha ainda é dividida em argumentos de acordo com o espaço em branco); enquanto -nexecuta o comando uma vez por argumento de entrada. Se você contar o número de ->nos exemplos de saída, esse é o número de vezes que o script ./showé executado.
Tobia
Entendo! não percebeu que um espaço no final de uma linha o une à próxima linha. obrigado!
olala
4
O GNU xargspode ou não ter extensões úteis que permitem acabar comtr ele. Tem uma extensão muito útil; from xargs --help- -d, --delimiter = Os itens CHARACTER no fluxo de entrada são separados por CHARACTER, não por espaço em branco; desativa citar e processamento de barra invertida e processamento EOF lógica
Piotr Dobrogost
Esta resposta parece confusa em relação -L. -Lnão diz quantas vezes executar o script por linha, diz quantas linhas de dados de entrada devem consumir por vez.
Moberg
22

Se você deseja executar o comando para cada linha (ou seja, resultado) proveniente find, para que precisa xargs?

Tentar:

find caminho -type f -exec seu comando {} \;

onde o literal {}é substituído pelo nome do arquivo e o literal \;é necessário para findsaber que o comando personalizado termina aí.

EDITAR:

(após a edição da sua pergunta, esclarecendo que você conhece -exec)

De man xargs:

-L max-lines
Use no máximo linhas máximas de linhas de entrada não vazias por linha de comando. Espaços em branco à direita fazem com que uma linha de entrada continue logicamente na próxima linha de entrada. Implica -x.

Observe que os nomes de arquivos que terminam em espaços em branco podem causar problemas se você usar xargs:

$ mkdir /tmp/bax; cd /tmp/bax
$ touch a\  b c\  c
$ find . -type f -print | xargs -L1 wc -l
0 ./c
0 ./c
0 total
0 ./b
wc: ./a: No such file or directory

Portanto, se você não se importa com a -execopção, é melhor usar -print0e -0:

$ find . -type f -print0 | xargs -0L1 wc -l
0 ./c
0 ./c
0 ./b
0 ./a
tzot
fonte
17

Como posso fazer xargs executar o comando exatamente uma vez para cada linha de entrada fornecida?

-L 1é a solução simples, mas não funciona se algum dos arquivos contiver espaços. Esta é uma função essencial do -print0argumento de localização - para separar os argumentos pelo caractere '\ 0' em vez de espaço em branco. Aqui está um exemplo:

echo "file with space.txt" | xargs -L 1 ls
ls: file: No such file or directory
ls: with: No such file or directory
ls: space.txt: No such file or directory

Uma solução melhor é usar trpara converter novas linhas em \0caracteres nulos ( ) e, em seguida, use o xargs -0argumento Aqui está um exemplo:

echo "file with space.txt" | tr '\n' '\0' | xargs -0 ls
file with space.txt

Se você precisar limitar o número de chamadas, poderá usar o -n 1argumento para fazer uma chamada ao programa para cada entrada:

echo "file with space.txt" | tr '\n' '\0' | xargs -0 -n 1 ls

Isso também permite filtrar a saída de localização antes de converter as quebras em nulos.

find . -name \*.xml | grep -v /target/ | tr '\n' '\0' | xargs -0 tar -cf xml.tar
cinzento
fonte
11
Há um erro de sintaxe no segundo bloco de código tr '\ n' '\ 0 \ => tr' \ n '' \ 0 ', tentei corrigir isso, mas "As edições devem ter pelo menos 6 caracteres" (isso parece ser estúpido como git recusando-se a comprometer, porque a minha mudança foi menos de 6 caracteres)
htaccess
11
O que isso significa: "Outro problema com o uso -Ltambém é que ele não permite vários argumentos para cada xargschamada de comando".
Moberg
Melhorei minha resposta para remover essas informações estranhas @Moberg.
cinzento
11

Outra alternativa ...

find /path -type f | while read ln; do echo "processing $ln"; done
Richard
fonte
9

Essas duas maneiras também funcionam e funcionarão para outros comandos que não estão usando o find!

xargs -I '{}' rm '{}'
xargs -i rm '{}'

exemplo de caso de uso:

find . -name "*.pyc" | xargs -i rm '{}'

excluirá todos os arquivos pyc nesse diretório, mesmo que os arquivos pyc contenham espaços.

Alex Riedler
fonte
Isso emite uma chamada de utilitário para cada elemento que não é ideal.
Grey
7
find path -type f | xargs -L1 command 

é tudo o que você precisa.


fonte
4

O comando a seguir encontrará todos os arquivos (-tipo f) /pathe os copiará cppara a pasta atual. Observe o uso if -I %para especificar um caractere de espaço reservado na cplinha de comando para que os argumentos possam ser colocados após o nome do arquivo.

find /path -type f -print0 | xargs -0 -I % cp % .

Testado com xargs (GNU findutils) 4.4.0


fonte
2

Você pode limitar o número de linhas ou argumentos (se houver espaços entre cada argumento) usando os sinalizadores --max-lines ou --max-args, respectivamente.

  -L max-lines
         Use at most max-lines nonblank input lines per command line.  Trailing blanks cause an input line to be logically continued on the next  input
         line.  Implies -x.

  --max-lines[=max-lines], -l[max-lines]
         Synonym  for  the -L option.  Unlike -L, the max-lines argument is optional.  If max-args is not specified, it defaults to one.  The -l option
         is deprecated since the POSIX standard specifies -L instead.

  --max-args=max-args, -n max-args
         Use at most max-args arguments per command line.  Fewer than max-args arguments will be used if the size (see  the  -s  option)  is  exceeded,
         unless the -x option is given, in which case xargs will exit.
Somente leitura
fonte
0

Parece que não tenho reputação suficiente para adicionar um comentário à resposta de Tobia acima , então estou adicionando essa "resposta" para ajudar aqueles que desejam experimentar xargsda mesma maneira nas plataformas Windows.

Aqui está um arquivo em lotes do Windows que faz a mesma coisa que o script "show" rapidamente codificado por Tobia:

@echo off
REM
REM  cool trick of using "set" to echo without new line
REM  (from:  http://www.psteiner.com/2012/05/windows-batch-echo-without-new-line.html)
REM
if "%~1" == "" (
    exit /b
)

<nul set /p=Args:  "%~1"
shift

:start
if not "%~1" == "" (
    <nul set /p=, "%~1"
    shift
    goto start
)
echo.
CrashNeb
fonte
0

As respostas do @Demonemon parecem estar corretas com "-0", mesmo com espaço no arquivo.

Eu estava tentando o comando xargs e descobri que "-0" funciona perfeitamente com "-L". até os espaços são tratados (se a entrada foi nula finalizada). o seguinte é um exemplo:

#touch "file with space"
#touch "file1"
#touch "file2"

A seguir, os valores nulos serão divididos e o comando será executado em cada argumento da lista:

 #find . -name 'file*' -print0 | xargs -0 -L1
./file with space
./file1
./file2

portanto -L1, executará o argumento em cada caractere terminado nulo se usado com "-0". Para ver a diferença, tente:

 #find . -name 'file*' -print0 | xargs -0 | xargs -L1
 ./file with space ./file1 ./file2

mesmo isso será executado uma vez:

 #find . -name 'file*' -print0  | xargs -0  | xargs -0 -L1
./file with space ./file1 ./file2

O comando será executado uma vez, pois o "-L" agora não é dividido em byte nulo. você precisa fornecer "-0" e "-L" para funcionar.

Mohammad Karmi
fonte
-3

No seu exemplo, o ponto de canalizar a saída de find para xargs é que o comportamento padrão da opção -exec de find é executar o comando uma vez para cada arquivo encontrado. Se você estiver usando find e quiser o comportamento padrão, a resposta é simples - não use xargs para começar.

Sherm Pendley
fonte
Na verdade, o que posso sugerir das edições do OP é que os dados de entrada não têm nada a ver com isso find, e é por isso que eles não preferem a -execopção.
tzot 24/12/2009
-3

execute a tarefa ant limpar tudo em todos os build.xml da atual ou subpasta.

find . -name 'build.xml' -exec ant -f {} clean-all \;
sergiofbsilva
fonte
Nem todo mundo antinstalou.
Gray