Uruchamianie wielu poleceń za pomocą xargs

310
cat a.txt | xargs -I % echo %

W powyższym przykładzie xargs przyjmuje echo %jako argument polecenia. Ale w niektórych przypadkach potrzebuję wielu poleceń do przetworzenia argumentu zamiast jednego. Na przykład:

cat a.txt | xargs -I % {command1; command2; ... }

Ale xargs nie akceptuje tej formy. Jednym ze znanych mi rozwiązań jest to, że mogę zdefiniować funkcję do zawijania poleceń, ale nie jest to potok, nie wolę tego. Czy jest inne rozwiązanie?

Dagang
źródło
1
Większość tych odpowiedzi to luki w zabezpieczeniach . Tutaj znajdziesz potencjalnie dobrą odpowiedź.
Mateen Ulhaq
2
Używam xargs do prawie wszystkiego, ale nienawidzę umieszczania poleceń w ciągach i jawnego tworzenia podpowłok. Jestem na skraju nauki, jak whiletworzyć potoki w pętli, która może zawierać wiele poleceń.
Sridhar Sarnobat

Odpowiedzi:

443
cat a.txt | xargs -d $'\n' sh -c 'for arg do command1 "$arg"; command2 "$arg"; ...; done' _

... lub bez bezużytecznego użycia kota :

<a.txt xargs -d $'\n' sh -c 'for arg do command1 "$arg"; command2 "$arg"; ...; done' _

Aby wyjaśnić niektóre drobniejsze kwestie:

  • Zastosowanie "$arg"zamiast %(i brak -Iw xargslinii poleceń) ze względów bezpieczeństwa: przekazywanie danych na temat shlisty parametru wywołania „S zamiast zastąpienie go do zawartości Uniemożliwia to kodowi, który danych może zawierać (na przykład $(rm -rf ~), w celu szczególnie złośliwy przykład) przed wykonaniem jako kod.

  • Podobnie użycie -d $'\n'rozszerzenia GNU powoduje, xargsże każdą linię pliku wejściowego traktuje się jako osobny element danych. Albo to, albo -0(która spodziewa się wartości NUL zamiast znaków nowej linii) jest konieczne, aby uniemożliwić xargsowi próbowanie zastosowania parsowania typu powłoki (ale niezupełnie kompatybilnego z powłoką) do strumienia, który czyta. (Jeśli nie masz GNU xargs, możesz użyć, tr '\n' '\0' <a.txt | xargs -0 ...aby uzyskać odczyt zorientowany liniowo bez -d).

  • _Jest zastępczy $0, tak, że inne wartości danych dodawane przez xargssię $1i dalej, co okazuje się być domyślny zestaw wartościom jako forpętla iteracyjnie.

Keith Thompson
źródło
58
Dla osób niezaznajomionych z sh -c- pamiętaj, że średnik po każdym poleceniu nie jest opcjonalny, nawet jeśli jest to ostatnie polecenie na liście.
Noah Sussman,
6
Przynajmniej w mojej konfiguracji musi być spacja zaraz po początkowym „{”. Przed końcowym nawiasem klamrowym nie jest wymagane miejsce, ale jak zauważył pan Sussman, potrzebny jest średnik zamykający.
willdye,
4
Ta odpowiedź wcześniej zawierała nawiasy klamrowe command1i command2; Później zrozumiałem, że nie są konieczne.
Keith Thompson
24
Aby wyjaśnić powyższe komentarze na temat średników, przed zamykaniem wymagany jest średnik }: sh -c '{ command1; command2; }' -- but it's not required at the end of a command sequence that doesn't use braces: sh -c 'command1; polecenie2 ''
Keith Thompson
8
Jeśli umieścisz %znak gdzieś w przekazanym ciągu sh -c, to jest to podatne na luki w zabezpieczeniach: nazwa pliku zawierająca $(rm -rf ~)'$(rm -rf ~)'(i to jest całkowicie legalny podciąg w nazwie pliku na wspólnych systemach plików UNIX!) Spowoduje, że ktoś będzie miał bardzo zły dzień .
Charles Duffy
35

