Xargs ile birden fazla komut çalıştırma


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

Yukarıdaki örnekte, xargs echo %komut bağımsız değişkeni olarak alınır. Ancak bazı durumlarda, argümanı bir yerine işlemek için birden fazla komuta ihtiyacım var. Örneğin:

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

Ancak xargs bu formu kabul etmiyor. Bildiğim bir çözüm, komutları sarmak için bir işlev tanımlayabildiğim, ancak bir boru hattı değil, tercih etmiyorum. Başka bir çözüm var mı?


1
Bu yanıtların çoğu güvenlik açıklarıdır . Potansiyel olarak iyi bir cevap için buraya bakınız.
Mateen Ulhaq

2
Hemen hemen her şey için xargs kullanıyorum, ancak dizelerin içine komutlar koymaktan ve açıkça alt kabuklar oluşturmaktan nefret ediyorum. whileBirden fazla komut içeren bir döngüye nasıl bağlanacağını öğrenme eşiğinde bulunuyorum .
Sridhar Sarnobat

Yanıtlar:


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

... veya Yararsız Bir Kedi Kullanımı olmadan :

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

Daha ince noktalardan bazılarını açıklamak için:

  • Kullanılması "$arg"yerine %(ve yokluğu -Iiçinde xargskomut satırı), güvenlik sebeplerinden dolayı: veri Geçmesh bireyin komut satırı bağımsız değişken listesi veri yerine örneğin (içerebilir kodlayan önler içerik haline ikame $(rm -rf ~), özellikle almak kötü amaçlı örnek).

  • Benzer şekilde, kullanımı , girdi dosyasının her satırının ayrı bir veri öğesi olarak ele alınmasına -d $'\n'neden olan bir GNU oluşumudur xargs. -0Xargs'ın okuduğu akışa kabuk benzeri (ancak oldukça kabuk uyumlu değil) ayrıştırmalarını uygulamalarını önlemek için bu ya da (satırsonu yerine NUL'lar bekler) gereklidir . (GNU xargs'ınız yoksa, tr '\n' '\0' <a.txt | xargs -0 ...hat yönelimli okuma almak için-d ).

  • _Bir yer tutucudur için $0, öyle ki tarafından eklenen diğer veri değerleri xargshaline $1bir değerler varsayılan kümesi olur ki, ileriye ve fordöngü üzerinde dolaşır.


58
Bilmeyenler için sh -c- listedeki son komut olsa bile, her komuttan sonra noktalı virgül isteğe bağlı değildir.
Noah Sussman

6
En azından benim yapılandırmamda, ilk "{" karakterinden hemen sonra bir boşluk olmalı. Kıvırcık ayracı sonlandırmadan önce yer gerekmez, ancak Bay Sussman'ın belirttiği gibi, kapanış noktalı virgülüne ihtiyacınız vardır.
willdye

4
Bu cevap daha önce etrafında küme parantezi vardı command1ve command2; Daha sonra gerekli olmadıklarını fark ettim.
Keith Thompson

24
Noktalı virgüllerle ilgili yukarıdaki yorumları açıklığa kavuşturmak için, kapanmadan önce noktalı virgül gerekir }: 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
Eğer dahil ediyorsanız %geçirilen dizenizle karakter bir yere sh -cbir dosya adı içeren:, o zaman bu güvenlik açıkları eğilimli $(rm -rf ~)'$(rm -rf ~)'(ve bu ortak UNIX dosya sistemlerinde bir dosya içinde olması gayet yasal alt dize var!) Bir kimse neden olacaktır çok kötü bir gün .
Charles Duffy

35

GNU Parallel ile şunları yapabilirsiniz:

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

Daha fazla bilgi edinmek için giriş videolarını izleyin: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Güvenlik nedeniyle kurmak için paket yöneticinizi kullanmanız önerilir. Ancak bunu yapamazsanız, bu 10 saniyelik yüklemeyi kullanabilirsiniz.

10 saniyelik kurulum tam kurulum yapmaya çalışır; bu başarısız olursa, kişisel bir kurulum; bu başarısız olursa, minimal bir kurulum.

