Rularea mai multor comenzi cu xargs (Programare, Bash, Xargs)

Dagang a intrebat.
a intrebat.
cat a.txt | xargs -I % echo %

În exemplul de mai sus, xargs ia echo % ca argument de comandă. Dar, în unele cazuri, am nevoie de mai multe comenzi pentru a procesa argumentul în loc de una singură. De exemplu:

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

Dar xargs nu acceptă această formă. O soluție pe care o știu este că pot defini o funcție care să înfășoare comenzile, dar vreau să evit acest lucru deoarece este complex. Există o soluție mai bună?

Comentarii

  • Cele mai multe dintre aceste răspunsuri sunt vulnerabilități de securitate. Vedeți aici pentru un răspuns potențial bun. –  > Por Mateen Ulhaq.
  • Folosesc xargs pentru aproape orice, dar urăsc să pun comenzi în interiorul șirurilor de caractere și să creez în mod explicit subshell-uri. Sunt pe punctul de a învăța cum să introduc pipe-uri într-un while buclă care poate conține mai multe comenzi. –  > Por Sridhar Sarnobat.
  • Testați soluțiile pe intrări de genul: ", *, a two spaces b, $(echo Do not print this). Dacă acestea nu funcționează conform așteptărilor, este posibil să existe și alte erori în soluție. –  > Por Ole Tange.
10 răspunsuri
Keith Thompson
cat a.txt | xargs -d $'
' sh -c 'for arg do command1 "$arg"; command2 "$arg"; ...; done' _

…sau, fără un Utilizare inutilă a pisicii:

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

Pentru a explica unele dintre cele mai fine puncte:

  • Utilizarea de "$arg" în loc de % (și absența lui -I în xargs din linia de comandă) este din motive de securitate: Transmiterea de date pe sh‘s pe lista de argumente din linia de comandă în loc să le înlocuiască în cod previne conținutul pe care datele l-ar putea conține (cum ar fi $(rm -rf ~), pentru a lua un exemplu deosebit de malițios) să nu fie executat ca și cod.

  • În mod similar, utilizarea lui -d $'
    '
    este o extensie GNU care face ca xargs să trateze fiecare linie a fișierului de intrare ca pe un element de date separat. Fie acest lucru, fie -0 (care așteaptă NUL-uri în loc de linii noi) este necesară pentru a preveni ca xargs să încerce să aplice un sistem de tip shell (dar nu chiar compatibil cu shell-urile) la fluxul pe care îl citește. (Dacă nu aveți GNU xargs, puteți folosi tr '
    ' '' <a.txt | xargs -0 ...
    pentru a obține o citire orientată pe linii fără -d).

  • Adresa _ este un simbol pentru $0, astfel încât alte valori de date adăugate de xargs devin $1 și mai departe, care se întâmplă să fie setul implicit de valori a for pe care o itera bucla.

