Menjalankan banyak perintah dengan xargs


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

Pada contoh di atas, xargs digunakan echo %sebagai argumen perintah. Tetapi dalam beberapa kasus, saya perlu beberapa perintah untuk memproses argumen, bukan satu. Sebagai contoh:

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

Tetapi xargs tidak menerima formulir ini. Satu solusi yang saya tahu adalah saya bisa mendefinisikan fungsi untuk membungkus perintah, tapi itu bukan pipa, saya tidak suka itu. Apakah ada solusi lain?


1
Sebagian besar jawaban ini adalah kerentanan keamanan . Lihat di sini untuk jawaban yang berpotensi bagus.
Mateen Ulhaq

2
Saya menggunakan xargs untuk hampir semuanya, tapi saya benci menempatkan perintah di dalam string dan secara eksplisit membuat subshell. Saya sedang di ambang belajar bagaimana pipa ke whileloop yang dapat berisi banyak perintah.
Sridhar Sarnobat

Jawaban:


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

... atau, tanpa Penggunaan Kucing yang Tidak Berguna :

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

Untuk menjelaskan beberapa poin penting:

  • Penggunaan "$arg"bukannya %(dan tidak adanya -Idalam xargsbaris perintah) adalah untuk alasan keamanan: Melewatkan data pada shdaftar argumen baris perintah alih-alih menggantikannya ke dalam kode mencegah konten yang mungkin berisi data (seperti $(rm -rf ~), untuk mengambil khususnya malicious example) agar tidak dieksekusi sebagai kode.

  • Demikian pula, penggunaan -d $'\n'adalah ekstensi GNU yang menyebabkan xargsmemperlakukan setiap baris dari file input sebagai item data yang terpisah. Entah ini atau -0(yang mengharapkan NUL alih-alih baris baru) diperlukan untuk mencegah xargs mencoba menerapkan shell-like (tapi tidak kompatibel dengan shell) untuk menguraikan aliran yang dibacanya. (Jika Anda tidak memiliki GNU xargs, Anda dapat menggunakan tr '\n' '\0' <a.txt | xargs -0 ...untuk mendapatkan bacaan berorientasi baris tanpa -d)

  • The _adalah pengganti untuk $0, sehingga nilai-nilai data lainnya ditambah xargsmenjadi $1dan seterusnya, yang terjadi menjadi default set nilai-nilai forlingkaran iterates atas.


58
Bagi mereka yang tidak terbiasa dengan sh -c- perhatikan bahwa tanda koma setelah setiap perintah tidak opsional, bahkan jika itu adalah perintah terakhir dalam daftar.
Noah Sussman

6
Setidaknya pada konfigurasi saya, harus ada spasi segera setelah awal "{". Tidak ada ruang yang dibutuhkan sebelum kurung kurawal berakhir, tetapi seperti yang dicatat oleh Mr. Sussman, Anda memang perlu titik koma penutup.
willdye

4
Jawaban ini sebelumnya memiliki kurung kurawal di sekitar command1dan command2; Saya kemudian menyadari bahwa itu tidak perlu.
Keith Thompson