$ (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
Bilinmeyen sitelerden rastgele komut dosyaları çalıştırarak araç yüklemek korkunç bir uygulamadır. Paralel, popüler dağıtımlar için rasgele wget |
sh'den

4
En kolay saldırı vektörünün ne olduğunu görelim: Pi.dk, GNU Parallel'ın yazarı tarafından kontrol edilir, böylece saldırmak için sunucuya girmeniz veya DNS'yi ele geçirmeniz gerekir. Bir dağıtımın resmi paketini devralmak için genellikle paketi korumak için gönüllü olabilirsiniz. Bu nedenle, genel olarak haklı olmanıza rağmen, bu özel durumda yorumunuz haklı görünmüyor.
Ole Tange

10
Uygulamada pi.dk'in yazara ait olduğunu bilmiyorum. Aslında durumun bu olduğunu doğrulamak, ssl'nin wget içinde nasıl kullanılacağını düşünmek ve bu komutun yapması gereken şeyi yaptığını kontrol etmek biraz iştir. Resmi paketin kötü amaçlı kod içerebileceği noktanız doğrudur, ancak wget paketi için de geçerlidir.
Fabian

3
OP'nin yürütmek istediği komutların her birinin sıralı olması gerekiyorsa, bu en iyi çözüm olmayabilir, değil mi?
IcarianComplex

2
@IcarianComplex -j1 eklenmesi bunu düzeltir.
Ole Tange

26

Bu sadece xargs veya cat olmadan başka bir yaklaşımdır:

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

2
Buggy, verildiği gibi. Siz temizlemezseniz IFS, dosya adlarında önde gelen ve arkadaki boşlukları yok sayar; eklemezseniz -r, gerçek ters eğik çizgiler içeren dosya adlarında bu karakterler yoksayılır.
Charles Duffy

Soruya cevap vermiyor. Özellikle hakkında sordu xargs. (Bu, GNU xargs' -P<n>seçeneğine benzer bir şey yapmak için genişletilmesi zordur )
Gert van den Berg

1
Bu mükemmel çalışıyor. Ayrıca boru gibi bir komut olarak da kullanabilirsiniz$ command | while read line; do c1 $line; c2 $line; done
Alexar

25

Kullanabilirsiniz

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

{} = metin dosyasındaki her satır için değişken


8
Bu güvensiz. Alt dize olarak bir file.txtdatum içeriyorsa ne olur $(rm -rf ~)?
Charles Duffy

19

Yaptığım bir şey .bashrc / .profile bu işlevi eklemek için:

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

o zaman böyle şeyler yapabilirsin

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

xargs veya -exec'ten daha az ayrıntılıdır. Ayrıca, bu davranışa da gereksinim duyduysanız, değeri her biri için komutlarda isteğe bağlı bir konumda okuma değerine ekleme işlevini değiştirebilirsiniz.


1
Yetersiz cevap, bu son derece kullanışlı
charlesreid1

15

Kuru çalışma moduna izin veren stili tercih ederim (olmadan | sh):

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

Borularla da çalışır:

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

2
Eğer GNU Xargs' kullanmak istediğiniz kadar bu eserler -Pseçeneği ... (Değilse, çoğunlukla kullanmak -execüzerinde findbenim girişler çoğunlukla dosya olduğundan,)
Gert van Berg den

13

Partiye biraz geç.

Taşımadan önce dizinlerimi binlerce küçük dosyayla sıkıştırmak için aşağıdaki biçimi kullanıyorum. Komutların içinde tek tırnak işaretine ihtiyacınız yoksa işe yarayacaktır.

Bazı değişikliklerle, eminim birisi için yararlı olacaktır. Test edildi Cygwin(babun)

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

find .Burada bulun Alt
-maxdepth 1dizinlere girmeyin
! -path .. / Geçerli dizin yolu
-type dyalnızca dizinlerle
-print0eşleşiyor Çıktıyı null baytla ayır \ 0
| xargsBorudan xargs'a
-0Girdi boş değerle ayrılmış bayt
-I @@Yer tutucu @@. @@ öğesini giriş ile değiştirin.
bash -c '...'Bash komutunu çalıştır
{...}Komut gruplama
&& Bir sonraki komutu yalnızca önceki komut başarıyla çıkıldığında çalıştır (çıkış 0)

final ; önemlidir, aksi takdirde başarısız olur.

Çıktı:

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

2018 Temmuz Güncellemesi:

Kesmek ve oynamayı seviyorsanız, ilginç bir şey var:

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

Çıktı:

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

Açıklama:
- Tek bir astar komut dosyası oluşturma ve bir değişkende saklayın
- xargs okur a.txtve olduğu gibi yürütür bunu bashkomut
- @@ emin bütün bir çizgi geçirilen her zaman yapar
- koymak @@sonra --emin yapar @@için konumsal parametre girdi olarak alınır bashkomuta değil, bir bashbaşlangıcı OPTION, yani -ckendisi gibi demekrun command

--büyülü, o yani birçok şey ile çalışır sshhatta,kubectl


1
Bu tür bir şeyle bir kurulum kullandım: find . -type f -print0|xargs -r0 -n1 -P20 bash -c 'f="{}";ls -l "$f"; gzip -9 "$f"; ls -l "$f.gz"'(Döngüleri dönüştürürken biraz daha kolay)
Gert van den Berg

1
Önceki yorumum için geç düzenleme: (Dosya adları çift tırnak işareti içeriyorsa bir güvenlik sorunu vardır ...) (Bunu "$@"önlemenin tek yolu ... gibi görünüyor -n1(parametre sayısını sınırlamak istiyorsanız))
Gert van den Berg

--Kabuk tarafından daha fazla seçeneğin kabul edilmeyeceğini söylemek için kullanıldığı belirtilmelidir . Bu da bir -sonra olmasını sağlar --. Örneğin grep -rkalıba dahil ettiğinizde kullanmıyorsanız çok ilginç ve kafa karıştırıcı bir çıktı elde edebilirsiniz -! Bununla birlikte, sizin ifade etme şekliniz bunun açıklığa kavuşmadığını, bunun nasıl çalıştığını gerçekten açıklamaz. Iirc POSIX bir şey ama yine de bunu belirtmeye değer, sanırım. Dikkate alınması gereken bir şey. Ve bu bonus btw seviyorum!
Pryftan

10

Bu en güvenli sürüm gibi görünüyor.

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

( -0Kaldırılabilir ve tr) bir yönlendirme ile değiştirilmiştir (veya dosya null ile değiştirilebilir yerine dosyayı ayırdı. Ben ağırlıklı olarak kullandığından İçerisi ağırlıklı olduğu xargsile findbirlikte -print0çıkış) (Bu aynı zamanda alakalı olabilir xargsolmadan sürümleri-0 uzantısı)

Güvenlidir, çünkü args parametreleri yürütürken parametreleri bir kabuk olarak geçirir. Kabuk (en azındanbash ) daha sonra, tümü kullanılarak elde edildiğinde, bunları değiştirilmemiş bir dizi olarak diğer işlemlere geçirir.["$@"][1]

Eğer kullanırsanız ...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";' '', dizede çift tırnak varsa atama başarısız olur. Bu, -iveya kullanan her varyant için geçerlidir -I. (Bir dizeye değiştirildiği için, giriş verilerine beklenmedik karakterler (tırnak işaretleri, ters işaretler veya dolar işaretleri gibi) ekleyerek her zaman komut enjekte edebilirsiniz)

Komutlar bir kerede yalnızca bir parametre alabilirse:

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

Veya biraz daha az süreçle:

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

GNU xargsveya başka bir -Puzantıya sahipseniz ve her biri her komut için 10'dan fazla parametre içermeyen 32 işlemi paralel olarak çalıştırmak istiyorsanız:

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

Bu, girişteki özel karakterlere karşı sağlam olmalıdır. (Girdi boş bırakılmışsa.) trSatırlardan bazıları yeni satır içeriyorsa sürüm geçersiz bir giriş alır, ancak bu satır yeni satır ayrılmış bir dosyayla kaçınılmazdır.

İçin boş ilk parametrenin nedeni bash -cbudur: ( bashMan sayfasından ) (Thanks @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.

Bu, dosya adlarındaki çift tırnaklarla bile çalışmalıdır. Bu, düzgün bir şekilde destekleyen bir kabuk gerektirir"$@"
Gert van den Berg

Bash için argv [0] argümanını kaçırıyorsunuz. bash -c 'command1 "$@"; command2 "$@";' arbitrarytextgoeshere
clacke

3
Bu xargs'ın yaptığı şeyle ilgili değil. bashile -cönce (komutlardan sonra) sürecin adı olacak bir argüman alır, sonra konumsal argümanları alır. bash -c 'echo "$@" ' 1 2 3 4Neyin ortaya çıktığını görün ve deneyin .
clacke

Bobby-Tabled'a sahip olmayan güvenli bir versiyona sahip olmak güzel.
Mateen Ulhaq

8

Benim için çalışan bir başka olası çözüm şudur:

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

Sonunda 'bash' not - ben bash argv [0] olarak geçti sanırım. Bu sözdiziminde olmadan, her komutun ilk parametresi kaybolur. Herhangi bir kelime olabilir.

Misal:

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

5
Alıntı yapmazsanız "$@", argüman listesini dize olarak böler ve glob olarak genişletirsiniz.
Charles Duffy

2

Bunun için mevcut BKM'm

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

Bunun bash'den daha az kurulması muhtemel perl kullanması talihsiz bir durumdur; ancak kabul edilen cevaptan daha fazla girdi işler. (Perl'e dayanmayan her yerde bulunan bir sürümü hoş geldiniz.)

@ KeithThompson'ın önerisi

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

harika - girişinizde kabuk yorum karakteri # yoksa, bu durumda ilk komutun bir parçası ve tüm ikinci komut kesilir.

Girdi ls veya find gibi bir dosya sistemi listesinden türetilmişse ve düzenleyiciniz adında # olan geçici dosyalar oluşturursa Hashes # oldukça yaygın olabilir.

Sorun örneği:

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

Hata! İşte sorun:

$ 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, bu daha iyi:

$ 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
# sorun tırnak işaretleri ile kolayca çözülebilir:ls | xargs -I % sh -c 'echo 1 "%"; echo 2 "%"'
gpl
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.