Làm quen với xargs

xargs là gì?

Chúng ta sử dụng các ống dẫn (pipe) để chuyển hướng stdout của 1 lệnh đến stdin của 1 lệnh khác.

vd:

$ cat foo.txt | grep "test

1 vài lệnh chấp nhận dữ liệu dưới dạng các đối số của lệnh mà không chấp nhận 1 dòng dữ liệu qua stdin. Trong những trường hợp như vậy, chúng ta không thể sử dụng các ống dẫn để cung cấp dữ liệu qua các đối số dòng lệnh.

Chúng ta nên thử các phương pháp thay thế khác. xargs là 1 lệnh rất hữu dụng trong việc xử lý chuyển đổi dữ liệu từ stdin sang đối số dòng lệnh. Nó có thể thao tác stdin và chuyển đối nó thành các đối số dòng lệnh cho 1 lệnh được chỉ rõ. Ngoài ra, xargs có thể chuyển đổi các dữ liệu văn bản 1 hay nhiều dòng bất kỳ thành các định dạng khác, như là nhiều dòng hay 1 dòng và ngược lại.

Tất cả chúng ta, những người sử dụng Bash shell đều thích các lệnh 1 lớp, nơi mà các trình tự lệnh được kết hợp với nhau bằng việc sử dụng toán tử ống dẫn – pipe (|), mà không sử dụng dấu chấm phẩy ; giữa các lệnh được dùng. Việc phát thảo các lệnh 1 dòng làm cho các tác vụ trở nên hiệu quả và đơn giản hơn để giải quyết. Nó đòi hỏi việc hiểu đúng và luyện tập để xây dựng 1 lớp cho việc giải quyết các vấn đề về xử lý văn bản. xargs là 1 trong những thành phần quan trong cho việc xây dựng các lệnh 1 lớp.

Khi chúng ta sử dụng toán tử pipe (|), lệnh xargs sẽ xuất hiện đầu tiên sau toán tử này. xargs sử dụng stdin như là nguồn dữ liệu chính. Nó sử dụng dụng stdin và thực thi lệnh khác bằng việc cung cấp các đối số dòng lệnh cho lệnh thực thi đó sử dụng nguồn dữ liệu stdin.

vd:

$ command | xargs

Lệnh xargs có thể cung cấp các đối số cho 1 lệnh bằng việc định dạng lại dữ liệu nhận được qua stdin.

xargs có thể hoạt động như 1 sự thay thế cho đối số -exec trong lệnh find.

1 số việc chúng ta có thể làm với xargs

1. Chuyển đổi nhiều dòng dữ liệu nhập vào thành 1 dòng dữ liệu xuất ra:

Dữ liệu nhập gồm nhiều dòng có thể được chuyển đổi 1 cách đơn giản bằng việc loại bỏ ký tự dòng mới ‘\n’ và thay thế nó bằng ký tự khoảng trắng. Bằng việc sử dụng xargs, chúng ta có thể thực hiện việc trên như sau:

$ cat example.txt
1 2 3 4 5 6
7 8 9 10
11 12
$ cat example.txt | xargs
1 2 3 4 5 6 7 8 9 10 11 12

2. Chuyển đổi 1 dòng thành nhiều dòng

Bằng việc cung cấp số lượng các đối số tối đa trong 1 dòng (n), chúng ta có thể chia bất kỳ văn bản nào từ stdin thành các dòng có n đối số. Mỗi đối số là 1 phần của 1 chuỗi được phân tách nhau bởi ký tự khoảng trắng ” ” (space). Khoảng trắng là ký tự phân tách mặc định. 1 dòng có thể được chia thành nhiều dòng như sau:

$ cat example.txt | xargs -n 3
1 2 3
4 5 6
7 8 9
10 11 12

Chúng ta cũng có thể sử dụng dấu phân cách của riêng mình để phân biệt các đối số. Để chỉ định 1 dấu phân tách tùy chọn, sử dụng tùy chọn -d như sau:

$ echo "splitXsplitXsplitXsplit" | xargs -d X
split split split split

Bằng việc sử dụng -n cùng với lệnh trước, chúng ta có thể chia dữ liệu nhập vào thành nhiều dòng như sau:

$ echo "splitXsplitXsplitXsplit" | xargs -d X -n 2
split split
split split

3. Truyền các đối số được định dạng đến 1 lệnh

Cùng bắt đầu với 1 script echo nhỏ để chúng ta hiểu rõ hơn về việc sử dụng xargs để cung cấp các đối số lệnh:

#!/bin/bash
#Filename: cecho.sh
echo $*'#'

Khi các đối số được truyền đến cecho.sh, nó sẽ in ra các đối số và kết thúc bằng ký tự #.

vd:

$ ./cecho.sh arg1 arg2
arg1 arg2 #

Ở đây chúng ta sẽ gặp phải 1 vấn đề sau:

Nếu chúng ta có 1 danh sách các đối số trong 1 tập tin (1 đối số trong mỗi dòng) để cung cấp đến 1 lệnh (trong trường hợp này là cecho.sh. Chúng ta cần cung cấp các đối số theo 2 cách.

  • Cách 1: Chúng ta cần cung cấp từng đối số cho lệnh như sau:
$ ./cecho.sh arg1
$ ./cecho.sh arg2
$ ./cecho.sh arg3

hoặc chúng ta cần cung cấp 2 hoặc 3 đối số cho mỗi lần thực thi lệnh như sau:

$ ./cecho.sh arg1 arg2
$ ./cecho.sh arg3
  • Cách 2: Chúng ta cung cấp tất cả đối số cho lệnh như sau:
$ ./cecho.sh arg1 arg2 arg3

Quay trở lại vấn đề, chúng ta có 1 danh sách các đối số nằm trong 1 tập tin như sau:

$ cat args.txt
arg1
arg2
arg3

Nếu chúng ta sử dụng toán tử pipe (|) để cung cấp các đối số trên cho lệnh script cecho.sh của chúng ta như sau:

$ cat args.txt | ./cecho.sh

ta sẽ được kết quả trả về như sau:

#

Đây chính là lúc xargs phát huy tác dụng của mình.

  •  Với cách 1 (cung cấp từng đối số cho lệnh), chúng ta thực hiện với xargs như sau:
$ cat args.txt | xargs -n 1 ./cecho.sh
arg1 #
arg2 #
arg3 #

Để thực thi 1 lệnh với X đối số cho mỗi lần thực thi, sử dụng:

INPUT | xargs -n X

vd:

$ cat args.txt | xargs -n 2 ./cecho.sh
arg1 arg2 #
arg3 #
  • Với cách 2 (cung cấp tất cả đối số cho lệnh trong 1 lần), chúng ta thực hiện với xargs như sau:
$ cat args.txt | xargs ./cecho.sh
arg1 arg2 arg3 #

Trong ví dụ trên, chúng ta đã cung cấp các đối số dòng lệnh trực tiếp đến 1 lệnh cụ thể. Chúng ta chỉ có thể cung cấp các đối số từ tập tin args.txt. Tuy nhiên, trên thực tế, chúng ta có thể cần thêm 1 tham số cùng với lệnh (ví dụ, cecho.sh), cùng với các đối số được lấy ra từ args.txt, vd:

$ ./cecho.sh -p arg1 -l

Trong việc thực thi lệnh trên, arg1 là biến duy nhất. Những cái khác sẽ không đổi. Chúng ta sẽ đọc các đối số từ 1 tập tin (args.txt) và cung cấp nó như sau:

$ ./cecho.sh -p arg1 -l
$ ./cecho.sh -p arg2 -l
$ ./cecho.sh -p arg3 -l

Để cung cấp 1 thứ tự thực thi lệnh như trên, xargs có 1 tùy chọn -I. Bằng việc sử dụng -I, chúng ta có thể chỉ rõ 1 chuỗi thay thế sẽ được thay thế trong khi xargs mở rộng. Khi -I được dùng với xargs, nó sẽ thực thi lần lượt cho từng đối số.

vd:

$ cat args.txt | xargs -I {} ./cecho.sh -p {} -l
-p arg1 -l #
-p arg2 -l #
-p arg3 -l #

-I {} chỉ ra chuỗi thay thế. Với mỗi đối số được cung cấp cho lệnh, chuỗi {} sẽ được thay thế với các đối số được đọc qua stdin.

Khi được dùng với -I, lệnh được thực thi trong 1 vòng lặp. Khi có 3 đối số lệnh sẽ được thực thi 3 lần cùng với lệnh {}. Mỗi lần thực thi, {} được thay bằng các đối số lần lượt từng cái một.

 4. Sử dụng xargs với find

xargsfind là những người bạn tốt. Chúng có thể kết hợp để thực hiện các tác vụ 1 cách dễ dàng. Chúng ta thường kết  hợp 2 lệnh này không đúng cách.

vd:

 $ find . -type f -name "*.txt" -print | xargs rm -f

Việc làm này rất nguy hiểm. Nó có thể gây ra việc xóa những tập tin không cần thiết. Ở đây, chúng ta không thể dự đoán được ký tự phân cách (như là ‘\n’ hay ‘ ‘) cho kết quả trả về của lệnh find. Nhiều tên tập tin có thể chứa ký tự khoảng trắng và do vậy, xargs có thể biên dịch nhầm chúng như 1 dấu phân cách. Ví dụ, “hell text.txt” có thể được hiểu nhầm rằng “hell” và “text.txt” bởi xargs.

Do đó, chúng ta phải sử dụng -print0 cùng với find để cho ra kết quả cùng với ký tự phân tách null (‘\0’) mỗi khi chúng ta sử dụng kết quả của find như dữ liệu của xargs.

Vd:

Tìm các tập tin .txt và xóa chúng bằng việc sử dụng xargs:

$ find . -type f -name "*.txt" -print0 | xargs -0 rm -f

Lệnh trên sẽ xóa tất cả tập tin .txt. xargs -0 biên dịch ký tự phân cách là \0.

5. Đếm số dòng mã C trong 1 thư mục

Đây là 1 tác vụ phần lớn các lập trình viên làm, đó là, đếm tất cả các tập tin chương trình C. Việc này được thực hiện như sau:

$ find source_code_dir_path -type f -name "*.c" -print0 | xargs -0 wc -l

Nếu chúng ta muốn nhiều thống kê hơn về mã nguồn, có 1 tiện ích gọi là  SLOCCount rất hữu dụng. Các bản phân phối GNU/Linux hiện nay thường có gói phần mềm này hoặc chúng ta có thể tải nó từ http://www.dwheeler.com/
sloccount

6. Sử dụng While và subshell với stdin

xargs bị giới hạn trong việc cung cấp các đối số theo những cách giới hạn. xargs không thể cung cấp các đối số đến nhiều tập hợp lệnh. Với các lệnh thực thi với 1 tập hợp các đối số từ stdin, chúng ta có 1 cách rất linh hoạt. 1 subshell (1 khối chương trình nhỏ được viết bằng Bash shell) với 1 vòng lặp while có thể được dùng để đọc các đối số và thực thi các lệnh 1 cách phức tạp hơn như sau:

$ cat files.txt | ( while read arg; do cat $arg; done )

Lệnh trên tương tự với

$ cat files.txt | xargs -I {} cat {}

Bằng việc thay thế cat $arg với bất kỳ lệnh nào trong vòng lặp while, chúng ta có thể thực hiện nhiều lệnh với cùng đối số. Chúng ta cũng có thể truyền kết quả đến các lệnh khác mà không sử dụng pipe. Thủ thuật subshell ( ) có thể được dùng trong nhiều môi trường khác nhau. Khi được bao đóng với các toán tử subshell, nó hoạt động như 1 đơn vị với nhiều dòng lệnh bên trong.

vd:

$ cmd0 | ( cmd1;cmd2;cmd3) | cmd4

Nếu cmd1 là cd /, bên trong subshell, đường dẫn của thư mục làm việc sẽ thay đổi. Tuy nhiên, việc thay đổi này chỉ xảy ra bên trong subshell. cmd4 sẽ không thấy được sự thay đổi trên.

Kết luận

Như vậy là chúng ta đã có cái nhìn khái quát về xargs và biết được 1 số ứng dụng của nó trong môi trường Bash shell.

Chúng ta kết thúc bài tìm hiểu về xargs tại đây.

Rất mong nhận được những ý kiến đóng góp, góp ý của mọi người để làm cho chất lượng các bài viết ngày càng được nâng cao.

Chúc các bạn thành công!

Discussion