24
Untuk mengklarifikasi komentar di atas tentang titik koma, titik koma diperlukan sebelum penutupan }: sh -c '{ command1; command2; }' -- but it's not required at the end of a command sequence that doesn't use braces: sh -c 'command1; command2'`
Keith Thompson

8
Jika Anda memasukkan %karakter di suatu tempat di string yang diteruskan ke Anda sh -c, maka ini rentan terhadap kerentanan keamanan: Sebuah nama file yang mengandung $(rm -rf ~)'$(rm -rf ~)'(dan itu adalah substring yang sah untuk dimiliki dalam nama file pada sistem file UNIX yang umum!) Akan menyebabkan seseorang pada hari yang sangat buruk .
Charles Duffy

35

Dengan GNU Parallel Anda dapat melakukan:

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

Tonton video intro untuk mempelajari lebih lanjut: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Untuk alasan keamanan disarankan Anda menggunakan manajer paket Anda untuk menginstal. Tetapi jika Anda tidak dapat melakukannya maka Anda dapat menggunakan instalasi 10 detik ini.

Instalasi 10 detik akan mencoba melakukan instalasi penuh; jika itu gagal, instalasi pribadi; jika itu gagal, instalasi minimal.

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

56
Menginstal alat melalui menjalankan skrip acak dari situs yang tidak dikenal adalah praktik yang mengerikan. Paralel memiliki paket-paket resmi untuk distro-distro populer, yang dapat dipercaya (sampai batas tertentu) lebih dari sekadar wget acak | sh ...
mdrozdziel

4
Mari kita lihat apa vektor serangan termudah: Pi.dk dikendalikan oleh penulis GNU Parallel, jadi untuk menyerang Anda harus masuk ke server atau mengambil alih DNS. Untuk mengambil alih paket distribusi resmi, Anda seringkali dapat secara sukarela memelihara paket tersebut. Jadi sementara Anda mungkin benar secara umum, tampaknya dalam kasus khusus ini komentar Anda tidak dibenarkan.
Ole Tange

10
Dalam praktiknya saya tidak tahu bahwa pi.dk milik penulis. Sebenarnya memverifikasi bahwa ini adalah kasusnya, memikirkan cara menggunakan ssl di wget dan memeriksa bahwa perintah ini melakukan apa yang seharusnya dilakukan adalah sedikit pekerjaan. Maksud Anda bahwa paket resmi dapat berisi kode berbahaya itu benar, tetapi itu juga berlaku untuk paket wget.
Fabian

3
Ini mungkin bukan solusi terbaik jika setiap perintah yang ingin dieksekusi OP harus berurutan, benar?
IcarianComplex

2
@IcarianComplex Menambahkan -j1 akan memperbaikinya.
Ole Tange

26

Ini hanyalah pendekatan lain tanpa xargs atau kucing:

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

2
Buggy, seperti yang diberikan. Kecuali Anda menghapus IFS, itu akan mengabaikan spasi putih memimpin dan tertinggal di nama file; kecuali Anda menambahkan -r, nama file dengan garis miring terbalik literal akan mengabaikan karakter tersebut.
Charles Duffy

Tidak menjawab pertanyaan. Ini secara khusus bertanya tentang xargs. (Ini sulit diperluas untuk melakukan sesuatu yang mirip dengan opsi GNU xargs' -P<n>)
Gert van den Berg

1
Ini bekerja dengan sangat baik. Anda juga dapat menggunakannya sebagai perintah pipa seperti$ command | while read line; do c1 $line; c2 $line; done
Alexar

25

Kamu bisa memakai

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

{} = variabel untuk setiap baris pada file teks


8
Ini tidak aman. Bagaimana jika Anda file.txtberisi datum dengan $(rm -rf ~)sebagai substring?
Charles Duffy

19

Satu hal yang saya lakukan adalah menambahkan .bashrc / .profile fungsi ini:

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

maka Anda dapat melakukan hal-hal seperti

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

yang kurang verbose dari xargs atau -exec. Anda juga dapat memodifikasi fungsi untuk menyisipkan nilai dari pembacaan di lokasi arbitrer pada perintah untuk masing-masing, jika Anda memerlukan perilaku itu juga.


1
Jawaban yang diremehkan, ini sangat berguna
charlesreid1

15

Saya lebih suka gaya yang memungkinkan mode lari kering (tanpa | sh):

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

Bekerja dengan pipa juga:

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

2
Karya ini, sampai Anda ingin menggunakan GNU xargs' -Poption ... (Jika tidak, aku lebih sering menggunakan -execpada find, karena masukan saya sebagian besar nama file)
Gert van den Berg

13

Sedikit terlambat ke pesta.

Saya menggunakan format di bawah ini untuk mengompresi direktori saya dengan ribuan file kecil sebelum bermigrasi. Jika Anda tidak membutuhkan tanda kutip tunggal di dalam perintah, itu harus berfungsi.

Dengan beberapa modifikasi, saya yakin itu akan bermanfaat bagi seseorang. Diuji dalam Cygwin(babun)

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

find .Temukan di sini
-maxdepth 1Jangan masuk ke direktori anak-anak
! -path .Kecualikan. / Jalur direktori saat ini
-type dhanya cocok dengan direktori
-print0Pisahkan output dengan byte nol \ 0
| xargsPipa ke xargs
-0Input null dipisahkan byte
-I @@Placeholder adalah @@. Ganti @@ dengan input.
bash -c '...'Jalankan perintah Bash,
{...}Pengelompokan perintah
&& Jalankan perintah berikutnya hanya jika perintah sebelumnya berhasil keluar (keluar 0)

Terakhir ; itu penting, kalau tidak maka akan gagal.

Keluaran:

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

Pembaruan 2018 Juli:

Jika Anda suka hack dan bermain-main, berikut ini sesuatu yang menarik:

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" -- @@

Keluaran:

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

Penjelasan:
- Membuat skrip satu liner dan menyimpannya dalam variabel
- xargs membaca a.txtdan mengeksekusinya sebagai bashskrip
- @@ memastikan setiap kali seluruh baris dilewati
- Menempatkan @@setelah --memastikan @@diambil sebagai input parameter posisi ke bashperintah, bukan bashmemulai OPTION, yaitu seperti -citu sendiri artinyarun command

--ajaib, ia bekerja dengan banyak hal lain, yaitu ssh, bahkankubectl


1
Saya telah menggunakan pengaturan dengan jenis hal ini: find . -type f -print0|xargs -r0 -n1 -P20 bash -c 'f="{}";ls -l "$f"; gzip -9 "$f"; ls -l "$f.gz"'(Ini sedikit lebih mudah ketika mengkonversi loop)
Gert van den Berg

1
Edit terlambat untuk komentar saya sebelumnya: (Jika nama file berisi tanda kutip ganda ada masalah keamanan ...) (Sepertinya menggunakan "$@"adalah satu-satunya cara untuk menghindari itu ... (dengan -n1jika Anda ingin membatasi jumlah parameter))
Gert van den Berg

Perlu dicatat bahwa --digunakan oleh shell untuk mengatakan bahwa tidak ada lagi opsi yang dapat diterima. Ini memungkinkan ada -after after --juga. Anda bisa mendapatkan hasil yang sangat menarik dan membingungkan jika Anda tidak melakukannya misalnya grep -rketika Anda memasukkan dalam pola -! Cara Anda mengucapkannya tidak menjelaskan ini tidak benar-benar menjelaskan cara kerjanya. Iirc itu adalah hal POSIX tetapi bagaimanapun juga itu layak menunjukkan ini, saya pikir. Hanya sesuatu yang perlu dipertimbangkan. Dan saya suka sekali bonus itu!
Pryftan

10

Ini sepertinya versi yang paling aman.

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

( -0dapat dihapus dan trdiganti dengan redirect (atau file dapat diganti dengan file yang dipisahkan nol sebagai gantinya). Hal ini terutama di sana karena saya terutama menggunakan xargsdengan finddengan -print0output) (Ini mungkin juga relevan pada xargsversi tanpa -0ekstensi)

Aman, karena args akan meneruskan parameter ke shell sebagai larik ketika mengeksekusinya. Shell (setidaknya bash) akan meneruskannya sebagai array yang tidak diubah ke proses lain ketika semua diperoleh dengan menggunakan["$@"][1]

Jika Anda menggunakan ...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";' '', tugas akan gagal jika string berisi tanda kutip ganda. Ini berlaku untuk setiap varian yang menggunakan -iatau-I . (Karena itu diganti menjadi string, Anda selalu dapat menyuntikkan perintah dengan memasukkan karakter yang tidak terduga (seperti tanda kutip, tanda kutip atau tanda dolar) ke dalam data input)

Jika perintah hanya dapat mengambil satu parameter sekaligus:

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

Atau dengan proses yang agak kurang:

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

Jika Anda memiliki GNU xargsatau yang lain dengan -Pekstensi dan Anda ingin menjalankan 32 proses secara paralel, masing-masing dengan tidak lebih dari 10 parameter untuk setiap perintah:

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

Ini harus kuat terhadap karakter khusus apa pun dalam input. (Jika inputnya dipisahkan nol). trVersi ini akan mendapatkan beberapa input yang tidak valid jika beberapa baris berisi baris baru, tetapi itu tidak dapat dihindari dengan file yang dipisahkan baris baru.

Parameter kosong pertama bash -cadalah karena ini: (Dari bashhalaman manual ) (Terima kasih @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.

Ini harus bekerja bahkan dengan tanda kutip ganda dalam nama file. Itu membutuhkan shell yang mendukung dengan benar"$@"
Gert van den Berg

Anda melewatkan argumen argv [0] untuk bash. bash -c 'command1 "$@"; command2 "$@";' arbitrarytextgoeshere
clacke

3
Ini bukan tentang apa yang dilakukan xarg. bashDengan -cmengambil pertama (setelah perintah) satu argumen yang akan menjadi nama proses, maka dibutuhkan argumen posisi. Coba bash -c 'echo "$@" ' 1 2 3 4dan lihat apa yang keluar.
clacke

Sangat menyenangkan memiliki versi aman yang tidak bisa di-Bobled-Tabled.
Mateen Ulhaq

8

Solusi lain yang mungkin bekerja bagi saya adalah sesuatu seperti -

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

Catat 'bash' di akhir - saya berasumsi ini dilewatkan sebagai argv [0] ke bash. Tanpa itu dalam sintaks ini parameter pertama untuk setiap perintah hilang. Mungkin kata apa saja.

Contoh:

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

5
Jika Anda tidak mengutip "$@", maka Anda membelah string dan memperluas daftar argumen.
Charles Duffy

2

BKM saya saat ini untuk ini

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

Sangat disayangkan bahwa ini menggunakan perl, yang lebih kecil kemungkinannya untuk diinstal daripada bash; tetapi menangani lebih banyak input dari jawaban yang diterima. (Saya menyambut versi mana-mana yang tidak bergantung pada perl.)

@ Saran KeithThompson tentang

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

hebat - kecuali jika Anda memiliki karakter komentar shell # di input Anda, yang mana bagian dari perintah pertama dan semua perintah kedua akan terpotong.

Hash # bisa sangat umum, jika input berasal dari daftar filesystem, seperti ls atau find, dan editor Anda membuat file sementara dengan # di namanya.

Contoh masalah:

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

Ups, ini masalahnya:

$ 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, itu lebih baik:

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

5
# masalah dapat diselesaikan dengan mudah menggunakan tanda kutip:ls | xargs -I % sh -c 'echo 1 "%"; echo 2 "%"'
gpl
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.