การรันหลายคำสั่งด้วย xargs


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

ในตัวอย่างข้างต้น xargs ใช้echo %เป็นอาร์กิวเมนต์คำสั่ง แต่ในบางกรณีฉันต้องการคำสั่งหลายคำสั่งเพื่อดำเนินการโต้แย้งแทนคำสั่งเดียว ตัวอย่างเช่น:

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

แต่ xargs ไม่ยอมรับแบบฟอร์มนี้ ทางออกหนึ่งที่ฉันรู้คือฉันสามารถกำหนดฟังก์ชั่นเพื่อห่อคำสั่งได้ แต่มันไม่ใช่ไพพ์ไลน์ฉันไม่ชอบมัน มีวิธีแก้ไขปัญหาอื่นอีกหรือไม่


1
ส่วนใหญ่ของคำตอบเหล่านี้เป็นช่องโหว่ความปลอดภัย ดูคำตอบที่ดีที่นี่
Mateen Ulhaq

2
ฉันใช้ xargs เกือบทุกอย่าง แต่ฉันเกลียดการใส่คำสั่งภายในสตริงและสร้าง subshells อย่างชัดเจน ฉันกำลังจะได้เรียนรู้วิธีที่จะสอดเข้าไปในwhileลูปที่มีหลายคำสั่ง
Sridhar Sarnobat

คำตอบ:


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

... หรือโดยไม่ต้องใช้แมวอย่างไร้ประโยชน์ :

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

ในการอธิบายคะแนนปลีกย่อยบางส่วน:

  • การใช้"$arg"แทน%(และไม่มี-Iในxargsบรรทัดคำสั่ง) เพื่อเหตุผลด้านความปลอดภัย: การส่งผ่านข้อมูลในshรายการอาร์กิวเมนต์บรรทัดคำสั่งของแทนการแทนที่มันลงในรหัสป้องกันเนื้อหาที่อาจมีข้อมูล (เช่น$(rm -rf ~)เพื่อใช้โดยเฉพาะอย่างยิ่ง ตัวอย่างที่เป็นอันตราย) จากการถูกเรียกใช้งานเป็นรหัส

  • ในทำนองเดียวกันการใช้-d $'\n'เป็นส่วนขยายของ GNU ซึ่งทำให้xargsถือว่าแต่ละบรรทัดของไฟล์อินพุตเป็นรายการข้อมูลแยกต่างหาก อาจเป็นสิ่งนี้หรือ-0(ซึ่งคาดว่าจะเป็น NUL แทนการขึ้นบรรทัดใหม่) มีความจำเป็นเพื่อป้องกันไม่ให้ xargs พยายามใช้การแยกเชลล์เหมือน (แต่ไม่เข้ากันได้กับเชลล์) กับสตรีมที่อ่าน (หากคุณไม่มี GNU xargs คุณสามารถใช้tr '\n' '\0' <a.txt | xargs -0 ...เพื่อรับการอ่านบรรทัดโดยไม่ต้อง-d)

  • The _เป็นตัวยึดสำหรับ$0เช่นค่าข้อมูลอื่น ๆ ที่เพิ่มขึ้นโดยการxargsเป็น$1และต่อไปซึ่งเกิดขึ้นเป็นชุดของค่าเริ่มต้นที่forวนซ้ำซ้ำ


58
สำหรับผู้ที่ไม่คุ้นเคยsh -c- โปรดทราบว่าเครื่องหมายอัฒภาคหลังจากแต่ละคำสั่งไม่ใช่ทางเลือกแม้ว่าจะเป็นคำสั่งสุดท้ายในรายการ
Noah Sussman

6
อย่างน้อยในการกำหนดค่าของฉันจะต้องมีช่องว่างทันทีหลังจาก "เริ่มต้น" ไม่จำเป็นต้องมีที่ว่างก่อนที่จะทำการดัดผมตอนจบ แต่อย่างที่คุณ Sussman ตั้งข้อสังเกตไว้คุณจำเป็นต้องใช้เซมิโคลอนปิด
willdye

