Fazer XARGS executar o comando uma vez para cada linha de entrada
-
11-07-2019 - |
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.
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:
- Como isso divide a entrada em "argumentos"; e
- 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 \;