Dzięki GNU Parallel możesz:

cat a.txt | parallel 'command1 {}; command2 {}; ...; '

Obejrzyj filmy wprowadzające, aby dowiedzieć się więcej: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Ze względów bezpieczeństwa zaleca się użycie menedżera pakietów do zainstalowania. Ale jeśli nie możesz tego zrobić, możesz użyć tej 10-sekundowej instalacji.

10 sekundowa instalacja spróbuje wykonać pełną instalację; jeśli to się nie powiedzie, instalacja osobista; jeśli to się nie powiedzie, minimalna instalacja.

$ (wget -O - pi.dk/3 || lynx -source pi.dk/3 || curl pi.dk/3/ || \
   fetch -o - http://pi.dk/3 ) > install.sh
$ sha1sum install.sh | grep 3374ec53bacb199b245af2dda86df6c9
12345678 3374ec53 bacb199b 245af2dd a86df6c9
$ md5sum install.sh | grep 029a9ac06e8b5bc6052eac57b2c3c9ca
029a9ac0 6e8b5bc6 052eac57 b2c3c9ca
$ sha512sum install.sh | grep f517006d9897747bed8a4694b1acba1b
40f53af6 9e20dae5 713ba06c f517006d 9897747b ed8a4694 b1acba1b 1464beb4
60055629 3f2356f3 3e9c4e3c 76e3f3af a9db4b32 bd33322b 975696fc e6b23cfb
$ bash install.sh
Ole Tange
źródło
56
Instalowanie narzędzi poprzez uruchamianie losowych skryptów z nieznanych stron jest okropną praktyką. Równolegle ma oficjalnie wszystkie pakiety dla popularnych dystrybucji, którym można zaufać (do pewnego
stopnia
4
Zobaczmy, który jest najłatwiejszym wektorem ataku: Pi.dk jest kontrolowany przez autora GNU Parallel, więc aby zaatakować, będziesz musiał włamać się na serwer lub przejąć DNS. Aby przejąć oficjalny pakiet dystrybucji, często możesz po prostu zgłosić się na ochotnika do jego utrzymania. Chociaż ogólnie możesz mieć rację, wydaje się, że w tym konkretnym przypadku twój komentarz nie jest uzasadniony.
Ole Tange,
10
W praktyce nie wiem, czy pi.dk należy do autora. Właściwie sprawdzenie, czy tak jest, myślenie o tym, jak używać ssl w wget i sprawdzanie, czy to polecenie robi to, co powinno, to trochę pracy. Twierdzenie, że oficjalny pakiet może zawierać złośliwy kod, jest prawdą, ale dotyczy to również pakietu wget.
Fabian
3
To może nie być najlepsze rozwiązanie, jeśli każde z poleceń, które OP chce wykonać, musi być sekwencyjne, prawda?
IcarianComplex,
2
@IcarianComplex Dodanie -j1 to naprawi.
Ole Tange,
26

To tylko kolejne podejście bez Xargsa i kota:

while read stuff; do
  command1 "$stuff"
  command2 "$stuff"
  ...
done < a.txt
hmontoliu
źródło
2
Buggy, jak podano. O ile nie wyczyścisz IFS, zignoruje początkowe i końcowe białe spacje w nazwach plików; chyba że dodasz -r, nazwy plików z dosłownymi ukośnikami odwrotnymi będą ignorowane.
Charles Duffy
Nie odpowiada na pytanie. Pytanie dotyczyło konkretnie xargs. (Trudno to rozszerzyć, aby zrobić coś podobnego do opcji GNU xargs' -P<n>)
Gert van den Berg
1
Działa to doskonale. Możesz go również użyć jako $ command | while read line; do c1 $line; c2 $line; done
potokowego
25

Możesz użyć

cat file.txt | xargs -i  sh -c 'command {} | command2 {} && command3 {}'

{} = zmienna dla każdej linii w pliku tekstowym

Ossama
źródło
8
To jest niepewne. Co jeśli twój file.txtzawiera dane $(rm -rf ~)jako podciąg?
Charles Duffy
19

Jedną z rzeczy, które robię, jest dodanie do .bashrc / .profile tej funkcji:

function each() {
    while read line; do
        for f in "$@"; do
            $f $line
        done
    done
}

wtedy możesz robić takie rzeczy jak

... | each command1 command2 "command3 has spaces"

która jest mniej szczegółowa niż xargs lub -exec. Możesz również zmodyfikować funkcję, aby wstawić wartość z odczytu w dowolnym miejscu w komendach do każdego polecenia, jeśli potrzebujesz tego również.

mwm
źródło
1
Niedoceniana odpowiedź, jest to bardzo przydatne
charlesreid1
15

Wolę styl, który pozwala na tryb pracy na sucho (bez | sh):

cat a.txt | xargs -I % echo "command1; command2; ... " | sh

Działa również z rurami:

cat a.txt | xargs -I % echo "echo % | cat " | sh
brablc
źródło
2
To działa, aż chce się używać GNU xargs' -Popcję ... (Jeśli nie, to najczęściej korzystają -execna find, ponieważ moje wejścia są przeważnie nazwy plików)
Gert van den Berg
13

Trochę późno na imprezę.

Używam poniższego formatu do kompresji moich katalogów tysiącami małych plików przed migracją. Jeśli nie potrzebujesz pojedynczych cudzysłowów wewnątrz poleceń, powinno działać.

Po pewnych modyfikacjach jestem pewien, że przyda się komuś. Testowane w Cygwin(babun)

find . -maxdepth 1 ! -path . -type d -print0 | xargs -0 -I @@ bash -c '{ tar caf "@@.tar.lzop" "@@" && echo Completed compressing directory "@@" ; }'

find .Znajdź tutaj
-maxdepth 1Nie wchodź do katalogów potomnych
! -path .Wyklucz. / Bieżąca ścieżka katalogu
-type dpasuje tylko do katalogów
-print0Oddziel wyniki przez bajty null \ 0
| xargsPotok do xargs Dane
-0wejściowe są oddzielone null bajty
-I @@Symbol zastępczy to @@. Zamień @@ na dane wejściowe.
bash -c '...'Uruchom polecenie Bash
{...}Grupowanie poleceń
&&Wykonaj następne polecenie tylko wtedy, gdy poprzednie polecenie zakończyło się pomyślnie (wyjście 0)

Finał ;jest ważny, inaczej się nie powiedzie.

Wynik:

Completed compressing directory ./Directory1 with meta characters in it
Completed compressing directory ./Directory2 with meta characters in it
Completed compressing directory ./Directory3 with meta characters in it

Aktualizacja z lipca 2018 r .:

Jeśli lubisz hacki i zabawę, oto coś ciekawego:

echo "a b c" > a.txt
echo "123" >> a.txt
echo "###this is a comment" >> a.txt
cat a.txt
myCommandWithDifferentQuotes=$(cat <<'EOF'                                     
echo "command 1: $@"; echo 'will you do the fandango?'; echo "command 2: $@"; echo
EOF
)
< a.txt xargs -I @@ bash -c "$myCommandWithDifferentQuotes" -- @@

Wynik:

command 1: a b c
will you do the fandango?
command 2: a b c

command 1: 123
will you do the fandango?
command 2: 123

command 1: ###this is a comment
will you do the fandango?
command 2: ###this is a comment

Objaśnienie:
- Utwórz pojedynczy skrypt liniowy i zapisz go w zmiennej
- xargs odczytuje a.txti wykonuje jako bashskrypt
- @@ upewnia się za każdym razem, gdy przekazywany jest cały wiersz
- Umieszczenie @@po --upewnia się, że @@parametr bashpolecenia pozycyjnego jest brany pod uwagę, a nie jako bashpoczątek OPTION, tj. jak -csam, co oznaczarun command

--jest magiczny, działa z wieloma innymi rzeczami, np. sshnawetkubectl

sdkks
źródło
1
Użyłem konfiguracji z tego typu rzeczami: find . -type f -print0|xargs -r0 -n1 -P20 bash -c 'f="{}";ls -l "$f"; gzip -9 "$f"; ls -l "$f.gz"'(Jest trochę łatwiej przy konwersji pętli)
Gert van den Berg
1
Późna edycja mojego poprzedniego komentarza: (Jeśli nazwy plików zawierają podwójne cudzysłowy, istnieje problem z bezpieczeństwem ...) (Wydaje się, że używanie "$@"to jedyny sposób, aby tego uniknąć ... ( -n1jeśli chcesz ograniczyć liczbę parametrów))
Gert van den Berg
Należy zauważyć, że --jest używany przez powłokę, aby powiedzieć, że nie ma już więcej opcji do zaakceptowania. Pozwala to tam się -po --zbyt. Możesz uzyskać bardzo interesujący i mylący wynik, jeśli tego nie zrobisz, np. grep -rGdy weźmiesz pod uwagę wzorzec -! Sposób, w jaki to wypowiadasz, nie wyjaśnia tego, ale tak naprawdę nie wyjaśnia, jak to działa. Iirc to kwestia POSIX, ale i tak warto to podkreślić, tak myślę. Tylko coś do rozważenia. I uwielbiam ten bonus btw!
Pryftan
10

To wydaje się być najbezpieczniejszą wersją.

tr '[\n]' '[\0]' < a.txt | xargs -r0 /bin/bash -c 'command1 "$@"; command2 "$@";' ''

( -0Mogą być usunięte i trzastąpione przekierowania (lub plik można zastąpić null oddzielone plik zamiast). Jest to przede wszystkim tam, ponieważ używam głównie xargsze findprzy -print0wyjściu) (To może również być istotna w xargswersjach bez -0rozszerzenia)

Jest to bezpieczne, ponieważ podczas wykonywania argumenty przekazują parametry do powłoki jako tablicę. Powłoka (przynajmniej bash) przekazałaby je następnie jako niezmienioną tablicę do innych procesów, gdy wszystkie zostaną uzyskane przy użyciu["$@"][1]

Jeśli użyjesz ...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";' '', przypisanie nie powiedzie się, jeśli ciąg zawiera podwójne cudzysłowy. Dotyczy to każdego wariantu używającego -ilub -I. (Ponieważ jest zamieniany na ciąg znaków, zawsze możesz wstrzykiwać polecenia, wstawiając nieoczekiwane znaki (takie jak cudzysłowy, znaki wsteczne lub znaki dolara) do danych wejściowych)

Jeśli polecenia mogą przyjmować tylko jeden parametr na raz:

tr '[\n]' '[\0]' < a.txt | xargs -r0 -n1 /bin/bash -c 'command1 "$@"; command2 "$@";' ''

Lub z nieco mniejszą ilością procesów:

tr '[\n]' '[\0]' < a.txt | xargs -r0 /bin/bash -c 'for f in "$@"; do command1 "$f"; command2 "$f"; done;' ''

Jeśli masz GNU xargslub inny z -Prozszerzeniem i chcesz uruchomić 32 procesy równolegle, każdy z nie więcej niż 10 parametrami dla każdego polecenia:

tr '[\n]' '[\0]' < a.txt | xargs -r0 -n10 -P32 /bin/bash -c 'command1 "$@"; command2 "$@";' ''

Powinno to być odporne na wszelkie znaki specjalne na wejściu. (Jeśli dane wejściowe są rozdzielone zerą.) trWersja otrzyma nieprawidłowe dane wejściowe, jeśli niektóre wiersze zawierają znaki nowego wiersza, ale jest to nieuniknione w przypadku pliku oddzielonego znakiem nowego wiersza.

Pusty pierwszy parametr dla bash -cwynika z tego: (Ze strony podręcznika bashman ) (Dzięki @clacke)

-c   If the -c option is present, then  commands  are  read  from  the  first  non-option  argument  com
     mand_string.   If there are arguments after the command_string, the first argument is assigned to $0
     and any remaining arguments are assigned to the positional parameters.  The assignment  to  $0  sets
     the name of the shell, which is used in warning and error messages.
Gert van den Berg
źródło
Powinno to działać nawet w przypadku podwójnych cudzysłowów w nazwach plików. Wymaga to powłoki, która odpowiednio obsługuje"$@"
Gert van den Berg
Brakuje argumentu argv [0] do bash. bash -c 'command1 "$@"; command2 "$@";' arbitrarytextgoeshere
clacke,
3
Tu nie chodzi o to, co robi xargs. bashz -cbierze pierwszy (po poleceniach) jeden argument, który będzie nazwa procesu, wówczas przyjmuje argumentów pozycyjnych. Spróbuj bash -c 'echo "$@" ' 1 2 3 4i zobacz, co wyjdzie.
clacke,
Fajnie jest mieć bezpieczną wersję, która nie ma Bobby-Tabled.
Mateen Ulhaq
8

Innym możliwym rozwiązaniem, które działa dla mnie, jest coś w stylu -

cat a.txt | xargs bash -c 'command1 $@; command2 $@' bash

Zwróć uwagę na „bash” na końcu - zakładam, że jest przekazywany jako argv [0] do bash. Bez tej składni pierwszy parametr każdego polecenia zostanie utracony. Może to być dowolne słowo.

Przykład:

cat a.txt | xargs -n 5 bash -c 'echo -n `date +%Y%m%d-%H%M%S:` ; echo " data: " $@; echo "data again: " $@' bash
tavvit
źródło
5
Jeśli nie cytujesz "$@", to dzielisz ciągi znaków i rozszerzasz listę argumentów.
Charles Duffy
2

Mój obecny BKM do tego jest

... | xargs -n1 -I % perl -e 'system("echo 1 %"); system("echo 2 %");'

Szkoda, że ​​używa to perla, który jest mniej prawdopodobne niż bash; ale obsługuje więcej danych wejściowych niż zaakceptowana odpowiedź. (Z zadowoleniem przyjmuję wszechobecną wersję, która nie opiera się na perlu.)

@ Sugestia KeithThompsona dotycząca

 ... | xargs -I % sh -c 'command1; command2; ...'

jest świetny - chyba że masz znak komentarza powłoki #, w którym to przypadku część pierwszego polecenia i wszystkie drugie polecenie zostaną obcięte.

Hashes # może być dość powszechne, jeśli dane wejściowe pochodzą z listy systemów plików, takich jak ls lub find, a twój edytor tworzy pliki tymczasowe z # w nazwie.

Przykład problemu:

$ bash 1366 $>  /bin/ls | cat
#Makefile#
#README#
Makefile
README

Ups, tutaj jest problem:

$ bash 1367 $>  ls | xargs -n1 -I % sh -i -c 'echo 1 %; echo 2 %'
1
1
1
1 Makefile
2 Makefile
1 README
2 README

Ahh, to lepiej:

$ bash 1368 $>  ls | xargs -n1 -I % perl -e 'system("echo 1 %"); system("echo 2 %");'
1 #Makefile#
2 #Makefile#
1 #README#
2 #README#
1 Makefile
2 Makefile
1 README
2 README
$ bash 1369 $>  
Krazy Glew
źródło
5
# problem można łatwo rozwiązać za pomocą cytatów:ls | xargs -I % sh -c 'echo 1 "%"; echo 2 "%"'
gpl