Chạy nhiều lệnh với xargs


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

Trong ví dụ trên, xargs lấy echo %làm đối số lệnh. Nhưng trong một số trường hợp, tôi cần nhiều lệnh để xử lý đối số thay vì một. Ví dụ:

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

Nhưng xargs không chấp nhận hình thức này. Một giải pháp tôi biết là tôi có thể định nghĩa một hàm để bọc các lệnh, nhưng đó không phải là một đường ống dẫn, tôi không thích nó. Có giải pháp nào khác không?


1
Hầu hết các câu trả lời là lỗ hổng bảo mật . Xem ở đây để có một câu trả lời tốt.
Mateen Ulhaq

2
Tôi sử dụng xargs cho hầu hết mọi thứ, nhưng tôi ghét đặt các lệnh bên trong chuỗi và tạo rõ ràng các chuỗi con. Tôi đang trên bờ vực học cách chuyển thành một whilevòng lặp có thể chứa nhiều lệnh.
Sridhar Sarnobat

Câu trả lời:


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

... hoặc, không sử dụng con mèo vô dụng :

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

Để giải thích một số điểm tốt hơn:

  • Việc sử dụng "$arg"thay vì %(và không có -Itrong xargsdòng lệnh) là vì lý do bảo mật: Truyền dữ liệu vào shdanh sách đối số dòng lệnh thay vì thay thế nó thành mã ngăn nội dung mà dữ liệu có thể chứa (chẳng hạn như $(rm -rf ~), đặc biệt ví dụ độc hại) từ việc được thực thi dưới dạng mã.

  • Tương tự, việc sử dụng -d $'\n'là một phần mở rộng GNU xargsđể xử lý từng dòng của tệp đầu vào như một mục dữ liệu riêng biệt. Điều này hoặc -0(mong đợi NUL thay vì dòng mới) là cần thiết để ngăn xargs cố gắng áp dụng phân tích cú pháp giống như vỏ (nhưng không tương thích với vỏ) cho luồng mà nó đọc. (Nếu bạn không có GNU xargs, bạn có thể sử dụng tr '\n' '\0' <a.txt | xargs -0 ...để có được cách đọc hướng dòng mà không có -d).

  • Đây _là một giữ chỗ cho $0, sao cho các giá trị dữ liệu khác được thêm vào xargstrở thành, trở thành $1tập hợp giá trị mặc định mà một forvòng lặp lặp lại.


58
Đối với những người không quen thuộc sh -c- lưu ý rằng dấu chấm phẩy sau mỗi lệnh không phải là tùy chọn, ngay cả khi đó là lệnh cuối cùng trong danh sách.

6
Ít nhất là trên cấu hình của tôi, phải có khoảng trắng ngay sau dấu "{" ban đầu. Không có khoảng trống được yêu cầu trước dấu ngoặc nhọn kết thúc, nhưng như ông Sussman lưu ý, bạn không cần một dấu chấm phẩy đóng.
willdye

4
Câu trả lời này trước đây có dấu ngoặc nhọn xung quanh command1command2; Sau này tôi nhận ra chúng không cần thiết.
Keith Thompson