Comentarii

    60

  • Pentru cei care nu sunt familiarizați cu sh -c — rețineți că punctul și virgula de după fiecare comandă nu este opțională, chiar dacă este ultima comandă din listă. –  > Por Noah Sussman.
  • Cel puțin pe configurația mea, trebuie să existe un spațiu imediat după „{” inițial. Nu este necesar niciun spațiu înaintea acoladei finale, dar, așa cum a observat domnul Sussman, aveți nevoie de un punct și virgulă de închidere. –  > Por willdye.
  • Acest răspuns avea anterior paranteze curbe în jurul command1 și command2; ulterior mi-am dat seama că nu sunt necesare. –  > Por Keith Thompson.
  • 25

  • Pentru a clarifica comentariile de mai sus despre punct și virgulă, este necesar un punct și virgulă înainte de o încheiere }: sh -c '{ command1; command2; }' -- but it's not required at the end of a command sequence that doesn't use braces: sh -c ‘comanda1; comanda2’` -.  > Por Keith Thompson.
  • În cazul în care includeți % undeva în șirul de caractere transmis către sh -c, atunci acest lucru este predispus la vulnerabilități de securitate: Un nume de fișier care conține $(rm -rf ~)'$(rm -rf ~)' (și acesta este un subșir de caractere perfect legal într-un nume de fișier pe sistemele de fișiere UNIX obișnuite!) va provoca un atac de tip foarte o zi foarte proastă. –  > Por Charles Duffy.
Ole Tange

Cu GNU Parallel puteți face:

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

Urmăriți videoclipurile introductive pentru a afla mai multe: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Din motive de securitate, se recomandă să folosiți managerul de pachete pentru a instala. Dar dacă nu puteți face acest lucru, puteți folosi această instalare în 10 secunde.

Instalarea în 10 secunde va încerca să facă o instalare completă; dacă nu reușește, o instalare personală; dacă nu reușește, o instalare minimă.

$ (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 67bd7bc7dc20aff99eb8f1266574dadb
12345678 67bd7bc7 dc20aff9 9eb8f126 6574dadb
$ md5sum install.sh | grep b7a15cdbb07fb6e11b0338577bc1780f
b7a15cdb b07fb6e1 1b033857 7bc1780f
$ sha512sum install.sh | grep 186000b62b66969d7506ca4f885e0c80e02a22444
6f25960b d4b90cf6 ba5b76de c1acdf39 f3d24249 72930394 a4164351 93a7668d
21ff9839 6f920be5 186000b6 2b66969d 7506ca4f 885e0c80 e02a2244 40e8a43f
$ bash install.sh

Comentarii

    59

  • Instalarea instrumentelor prin rularea unor scripturi aleatorii de pe site-uri necunoscute este o practică oribilă. Parallel are pachete oficiale pentru distribuțiile populare, în care se poate avea încredere (într-o oarecare măsură) mult mai mult decât în wget|sh… –  > Por mdrozdziel.
  • Să vedem care este cel mai simplu vector de atac: Pi.dk este controlat de autorul GNU Parallel, așa că pentru a ataca acest site ar trebui să pătrundeți în server sau să preluați DNS. Pentru a prelua pachetul oficial al unei distribuții, de multe ori puteți doar să vă oferiți voluntar pentru a menține pachetul. Așadar, deși s-ar putea să aveți dreptate în general, se pare că în acest caz particular comentariul dvs. nu este justificat. –  > Por Ole Tange.
  • În practică, nu știu dacă pi.dk aparține autorului. De fapt, verificarea acestui lucru, gândindu-mă cum să folosesc ssl în wget și verificând dacă această comandă face ceea ce trebuie să facă este un pic de muncă. Observația dumneavoastră că pachetul oficial poate conține cod malițios este adevărată, dar acest lucru este valabil și pentru pachetul wget. –  > Por Fabian.
  • S-ar putea ca aceasta să nu fie cea mai bună soluție dacă fiecare dintre comenzile pe care OP dorește să le execute trebuie să fie secvențială, corect? –  > Por IcarianComplex.
  • @IcarianComplex Adăugarea lui -j1 va rezolva această problemă. –  > Por Ole Tange.
Ossama

Puteți folosi

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

{} = variabilă pentru fiecare linie din fișierul text

Comentarii

  • Acest lucru este nesigur. Ce se întâmplă dacă file.txt conține o dată cu $(rm -rf ~) ca subșir? –  > Por Charles Duffy.
  • Acest lucru a funcționat bine pentru mine, din fericire niciuna dintre definițiile de fus orar din zoneinfo nu conține rm -rf 😉 –  > Por Kyle K.
hmontoliu

Aceasta este doar o altă abordare fără xargs și nici cat:

while read stuff; do
  command1 "$stuff"
  command2 "$stuff"
  ...
done < a.txt

Comentarii

  • Buggy, așa cum a fost dat. Cu excepția cazului în care nu ștergeți IFS, va ignora spațiile albe de început și de sfârșit în numele fișierelor; dacă nu adăugați -r, numele de fișiere cu backslash-uri literale vor avea aceste caractere ignorate. –  > Por Charles Duffy.
  • Nu răspunde la întrebare. Întrebarea se referea în mod specific la xargs. (Aceasta este greu de extins pentru a face ceva similar cu GNU xargs-P<n> ) – –  > Por Gert van den Berg.
  • Acest lucru funcționează perfect bine. De asemenea, puteți să o utilizați ca o comandă piped, cum ar fi $ command | while read line; do c1 $line; c2 $line; done –  > Por Alexar.
brablc

Eu prefer stilul care permite modul de funcționare uscată (fără | sh) :

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

Funcționează și cu țevi:

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

Comentarii

  • Acest lucru funcționează, până când doriți să folosiți GNU xargs’ -P (dacă nu, eu folosesc mai ales opțiunea -exec pe find, deoarece intrările mele sunt în mare parte nume de fișiere) –  > Por Gert van den Berg.
  • Nu reușește la intrare: ” –  > Por Ole Tange.
mwm

Un lucru pe care îl fac este să adaug în .bashrc/.profile această funcție:

function each() {
    while read line; do
        for f in "[email protected]"; do
            $f $line
        done
    done
}

apoi puteți face lucruri precum

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

care este mai puțin verbos decât xargs sau -exec. De asemenea, ați putea modifica funcția pentru a insera valoarea de la citire într-o locație arbitrară în comenzile către fiecare, dacă aveți nevoie și de acest comportament.

Comentarii

  • Răspuns subestimat, acest lucru este extrem de util –  > Por charlesreid1.
  • Nu funcționează corect dacă intrarea are două spații la rând sau *. –  > Por Ole Tange.
Gert van den Berg

Aceasta pare a fi cea mai sigură versiune.

tr '[
]' '[]' < a.txt | xargs -r0 /bin/bash -c 'command1 "[email protected]"; command2 "[email protected]";' ''

(-0 poate fi eliminat și se poate adăuga tr poate fi înlocuit cu o redirecționare (sau fișierul poate fi înlocuit cu un fișier separat de nul). Este în principal acolo, deoarece folosesc în principal xargs cu find cu -print0 output) (Acest lucru ar putea fi relevant și pe xargs versiunile fără -0 extensie)

Este sigur, deoarece args va transmite parametrii către shell sub formă de matrice în momentul execuției. Shell-ul (cel puțin bash) îi va transmite apoi ca un array nealterat celorlalte procese atunci când toate sunt obținute folosind ["[email protected]"][1]

Dacă utilizați ...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";' '', atribuirea va eșua dacă șirul conține ghilimele duble. Acest lucru este valabil pentru fiecare variantă care utilizează -i sau -I. (Datorită faptului că este înlocuit într-un șir de caractere, puteți oricând să injectați comenzi prin inserarea unor caractere neașteptate (cum ar fi ghilimele, ghilimele sau semne de dolar) în datele de intrare)

Dacă comenzile nu pot primi decât un singur parametru la un moment dat:

tr '[
]' '[]' < a.txt | xargs -r0 -n1 /bin/bash -c 'command1 "[email protected]"; command2 "[email protected]";' ''

Sau cu procese ceva mai puține:

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

Dacă aveți GNU xargs sau altul cu -P și doriți să executați 32 de procese în paralel, fiecare cu cel mult 10 parametri pentru fiecare comandă:

tr '[
]' '[]' < a.txt | xargs -r0 -n10 -P32 /bin/bash -c 'command1 "[email protected]"; command2 "[email protected]";' ''

Acest lucru ar trebui să fie rezistent la orice caractere speciale din datele de intrare. (În cazul în care datele de intrare sunt separate de nul). tr va primi unele intrări invalide dacă unele dintre linii conțin linii noi, dar acest lucru este inevitabil în cazul unui fișier separat de linii noi.

Primul parametru gol pentru bash -c se datorează acestui lucru: (Din bash pagina de manual) (Mulțumesc @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.

Comentarii

  • Acest lucru ar trebui să funcționeze chiar și cu ghilimele duble în numele de fișier. Aceasta necesită un shell care să suporte în mod corespunzător "[email protected]" –  > Por Gert van den Berg.
  • Îți lipsește argumentul argv[0] pentru bash. bash -c 'command1 "[email protected]"; command2 "[email protected]";' arbitrarytextgoeshere –  > Por clacke.
  • Nu este vorba despre ceea ce face xargs. bash cu -c ia mai întâi (după comenzi) un argument care va fi numele procesului, apoi ia argumentele poziționale. Încercați bash -c 'echo "[email protected]" ' 1 2 3 4 și vedeți ce iese. –  > Por clacke.
  • Este bine să avem o versiune sigură care nu devine Bobby-Tabled. –  > Por Mateen Ulhaq.
tavvit

O altă soluție posibilă care funcționează pentru mine este ceva de genul –

cat a.txt | xargs bash -c 'command1 [email protected]; command2 [email protected]' bash

Observați „bash” de la sfârșit – presupun că este trecut ca argv[0] către bash. Fără el în această sintaxă, primul parametru la fiecare comandă este pierdut. Acesta poate fi orice cuvânt.

Exemplu:

cat a.txt | xargs -n 5 bash -c 'echo -n `date +%Y%m%d-%H%M%S:` ; echo " data: " [email protected]; echo "data again: " [email protected]' bash

Comentarii

  • Dacă nu citați "[email protected]", atunci separați șirul de caractere și extindeți lista de argumente. –  > Por Charles Duffy.
Krazy Glew

BKM-ul meu actual pentru acest lucru este

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

Este regretabil că acesta folosește perl, care este mai puțin probabil să fie instalat decât bash; dar gestionează mai multe intrări decât răspunsul acceptat. (Salut o versiune omniprezentă care să nu se bazeze pe perl).

Sugestia lui @KeithThompson de

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

este grozavă – cu excepția cazului în care aveți caracterul de comentariu de shell # în intrare, caz în care o parte din prima comandă și toată a doua comandă vor fi trunchiate.

Hașurile # pot fi destul de frecvente, dacă intrarea este derivată dintr-o listare a sistemului de fișiere, cum ar fi ls sau find, iar editorul dvs. creează fișiere temporare cu # în numele lor.

Exemplu de problemă:

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

Ups, iată care este problema:

$ 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, așa e mai bine:

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

Comentarii

  • # problema poate fi ușor de rezolvat folosind ghilimele: ls | xargs -I % sh -c 'echo 1 "%"; echo 2 "%"' –  > Por gpl.
Erik Berg

Încearcă asta:

git config --global alias.all '!f() { find . -d -name ".git" | sed s/\/.git//g | xargs -P10 -I{} git --git-dir={}/.git --work-tree={} $1; }; f'

Rulează zece fire de execuție în paralel și execută orice comandă git pe care o dorești pentru toate depozitele din structura de dosare. Nu contează dacă repo-ul este unul sau n niveluri de adâncime.

De ex: git all pull

Comentarii

  • Exemplul tău este foarte util, dar este suficient de complicat încât o explicație ar fi de ajutor. Se pare că nu răspunde la întrebarea despre cum să executați mai multe comenzi cu xargs. Exemplul dvs. face următoarele lucruri git --git-dir=A1/.git --work-tree=A1 pull în exemplul dvs. unde A1 este unul dintre depozitele pe care le găsește. Întrebarea era cum să faci ceva de genul ls -al {}; rm -f {} (adică două comenzi pentru fiecare linie dată la xargs, nu una). –  > Por Steven cel ușor de amuzat.
  • Comentariul meu pare să fie mult deplasat. Nu răspunde deloc la întrebarea inițială. Eram sigur că am postat asta la o altă întrebare 😀 Ceea ce face comentariul meu este să pregătească un alias git. Aliasul permite rularea de comenzi git pe toate depozitele aflate în subdirectoare. @SteventheEasilyAmused, crezi că ar trebui să elimin comentariul? –  > Por Erik Berg.

Tags:,