Pergunta

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

A partir de http://en.wikipedia.org/wiki/XARGS:

encontre /path -type f -Print0 | XARGS -0 RM

Neste exemplo, encontre -se a entrada de XARGs com uma longa lista de nomes de arquivos. O XARGS divide essa lista em sublistas e chama a RM uma vez para cada sublista. Isso é mais eficiente do que esta versão funcionalmente equivalente:

encontre /path -type f -exec rm '{}' ;

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

Foi útil?

Solução

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

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

Da página do homem:

-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.

Outras dicas

Parece -me que todas as respostas existentes nesta página estão erradas, incluindo a marcada como correta. Isso decorre do fato de que a questão é ambiguamente redigida.

Resumo: Se você quiser executar o comando "Exatamente uma vez para cada linha de entrada dada," passando por toda a linha (sem a nova linha) para o comando como um argumento único, Então esta é a melhor maneira compatível com o Unix de fazê-lo:

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

GNU xargs pode ou não ter extensões úteis que permitem que você elimine com tr, mas eles não estão disponíveis no OS X e em outros sistemas Unix.

Agora, para a longa explicação ...


Há duas questões a serem levadas em consideração ao usar XARGs:

  1. Como isso divide a entrada em "argumentos"; e
  2. Quantos argumentos para passar no comando da criança de cada vez.

Para testar o comportamento de Xargs, precisamos de um utilitário que mostre quantas vezes está sendo executado e com quantos argumentos. Não sei se existe um utilitário padrão para fazer isso, mas podemos codificá -lo com bastante facilidade em Bash:

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

Supondo que você o salve como show No seu diretório atual e o faça executável, eis 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 lido assim (mudanças em negrito):

Como posso fazer XARGS executar o comando exatamente uma vez para cada argumento de entrada dada? Seu comportamento padrão é reduzir o entrada em argumentos e executar o comando o menor número possível, passando múltiplos argumentos para cada instância.

Então a resposta é -n 1.

Vamos comparar o comportamento padrão de Xargs, que divide a entrada em torno do espaço em branco e chama o comando o mais rápido 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 o ponto 1. Divisão de entrada e deveria ser lida como essa (muitas pessoas que vêm aqui parecem pensar que é esse o caso, ou estão confundindo os dois problemas):

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

Então a resposta é mais sutil.

Alguém poderia pensar que -L 1 Pode ser útil, mas acontece que não muda a análise de argumentos. Ele executa apenas o comando uma vez para cada linha de entrada, com tantos argumentos quanto na 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, será anexado ao próximo:

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

Claramente, -L não se trata de mudar a maneira como XARGS divide a entrada em argumentos.

O único argumento que o faz de maneira cruzada (excluindo extensões GNU) é -0, que divide a entrada em torno de NUL Bytes.

Então, é apenas uma questão de traduzir linhas de novo para o NUL com a ajuda de tr:

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

Agora, o argumento da análise parece certo, incluindo o espaço em branco à direita.

Finalmente, se você combinar esta técnica com -n 1, você obtém exatamente uma execução de comando por linha de entrada, qualquer que seja a entrada que tenha, que pode ser mais uma maneira de examinar a pergunta original (possivelmente a mais intuitiva, dado o título):

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

Se você quiser executar o comando para todo linha (ou seja, resultado) vindo de find, então o que você precisa do xargs por?

Tentar:

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

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

EDITAR:

(Após a edição da sua pergunta, esclarecendo que você sabe sobre -exec)

A partir de man xargs:

-EU Lines máximas
Use no máximo Lines máximas Linhas de entrada não branqueadas por linha de comando. Os espaços em branco à direita fazem com que uma linha de entrada seja logicamente continuada na próxima linha de entrada. Implica -x.

Observe que os nomes de arquivos que terminam em espaços em branco causariam 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

Então, se você não se importa com o -exec opção, é melhor você usar -print0 e -0:

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

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

Eu não uso -L 1 Resposta porque não lida com arquivos com espaços neles, o que é uma função essencial de find -print0.

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

Uma solução melhor é usar tr para converter novas linhas para nulo (\0) caracteres, e então use o xargs -0 argumento.

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 1 Argumento para fazer uma chamada para o 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 do encontro antes da convertendo as quebras em nulos.

find . -name \*.xml | grep -v /workspace/ | tr '\n' '\0' | xargs -0 tar -cf xml.tar

Outra alternativa ...

find /path -type f | while read ln; do echo "processing $ln"; done
find path -type f | xargs -L1 command 

é tudo o que você precisa.

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 uso de uso:

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

Excluirá todos os arquivos PYC neste diretório, mesmo que os arquivos PYC contenham espaços.

O comando a seguir encontrará todos os arquivos (-Type f) em /path e então copie -os usando cp para a pasta atual. Observe o uso se -I % para especificar um caráter de espaço reservado no cp linha 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

You can limit the number of lines, or arguments (if there are spaces between each argument) using the --max-lines or --max-args flags, respectively.

  -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.

It seems I don't have enough reputation to add a comment to Tobia's answer above, so I am adding this "answer" to help those of us wanting to experiment with xargs the same way on the Windows platforms.

Here is a windows batch file that does the same thing as Tobia's quickly coded "show" script:

@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.

@Draemon answers seems to be right with "-0" even with space in the file.

I was trying the xargs command and I found that "-0" works perfectly with "-L". even the spaces are treated (if input was null terminated ). the following is an example :

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

The following will split the nulls and execute the command on each argument in the list :

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

so -L1 will execute the argument on each null terminated character if used with "-0". To see the difference try :

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

even this will execute once :

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

The command will execute once as the "-L" now doesn't split on null byte. you need to provide both "-0" and "-L" to work.

In your example, the point of piping the output of find to xargs is that the standard behavior of find's -exec option is to execute the command once for each found file. If you're using find, and you want its standard behavior, then the answer is simple - don't use xargs to begin with.

execute ant task clean-all on every build.xml on current or sub-folder.

find . -name 'build.xml' -exec ant -f {} clean-all \;
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top