4
คำตอบนี้ก่อนหน้านี้มีวงเล็บปีกการอบcommand1และcommand2; ฉันมารู้ทีหลังว่าพวกมันไม่จำเป็น
Keith Thompson

24
เพื่อชี้แจงความคิดเห็นข้างต้นเกี่ยวกับอัฒภาคต้องใช้อัฒภาคก่อนปิด}: 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
หากคุณกำลังรวมทั้ง%บางแห่งตัวละครในสายของคุณผ่านไปsh -cแล้วนี้มีแนวโน้มที่จะช่องโหว่ความปลอดภัย: ชื่อไฟล์ที่มี$(rm -rf ~)'$(rm -rf ~)'(และที่เป็น substring สมบูรณ์ตามกฎหมายที่จะมีภายในชื่อไฟล์บนระบบไฟล์ UNIX ทั่วไป!) จะทำให้ใครบางคนมากวันที่เลวร้าย .
Charles Duffy

35

ด้วย GNU Parallel คุณสามารถทำได้:

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

ดูวิดีโอแนะนำเพื่อเรียนรู้เพิ่มเติม: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

เพื่อความปลอดภัยขอแนะนำให้คุณใช้ตัวจัดการแพ็คเกจเพื่อติดตั้ง แต่ถ้าคุณไม่สามารถทำเช่นนั้นคุณสามารถใช้การติดตั้ง 10 วินาทีนี้

การติดตั้ง 10 วินาทีจะพยายามทำการติดตั้งแบบเต็ม หากล้มเหลวการติดตั้งส่วนบุคคล; หากการติดตั้งนั้นล้มเหลว

