Chamando funções de shell com xargs


168

Estou tentando usar xargs para chamar uma função mais complexa em paralelo.

#!/bin/bash
echo_var(){
    echo $1
    return 0
}
seq -f "n%04g" 1 100 |xargs -n 1 -P 10 -i echo_var {} 
exit 0

Isso retorna o erro

xargs: echo_var: No such file or directory

Quaisquer idéias sobre como eu posso usar o xargs para fazer isso, ou qualquer outra solução, serão bem-vindas.


2
Perigo, usuário1148366, Perigo! Não use o bash para programação paralela - você terá muitos problemas. Use C / C ++ e pthreads, ou threads Java, ou qualquer coisa que faça você pensar muito sobre o que está fazendo, porque a programação paralela exige muita reflexão para se acertar.
David Souther

27
@DavidSouther Se as tarefas forem independentes, como converter todos esses arquivos de imagem em png, não se preocupe. É quando você tem sincronização (além da espera para que todos terminem) e comunicação que fica confusa.
Ctrl-alt-delor

@ DavidSouther - Eu sou um desenvolvedor Java de muito tempo e tenho trabalhado no groovy ultimamente. E continuo dizendo às pessoas: amigos não deixam amigos escreverem o roteiro do bash. E, no entanto, encontro-me a olhar para este post / solução, porque (face triste :() Estou empenhado em processamento paralelo em bash que eu poderia facilmente fazê-lo em Groovy / java Bad..!
Christian Bongiorno

Respostas:


172

A exportação da função deve fazê-lo (não testado):

export -f echo_var
seq -f "n%04g" 1 100 | xargs -n 1 -P 10 -I {} bash -c 'echo_var "$@"' _ {}

Você pode usar o builtin em printfvez do externo seq:

printf "n%04g\n" {1..100} | xargs -n 1 -P 10 -I {} bash -c 'echo_var "$@"' _ {}

Além disso, usar return 0e exit 0assim mascara qualquer valor de erro que possa ser produzido pelo comando que o precede. Além disso, se não houver erro, é o padrão e, portanto, um pouco redundante.

@phobic menciona que o comando Bash poderia ser simplificado para

bash -c 'echo_var "{}"'

movendo o {}diretamente dentro dele. Mas é vulnerável à injeção de comandos, como apontado por @Sasha.

Aqui está um exemplo de por que você não deve usar o formato incorporado:

$ echo '$(date)' | xargs -I {} bash -c 'echo_var "{}"'
Sun Aug 18 11:56:45 CDT 2019

Outro exemplo de por que não :

echo '\"; date\"' | xargs -I {} bash -c 'echo_var "{}"'

É isso que é produzido usando o formato seguro :

$ echo '$(date)' | xargs -I {} bash -c 'echo_var "$@"' _ {}
$(date)

Isso é comparável ao uso de consultas SQL parametrizadas para evitar injeção .

Estou usando dateuma substituição de comando ou entre aspas aqui, em vez do rmcomando usado no comentário de Sasha, pois não é destrutivo.


14
Um pouco mais de discussão: xargs executa uma instância completamente nova do processo chamado. Nesse caso, você fornece o nome echo_var, que é uma função neste script, não um processo (programa) no seu PATH. O que a solução de Dennis faz é exportar a função que os processos filho bash devem usar, bifurcar para o subprocesso e executar lá.
David Souther

7
Qual é o significado de _e \, sem eles, não estava trabalhando para mim
Hashbrown

9
@Hashbrown: O sublinhado ( _) fornece um espaço reservado para argv[0]( $0) e quase tudo pode ser usado lá. Acho que adicionei a barra invertida-ponto-e-vírgula ( \;) por causa de seu uso para encerrar a -execcláusula find, mas funciona para mim sem ela aqui. De fato, se a função fosse usada em $@vez de $1, ela veria o ponto-e-vírgula como um parâmetro, portanto, ela deve ser omitida.
Pausado até novo aviso.

4
O argumento -i para xargs foi descontinuado. Use -I (capital i) em vez disso.
Nicolai S

11
Você pode simplificar isso incluindo o argumento de xargs na cadeia de comandos do bash with bash -c 'echo_var "{}"'. Portanto, você não precisa do _ {} no final.
585 phobic ph

16

O uso do GNU Parallel é assim:

#!/bin/bash
echo_var(){
    echo $1
    return 0
}
export -f echo_var
seq -f "n%04g" 1 100 | parallel -P 10 echo_var {} 
exit 0

Se você usa a versão 20170822, nem precisa export -f, desde que tenha executado isso:

. `which env_parallel.bash`
seq -f "n%04g" 1 100 | env_parallel -P 10 echo_var {} 

onde posso comprar osx?
11114 Nick

NVM é setopt em zsh
Nick

Obtendo isso abaixo eerror Ole sh: parallel_bash_environment: line 67: unexpected EOF while looking for matching '' sh: parallel_bash_environment: linha 79: erro de sintaxe: final inesperado do arquivo sh: erro ao importar definição de função para parallel_bash_environment' /usr/local/bin/bash: parallel_bash_environment: line 67: unexpected EOF while looking for matching '' / usr / local / bin / bash: parallel_bash_environment: linha 79: erro de sintaxe: final inesperado arquivo / usr / local / bin / bash: erro ao importar definição de função para `...
Nick

Você sofreu um shellaftershocked: o Shellshock não afetou o GNU Parallel diretamente. A solução para o shellshock, no entanto, foi: Ele quebrou completamente --env e o truque env_parallel. Acredita-se para ser corrigido na versão git: git.savannah.gnu.org/cgit/parallel.git/snapshot/...
Ole Tange

1
Eu gosto desta resposta, porque ele me fez descobrir a ferramenta paralela
JR Utily

10

Algo assim deve funcionar também:

function testing() { sleep $1 ; }
echo {1..10} | xargs -n 1 | xargs -I@ -P4 bash -c "$(declare -f testing) ; testing @ ; echo @ "

1

Talvez isso seja uma prática ruim, mas você, se estiver definindo funções em um .bashrcou outro script, poderá agrupar o arquivo ou pelo menos as definições de função com uma configuração de allexport:

set -o allexport

function funcy_town {
  echo 'this is a function'
}
function func_rock {
  echo 'this is a function, but different'
}
function cyber_func {
  echo 'this function does important things'
}
function the_man_from_funcle {
  echo 'not gonna lie'
}
function funcle_wiggly {
  echo 'at this point I\'m doing it for the funny names'
}
function extreme_function {
  echo 'goodbye'
}

set +o allexport
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.