24
Để làm rõ các ý kiến ​​trên về dấu chấm phẩy, cần có dấu chấm phẩy trước khi đóng }: sh -c '{ command1; command2; }' -- but it's not required at the end of a command sequence that doesn't use braces: sh -c 'lệnh1; lệnh2'`
Keith Thompson

8
Nếu bạn bao gồm %ký tự ở đâu đó trong chuỗi của bạn được chuyển đến sh -c, thì điều này dễ bị lỗ hổng bảo mật: Tên tệp chứa $(rm -rf ~)'$(rm -rf ~)'(và đó là một chuỗi con hoàn toàn hợp pháp để có trong tên tệp trên các hệ thống tệp UNIX phổ biến!) Sẽ khiến ai đó có một ngày rất tồi tệ .
Charles Duffy

35

Với GNU Parallel bạn có thể làm:

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

Xem các video giới thiệu để tìm hiểu thêm: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Vì lý do bảo mật, bạn nên sử dụng trình quản lý gói để cài đặt. Nhưng nếu bạn không thể làm điều đó thì bạn có thể sử dụng cài đặt 10 giây này.

Cài đặt 10 giây sẽ cố gắng thực hiện cài đặt đầy đủ; nếu thất bại, cài đặt cá nhân; Nếu thất bại, cài đặt tối thiểu.

$ (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
Cài đặt công cụ thông qua chạy các kịch bản ngẫu nhiên từ các trang web không xác định là thực tế khủng khiếp. Song song có các gói thông thường cho các bản phát hành phổ biến, có thể được tin cậy (với một số phần mở rộng) nhiều hơn so với wget ngẫu nhiên | sh ...
mdrozdziel 13/12/13

4
Hãy cho chúng tôi xem vectơ tấn công dễ nhất là gì: Pi.dk được điều khiển bởi tác giả của GNU Parallel, vì vậy để tấn công mà bạn sẽ phải đột nhập vào máy chủ hoặc chiếm lấy DNS. Để tiếp quản gói chính thức của một bản phân phối, bạn thường chỉ có thể tình nguyện duy trì gói. Vì vậy, trong khi bạn có thể nói chung, có vẻ như trong trường hợp cụ thể này, nhận xét của bạn không chính đáng.
Ole Tange

10
Trong thực tế tôi không biết rằng pi.dk thuộc về tác giả. Trên thực tế xác minh rằng đây là trường hợp, suy nghĩ về cách sử dụng ssl trong wget và kiểm tra xem lệnh này có làm những gì nó phải làm là một chút công việc. Quan điểm của bạn rằng gói chính thức có thể chứa mã độc là đúng, nhưng điều đó cũng đúng với gói wget.
Fabian

3
Đây có thể không phải là giải pháp tốt nhất nếu mỗi lệnh OP muốn thực thi phải tuần tự, đúng không?
IcarianComplex

2
@IcarianComplex Thêm -j1 sẽ khắc phục điều đó.
Ole Tange

26

Đây chỉ là một cách tiếp cận khác mà không có xargs hay cat:

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

2
Buggy, như đã cho. Trừ khi bạn rõ ràng IFS, nó sẽ bỏ qua khoảng trắng hàng đầu và dấu trong tên tệp; trừ khi bạn thêm -r, tên tệp có dấu gạch chéo theo nghĩa đen sẽ bị bỏ qua các ký tự đó.
Charles Duffy

Không trả lời câu hỏi. Nó đặc biệt hỏi về xargs. (Điều này khó mở rộng để làm một cái gì đó tương tự như tùy chọn xargscủa GNU -P<n>)
Gert van den Berg

1
Điều này hoạt động hoàn toàn tốt. Bạn cũng có thể sử dụng nó như một lệnh đường ống như$ command | while read line; do c1 $line; c2 $line; done
Alexar

25

Bạn có thể dùng

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

{} = biến cho mỗi dòng trên tệp văn bản


8
Điều này là không an toàn. Điều gì nếu bạn file.txtchứa một mốc thời gian với $(rm -rf ~)một chuỗi con?
Charles Duffy

19

Một điều tôi làm là thêm vào .bashrc / .profile chức năng này:

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

sau đó bạn có thể làm những việc như

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

ít dài dòng hơn xargs hoặc -exec. Bạn cũng có thể sửa đổi hàm để chèn giá trị từ đọc tại một vị trí tùy ý trong các lệnh cho mỗi lệnh, nếu bạn cũng cần hành vi đó.


1
Câu trả lời không thể bỏ qua, điều này cực kỳ tiện dụng
charlesreid1

15

Tôi thích phong cách cho phép chế độ chạy khô (không có | sh):

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

Hoạt động với đường ống quá:

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

2
Công trình này, cho đến khi bạn muốn sử dụng GNU xargs' -Ptùy chọn ... (Nếu không, tôi chủ yếu sử dụng -exectrên find, vì đầu vào của tôi chủ yếu là tên tập tin)
Gert van den Berg

13

Một chút muộn để dự tiệc.

Tôi sử dụng định dạng dưới đây để nén các thư mục của mình với hàng ngàn tệp nhỏ trước khi di chuyển. Nếu bạn không cần dấu ngoặc đơn bên trong các lệnh, nó sẽ hoạt động.

Với một số sửa đổi, tôi chắc chắn nó sẽ hữu ích cho ai đó. Đã thử nghiệm trong Cygwin(babun)

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

find .Tìm ở đây
-maxdepth 1Đừng đi vào thư mục con
! -path .Loại trừ. / Đường dẫn thư mục hiện tại
-type dchỉ khớp với các thư mục
-print0Đầu ra riêng biệt bằng null byte \ 0
| xargsĐường ống đến xargs
-0Đầu vào là byte được tách riêng byte
-I @@Giữ chỗ là @@. Thay thế @@ bằng đầu vào.
bash -c '...'Chạy lệnh Bash
{...}Nhóm lệnh
&&Thực thi lệnh tiếp theo chỉ khi lệnh trước đó thoát thành công (thoát 0)

Cuối cùng ;là quan trọng, nếu không nó sẽ thất bại.

Đầu ra:

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

Cập nhật tháng 7 năm 2018:

Nếu bạn thích hack và chơi xung quanh, đây là một điều thú vị:

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

Đầu ra:

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

Giải thích:
- Tạo một tập lệnh lót duy nhất và lưu trữ nó trong một biến
- xargs đọc a.txtvà thực thi nó dưới dạng bashtập lệnh
- @@ đảm bảo mỗi khi toàn bộ một dòng được truyền
- Đặt @@sau khi --đảm bảo @@được lấy làm đầu vào tham số vị trí cho bashlệnh, không phải là bashbắt đầu OPTION, nghĩa là giống như -cnó có nghĩa làrun command

--là ma thuật, nó hoạt động với nhiều thứ khác, tức là ssh, thậm chíkubectl


1
Tôi đã sử dụng một thiết lập với loại điều này: find . -type f -print0|xargs -r0 -n1 -P20 bash -c 'f="{}";ls -l "$f"; gzip -9 "$f"; ls -l "$f.gz"'(Nó dễ dàng hơn một chút khi chuyển đổi các vòng lặp)
Gert van den Berg

1
Chỉnh sửa muộn cho nhận xét trước của tôi: (Nếu tên tệp chứa dấu ngoặc kép có vấn đề bảo mật ...) (Có vẻ như sử dụng "$@"là cách duy nhất để tránh điều đó ... (với -n1nếu bạn muốn giới hạn số lượng tham số))
Gert van den Berg

Cần lưu ý rằng --được sử dụng bởi shell để nói rằng không có thêm tùy chọn nào được chấp nhận. Điều này cho phép có một -sau --quá. Bạn có thể nhận được một đầu ra rất thú vị và khó hiểu nếu bạn không sử dụng, ví dụ như grep -rkhi bạn đưa vào mẫu -! Cách bạn nói nó mặc dù không làm rõ điều này không thực sự giải thích cách thức hoạt động của nó. Tôi nghĩ đó là một điều POSIX nhưng dù sao nó cũng đáng để chỉ ra điều này, tôi nghĩ vậy. Chỉ cần một cái gì đó để xem xét. Và tôi thích phần thưởng btw đó!
Pryftan

10

Đây dường như là phiên bản an toàn nhất.

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

( -0Có thể được gỡ bỏ và trthay thế bằng một chuyển hướng (hoặc tập tin có thể được thay thế bằng một null tách tập tin thay vì). Nó là chủ yếu trong đó kể từ khi tôi chủ yếu sử dụng xargsvới findvới -print0đầu ra) (Điều này cũng có thể là có liên quan về xargscác phiên bản mà không có -0phần mở rộng)

Nó là an toàn, vì args sẽ truyền các tham số cho shell dưới dạng một mảng khi thực hiện nó. Shell (ít nhất bash) sau đó sẽ chuyển chúng thành một mảng không thay đổi cho các quy trình khác khi tất cả thu được bằng cách sử dụng["$@"][1]

Nếu bạn sử dụng ...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";' '', việc gán sẽ thất bại nếu chuỗi chứa dấu ngoặc kép. Điều này đúng cho mọi biến thể sử dụng -ihoặc -I. (Do được thay thế thành một chuỗi, bạn luôn có thể chèn các lệnh bằng cách chèn các ký tự không mong muốn (như dấu ngoặc kép, dấu hiệu ngược hoặc ký hiệu đô la) vào dữ liệu đầu vào)

Nếu các lệnh chỉ có thể nhận một tham số tại một thời điểm:

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

Hoặc với một số quy trình ít hơn:

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

Nếu bạn có GNU xargshoặc cái khác có -Pphần mở rộng và bạn muốn chạy song song 32 tiến trình, mỗi tiến trình có không quá 10 tham số cho mỗi lệnh:

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

Điều này nên mạnh mẽ chống lại bất kỳ ký tự đặc biệt trong đầu vào. (Nếu đầu vào là null tách biệt.) trPhiên bản sẽ nhận được một số đầu vào không hợp lệ nếu một số dòng chứa dòng mới, nhưng điều đó là không thể tránh khỏi với một tệp được phân tách dòng mới.

Tham số đầu tiên trống bash -clà do điều này: (Từ bashtrang man ) (Cảm ơn @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.

Điều này sẽ làm việc ngay cả với dấu ngoặc kép trong tên tệp. Điều đó đòi hỏi một cái vỏ hỗ trợ đúng cách"$@"
Gert van den Berg

Bạn đang thiếu đối số argv [0] để bash. bash -c 'command1 "$@"; command2 "$@";' arbitrarytextgoeshere
clacke

3
Đây không phải là về những gì xargs làm. bashvới -cmất đầu tiên (sau các lệnh) một đối số sẽ là tên của quá trình, sau đó nó nhận các đối số vị trí. Hãy thử bash -c 'echo "$@" ' 1 2 3 4và xem những gì đi ra.
clacke

Thật tuyệt khi có một phiên bản an toàn không có Bobby-Tabled.
Mateen Ulhaq

8

Một giải pháp khả thi khác phù hợp với tôi là một cái gì đó như -

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

Lưu ý 'bash' ở cuối - Tôi giả sử nó được truyền dưới dạng argv [0] cho bash. Không có nó trong cú pháp này, tham số đầu tiên cho mỗi lệnh sẽ bị mất. Nó có thể là bất kỳ từ nào.

Thí dụ:

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

5
Nếu bạn không trích dẫn "$@", thì bạn sẽ tách chuỗi và mở rộng toàn cầu danh sách đối số.
Charles Duffy

2

BKM hiện tại của tôi cho điều này là

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

Thật không may là điều này sử dụng perl, ít có khả năng được cài đặt hơn bash; nhưng nó xử lý nhiều đầu vào mà câu trả lời được chấp nhận. (Tôi hoan nghênh phiên bản có mặt ở khắp mọi nơi không dựa vào perl.)

@ Đề nghị của KeithThndry về

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

là tuyệt vời - trừ khi bạn có ký tự nhận xét shell # trong đầu vào của mình, trong trường hợp đó, một phần của lệnh đầu tiên và tất cả lệnh thứ hai sẽ bị cắt ngắn.

Băm # có thể khá phổ biến, nếu đầu vào được lấy từ danh sách hệ thống tệp, chẳng hạn như ls hoặc find và trình soạn thảo của bạn tạo các tệp tạm thời với # trong tên của chúng.

Ví dụ về vấn đề:

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

Rất tiếc, đây là vấn đề:

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

À, tốt hơn rồi:

$ 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
# vấn đề có thể được giải quyết dễ dàng bằng cách sử dụng dấu ngoặc kép:ls | xargs -I % sh -c 'echo 1 "%"; echo 2 "%"'
gpl
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.