$ (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
การติดตั้งเครื่องมือผ่านการเรียกใช้สคริปต์แบบสุ่มจากไซต์ที่ไม่รู้จักเป็นวิธีปฏิบัติที่น่ากลัว Parallel มีแพ็คเกจขนาดเล็กสำหรับ distros ยอดนิยมซึ่งสามารถเชื่อถือได้ (เพื่อขยาย) วิธีมากกว่า wget แบบสุ่ม | sh ...
mdrozdziel

4
ให้เราดูว่าเวกเตอร์การโจมตีที่ง่ายที่สุดคืออะไร: Pi.dk ถูกควบคุมโดยผู้สร้าง GNU Parallel ดังนั้นการโจมตีที่คุณจะต้องบุกเข้าไปในเซิร์ฟเวอร์หรือเข้ายึด DNS หากต้องการรับแพ็คเกจอย่างเป็นทางการของการแจกจ่ายคุณสามารถอาสาสมัครเพื่อรักษาแพ็คเกจ ดังนั้นในขณะที่คุณอาจถูกต้องโดยทั่วไปดูเหมือนว่าในกรณีนี้ความคิดเห็นของคุณจะไม่เป็นธรรม
Ole Tange

10
ในทางปฏิบัติฉันไม่ทราบว่า pi.dk เป็นของผู้เขียน การตรวจสอบจริงว่าเป็นกรณีนี้คิดว่าวิธีการใช้ ssl ใน wget และตรวจสอบว่าคำสั่งนี้ทำสิ่งที่ควรจะทำคือการทำงาน จุดของคุณที่แพคเกจอย่างเป็นทางการสามารถมีรหัสที่เป็นอันตรายนั้นเป็นความจริง แต่ก็มีไว้สำหรับแพ็คเกจ wget
เฟเบียน

3
นี่อาจไม่ใช่ทางออกที่ดีที่สุดหากคำสั่งแต่ละคำสั่งที่ OP ต้องการดำเนินการต้องเป็นลำดับใช่ไหม?
IcarianComplex

2
@IcarianComplex การเพิ่ม -j1 จะแก้ไขได้
Ole Tange

26

นี่เป็นอีกวิธีหนึ่งที่ไม่มี xargs หรือ cat:

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

2
Buggy ตามที่กำหนด หากคุณไม่ชัดเจนIFSจะไม่สนใจช่องว่างนำหน้าและต่อท้ายในชื่อไฟล์ นอกจากว่าคุณจะเพิ่ม-rชื่อไฟล์ที่มีเครื่องหมายแบ็กสแลชที่แท้จริงจะทำให้ตัวอักษรเหล่านั้นถูกละเว้น
Charles Duffy

ไม่ตอบคำถาม xargsมันโดยเฉพาะถามเกี่ยวกับ (นี่เป็นการยากที่จะขยายการทำสิ่งที่คล้ายกับตัวเลือกxargsของGNU -P<n>)
Gert van den Berg

1
มันใช้งานได้ดีอย่างสมบูรณ์ คุณสามารถใช้มันเป็นคำสั่ง$ command | while read line; do c1 $line; c2 $line; done
piped

25

คุณสามารถใช้ได้

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

{} = ตัวแปรสำหรับแต่ละบรรทัดในไฟล์ข้อความ


8
สิ่งนี้ไม่ปลอดภัย เกิดอะไรขึ้นถ้าคุณfile.txtมี datum $(rm -rf ~)ที่มีซับสตริงอยู่
Charles Duffy

19

สิ่งหนึ่งที่ฉันทำคือการเพิ่ม. bashrc / .profile ฟังก์ชั่นนี้:

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

จากนั้นคุณสามารถทำสิ่งต่าง ๆ เช่น

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

ซึ่งน้อยกว่า verbose มากกว่า xargs หรือ -exec คุณยังสามารถปรับเปลี่ยนฟังก์ชั่นเพื่อแทรกค่าจากการอ่านที่ตำแหน่งโดยพลการในคำสั่งไปยังแต่ละรายการได้หากคุณต้องการพฤติกรรมนั้น


1
คำตอบที่ถูกประเมินต่ำกว่านี้มีประโยชน์มาก
charlesreid1

15

ฉันชอบสไตล์ที่ช่วยให้ใช้โหมดแห้ง (ไม่มี| sh):

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

ทำงานกับท่อได้เช่นกัน:

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

2
งานนี้จนกว่าคุณจะต้องการใช้ GNU xargs' -Pตัวเลือก ... (ถ้าไม่ได้ผมใช้ส่วนใหญ่-execในfindเนื่องจากปัจจัยการผลิตของฉันส่วนใหญ่จะเป็นชื่อไฟล์)
Gert van den Berg

13

สายไปงานเลี้ยงเล็กน้อย

ฉันใช้รูปแบบด้านล่างเพื่อบีบอัดไดเรกทอรีของฉันด้วยไฟล์เล็ก ๆ นับพันก่อนที่จะย้ายข้อมูล หากคุณไม่ต้องการคำพูดเดียวภายในคำสั่งมันควรจะทำงานได้

ด้วยการดัดแปลงบางอย่างฉันแน่ใจว่ามันจะมีประโยชน์สำหรับใครบางคน ทดสอบในCygwin(babun)

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

find .ค้นหาที่นี่
-maxdepth 1อย่าเข้าสู่ไดเรกทอรีย่อย
! -path .ยกเว้น / พา ธ ไดเรกทอรีปัจจุบัน
-type dจับคู่กับไดเรกทอรีเท่านั้น
-print0แยกเอาต์พุตด้วย null bytes \ 0
| xargsไปยัง xargs
-0อินพุตเป็น null คั่นด้วยไบต์ตัว
-I @@ยึดตำแหน่งคือ @@ แทนที่ @@ ด้วยอินพุต
bash -c '...'เรียกใช้คำสั่ง Bash
{...}การจัดกลุ่มคำสั่ง
&&ดำเนินการคำสั่งถัดไปเฉพาะเมื่อคำสั่งก่อนหน้าออกจากระบบเรียบร้อยแล้ว (ออกจาก 0)

รอบชิงชนะเลิศ;เป็นสิ่งสำคัญมิฉะนั้นจะล้มเหลว

เอาท์พุท:

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

อัพเดตเดือนกรกฎาคม 2561:

ถ้าคุณชอบแฮ็กและเล่นรอบ ๆ นี่เป็นสิ่งที่น่าสนใจ:

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

เอาท์พุท:

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

คำอธิบาย:
-สร้างสคริปต์ซับเดี่ยวและเก็บไว้ในตัวแปร
- xargsอ่านa.txtและดำเนินการเป็นbashสคริปต์
- @@ตรวจสอบให้แน่ใจว่าทุกครั้งที่มีการส่งผ่านทั้งบรรทัด
- การวาง@@หลังจาก--ตรวจสอบให้แน่ใจว่า@@ได้รับการป้อนพารามิเตอร์ตำแหน่งbashคำสั่งไม่ใช่bashเริ่มต้นOPTIONเช่น ชอบ-cตัวเองซึ่งหมายถึงrun command

--มันมีมนต์ขลังมันใช้งานได้กับสิ่งอื่น ๆ เช่นsshแม้กระทั่งkubectl


1
ฉันใช้การตั้งค่ากับสิ่งประเภทนี้: find . -type f -print0|xargs -r0 -n1 -P20 bash -c 'f="{}";ls -l "$f"; gzip -9 "$f"; ls -l "$f.gz"'(มันง่ายกว่าเล็กน้อยเมื่อแปลงลูป)
Gert van den Berg

1
แก้ไขล่าช้าสำหรับความคิดเห็นก่อนหน้าของฉัน: (หากชื่อไฟล์มีเครื่องหมายคำพูดคู่มีปัญหาด้านความปลอดภัย ... ) (ดูเหมือนว่าการใช้"$@"เป็นวิธีเดียวที่จะหลีกเลี่ยง ... (โดย-n1ถ้าคุณต้องการ จำกัด จำนวนพารามิเตอร์))
Gert van den Berg

ควรสังเกตว่า--เชลล์ใช้เพื่อบอกว่าจะไม่ยอมรับตัวเลือกเพิ่มเติม นี้ช่วยให้มีการ-หลังจาก--เกินไป คุณสามารถได้ผลลัพธ์ที่น่าสนใจและสับสนหากคุณไม่ได้ใช้เช่นgrep -rเมื่อคุณใส่ในรูปแบบ-! วิธีที่คุณพูดถึงมันไม่ได้อธิบายอย่างชัดเจน แต่ก็ไม่ได้อธิบายว่ามันใช้งานอย่างไร Iirc มันเป็นสิ่งที่ POSIX แต่อย่างไรก็ตามมันก็คุ้มค่าที่จะชี้ให้เห็นว่าฉันคิดว่า สิ่งที่ต้องพิจารณา และฉันชอบโบนัสนั้นด้วย!
Pryftan

10

นี่น่าจะเป็นเวอร์ชั่นที่ปลอดภัยที่สุด

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

( -0สามารถถอดออกและtrแทนที่ด้วยการเปลี่ยนเส้นทาง (หรือไฟล์สามารถถูกแทนที่ด้วย null แยกไฟล์แทน). เป็นส่วนใหญ่ในที่นั่นตั้งแต่ผมส่วนใหญ่ใช้xargsกับfindกับ-print0การส่งออก) (ซึ่งอาจจะเกี่ยวข้องในxargsรุ่นโดยไม่ต้อง-0ขยาย)

มีความปลอดภัยเนื่องจาก args จะส่งพารามิเตอร์ไปยังเชลล์เป็นอาร์เรย์เมื่อดำเนินการ เชลล์ (อย่างน้อยที่สุดbash) จะผ่านมันเป็นอาร์เรย์ที่ไม่เปลี่ยนแปลงไปยังกระบวนการอื่นเมื่อทั้งหมดได้รับโดยใช้["$@"][1]

หากคุณใช้...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";' ''การกำหนดจะล้มเหลวหากสตริงมีเครื่องหมายคำพูดคู่ นี้เป็นจริงสำหรับทุกตัวแปรที่ใช้หรือ-i -I(เนื่องจากมันถูกแทนที่เป็นสตริงคุณสามารถฉีดคำสั่งได้ตลอดเวลาโดยการใส่อักขระที่ไม่คาดคิด (เช่นอัญประกาศ backticks หรือเครื่องหมายดอลลาร์) ลงในข้อมูลอินพุต)

หากคำสั่งสามารถรับพารามิเตอร์ได้ครั้งละหนึ่งพารามิเตอร์เท่านั้น:

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

หรือด้วยกระบวนการที่ค่อนข้างน้อย:

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

หากคุณมี GNU xargsหรืออื่นที่มี-Pส่วนขยายและคุณต้องการเรียกใช้กระบวนการ 32 แบบขนานโดยแต่ละรายการมีพารามิเตอร์ไม่เกิน 10 พารามิเตอร์สำหรับแต่ละคำสั่ง:

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

สิ่งนี้ควรมีความแข็งแกร่งเมื่อเทียบกับอักขระพิเศษใด ๆ ในอินพุต (หากอินพุตเป็นโมฆะคั่น) trเวอร์ชันจะได้รับอินพุตที่ไม่ถูกต้องหากบรรทัดบางบรรทัดมีบรรทัดใหม่ แต่ไม่สามารถหลีกเลี่ยงได้ด้วยไฟล์ที่คั่นด้วยบรรทัดใหม่

พารามิเตอร์แรกที่ว่างเปล่าสำหรับbash -cเนื่องจาก: (จากbashหน้าคน ) (ขอบคุณ @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

คุณไม่มีอาร์กิวเมนต์ argv [0] เพื่อทุบตี bash -c 'command1 "$@"; command2 "$@";' arbitrarytextgoeshere
2511 clacke

3
นี่ไม่เกี่ยวกับสิ่งที่ xargs ทำ bashด้วย-ctake แรก (หลังจากคำสั่ง) อาร์กิวเมนต์หนึ่งตัวที่จะเป็นชื่อของกระบวนการจากนั้นจะรับอาร์กิวเมนต์ตำแหน่ง ลองbash -c 'echo "$@" ' 1 2 3 4ดูว่ามีอะไรเกิดขึ้นบ้าง
clacke

เป็นเรื่องดีที่มีรุ่นที่ปลอดภัยที่ไม่มี Bobby-Tabled
Mateen Ulhaq

8

อีกวิธีแก้ปัญหาที่เป็นไปได้สำหรับฉันก็คือ

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

สังเกตว่า 'bash' ในตอนท้าย - ฉันถือว่ามันถูกส่งผ่านเป็น argv [0] เพื่อ bash หากไม่มีในไวยากรณ์นี้พารามิเตอร์แรกของแต่ละคำสั่งจะหายไป มันอาจจะเป็นคำพูดใด ๆ

ตัวอย่าง:

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

5
ถ้าคุณไม่พูด"$@"คุณก็จะแยกสตริงและขยายรายการอาร์กิวเมนต์
Charles Duffy

2

BKM ปัจจุบันของฉันสำหรับสิ่งนี้คือ

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

โชคไม่ดีที่สิ่งนี้ใช้ Perl ซึ่งมีแนวโน้มที่จะติดตั้งน้อยกว่าทุบตี แต่จะจัดการกับอินพุตที่มากกว่าซึ่งคำตอบที่ยอมรับได้ (ฉันยินดีต้อนรับรุ่นที่แพร่หลายที่ไม่พึ่งพา Perl)

@ ข้อเสนอแนะของ KeithThompson

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

ดีมาก - ยกเว้นว่าคุณมีอักขระความคิดเห็นเชลล์ # ในอินพุตของคุณซึ่งในกรณีส่วนหนึ่งของคำสั่งแรกและคำสั่งที่สองทั้งหมดจะถูกตัดทอน

Hashes # สามารถพบได้บ่อยถ้าอินพุตนั้นได้มาจากรายการระบบไฟล์เช่น ls หรือ find และตัวแก้ไขของคุณจะสร้างไฟล์ชั่วคราวด้วย # ในชื่อของมัน

ตัวอย่างของปัญหา:

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

โอ๊ะนี่คือปัญหา:

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

อ่าดีกว่า:

$ 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
# ปัญหาสามารถแก้ไขได้อย่างง่ายดายโดยใช้เครื่องหมายคำพูด:ls | xargs -I % sh -c 'echo 1 "%"; echo 2 "%"'
gpl
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.