Wywoływanie funkcji powłoki za pomocą xargs

168

Próbuję użyć xargs do równoległego wywołania bardziej złożonej funkcji.

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

To zwraca błąd

xargs: echo_var: No such file or directory

Wszelkie pomysły, jak mogę użyć xargs, aby to osiągnąć, lub jakiekolwiek inne rozwiązania byłyby mile widziane.

fac3
źródło
2
Niebezpieczeństwo, użytkownik1148366, Niebezpieczeństwo! Nie używaj basha do programowania równoległego - napotkasz tak wiele problemów. Używaj C / C ++ i pthreads lub wątków Java lub czegokolwiek, co sprawia, że ​​zastanawiasz się długo i intensywnie nad tym, co robisz, ponieważ programowanie równoległe wymaga dużo uwagi, aby było dobrze.
David Souther,
27
@DavidSouther Jeśli zadania są niezależne, takie jak konwersja wszystkich plików graficznych do formatu png, nie martw się. Kiedy masz synchronizację (poza czekaniem na zakończenie wszystkiego) i komunikację, robi się bałagan.
ctrl-alt-delor
@DavidSouther - Od dawna jestem programistą Java i ostatnio pracowałem w groovy. I nadal mówię ludziom: przyjaciele nie pozwalają znajomym pisać scenariusza basha. A jednak widzę ten post / rozwiązanie, ponieważ (smutna buźka :() Jestem zaangażowany w przetwarzanie równoległe w bash. Z łatwością mógłbym to zrobić w groovy / java. Złe!
Christian Bongiorno
Omówiono również na unix.stackexchange.com/questions/158564/…
Joshua Goldberg

Odpowiedzi:

172

Eksport funkcji powinien to zrobić (nieprzetestowany):

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

Możesz użyć wbudowanego printfzamiast zewnętrznego seq:

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

Ponadto użycie return 0i w exit 0ten sposób maskuje wszelkie wartości błędu, które mogą zostać wygenerowane przez poprzedzające je polecenie. Ponadto, jeśli nie ma błędu, jest to ustawienie domyślne, a zatem nieco zbędne.

@phobic wspomina, że ​​polecenie Bash można uprościć do

bash -c 'echo_var "{}"'

przenosząc {}bezpośrednio w nim. Ale jest podatny na wstrzyknięcie poleceń, jak wskazał @Sasha.

Oto przykład, dlaczego nie należy używać osadzonego formatu:

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

Inny przykład, dlaczego nie :

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

Oto, co jest wyprowadzane przy użyciu bezpiecznego formatu :

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

Jest to porównywalne do używania sparametryzowanych zapytań SQL w celu uniknięcia wstrzyknięcia .

Używam datetutaj zastępowania poleceń lub cudzysłowów zamiast rmpolecenia używanego w komentarzu Sashy, ponieważ jest ono nieniszczące.

Wstrzymano do odwołania.
źródło
14
Trochę więcej dyskusji: xargs wykonuje zupełnie nową instancję procesu o nazwie. W takim przypadku echo_varw PATH podajesz nazwę , która jest funkcją w tym skrypcie, a nie proces (program). Rozwiązanie Dennisa polega na wyeksportowaniu funkcji do użycia przez potomne procesy bash, a następnie rozwidleniu do podprocesu i wykonaniu tam.
David Souther,
7
jakie jest znaczenie _i \bez nich to nie działało dla mnie
Hashbrown
9
@Hashbrown: podkreślenie ( _) zapewnia miejsce na argv[0]( $0) i prawie wszystko może zostać tam użyte. Myślę, że dodałem odwrotny ukośnik-średnik ( \;) ze względu na jego użycie do zakończenia -execklauzuli w find, ale działa dla mnie bez niego tutaj. W rzeczywistości, gdyby funkcja miała użyć $@zamiast $1tego, zobaczyłaby średnik jako parametr, więc należy go pominąć.
Wstrzymano do odwołania.
4
-i argument xargs jest od tego czasu przestarzały. Zamiast tego użyj -I (duże i).
Nicolai S
11
Możesz to uprościć, dołączając argument z xargs do polecenia dla bash z bash -c 'echo_var "{}"'. Więc nie potrzebujesz znaku _ {} na końcu.
fobiczny
16

Używanie GNU Parallel wygląda następująco:

#!/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

Jeśli używasz wersji 20170822, nie musisz tego nawet robić, o export -file uruchomiłeś to:

. `which env_parallel.bash`
seq -f "n%04g" 1 100 | env_parallel -P 10 echo_var {} 
Ole Tange
źródło
skąd wziąć shopt dla osx?
Nick
nvm jest ustawiony w zsh
Nick,
Otrzymuję to poniżej eerror Ole sh: parallel_bash_environment: line 67: unexpected EOF while looking for matching '' sh: parallel_bash_environment: line 79: syntax error: nieoczekiwany koniec pliku sh: błąd podczas importowania definicji funkcji dla 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: line 79: syntax error: nieoczekiwany koniec file / usr / local / bin / bash: błąd podczas importowania definicji funkcji dla `...
Nick
Zostałeś oszukany: Shellshock nie wpłynął bezpośrednio na GNU Parallel. Jednak rozwiązanie problemu shellshock było takie: całkowicie zepsuło --env i sztuczkę env_parallel. Uważa się, że zostało to naprawione w wersji git: git.savannah.gnu.org/cgit/parallel.git/snapshot/ ...
Ole Tange
1
Podoba mi się ta odpowiedź, ponieważ sprawiła, że ​​odkryłem równoległe narzędzie
JR Całkowicie
10

Coś takiego powinno też działać:

function testing() { sleep $1 ; }
echo {1..10} | xargs -n 1 | xargs -I@ -P4 bash -c "$(declare -f testing) ; testing @ ; echo @ "
Eremita
źródło
1

Może to zła praktyka, ale jeśli definiujesz funkcje w .bashrcskrypcie lub innym, możesz opakować plik lub przynajmniej definicje funkcji za pomocą ustawienia 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
xdhmoore
źródło