103.4 Bài 2
Chứng chỉ: |
LPIC-1 |
---|---|
Phiên bản: |
5.0 |
Chủ đề: |
103 Lệnh GNU và Unix |
Mục tiêu: |
103.4 Sử dụng Luồng, Đường ống và Chuyển hướng |
Bài: |
2 trên 2 |
Giới thiệu
Một khía cạnh trong triết lý của Unix đã khẳng định rằng: mỗi một chương trình nên có một mục đích cụ thể và không nên được tích hợp với các tính năng nằm ngoài phạm vi của nó. Tuy nhiên, việc giữ cho mọi thứ đơn giản không có nghĩa là kết quả sẽ ít phức tạp hơn vì nhiều chương trình có thể được kết nối với nhau để tạo ra một kết quả đầu ra tổng hợp. Ký tự thanh dọc |
(còn được gọi là ký hiệu ống) có thể được sử dụng để tạo một đường dẫn nối trực tiếp đầu ra của một chương trình với đầu vào của một chương trình khác, trong khi trình thay thế lệnh sẽ cho phép người dùng lưu trữ đầu ra của một chương trình trong một biến hoặc trực tiếp sử dụng nó làm đối số cho một lệnh khác.
Đường ống
Không giống như chuyển hướng, với đường ống, dữ liệu sẽ đi theo luồng từ trái sang phải trong dòng lệnh và mục tiêu là một tiến trình khác chứ không phải một đường dẫn hệ thống tệp, bộ mô tả tệp hay Here document. Ký tự ống |
sẽ yêu cầu vỏ bắt đầu tất cả các lệnh riêng biệt cùng một lúc và kết nối đầu ra của lệnh trước với đầu vào của lệnh sau, từ trái sang phải. Ví dụ: thay vì sử dụng chuyển hướng, nội dung của tệp /proc/cpuinfo
được gửi tới đầu ra tiêu chuẩn bởi cat
có thể được dẫn tới stdin của wc
bằng lệnh sau:
$ cat /proc/cpuinfo | wc 208 1184 6096
Trong trường hợp không có đường dẫn đến tệp, wc
sẽ đếm số dòng, từ và ký tự mà nó nhận được từ stdin của nó (như trường hợp trong ví dụ). Có thể có nhiều đường ống trong một lệnh ghép. Trong ví dụ sau, có hai đường ống được sử dụng:
$ cat /proc/cpuinfo | grep 'model name' | uniq model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz
Nội dung của tệp /proc/cpuinfo
do cat /proc/cpuinfo
tạo ra đã được dẫn đến lệnh grep 'model name'
. Lệnh này sau đó sẽ chỉ chọn các dòng có chứa cụm từ model name
. Vì máy chạy ví dụ có nhiều CPU nên có dòng model name
đã lặp đi lặp lại. Đường ống cuối cùng sẽ kết nối grep 'model name'
với uniq
và chịu trách nhiệm bỏ qua bất kỳ dòng nào tương đương với dòng đã xuất hiện trong kết quả của lệnh grep
.
Các đường ống có thể được kết hợp với các phiên chuyển hướng trong cùng một dòng lệnh. Ví dụ trước có thể được viết lại thành một dạng đơn giản hơn:
$ grep 'model name' </proc/cpuinfo | uniq model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz
Chuyển hướng đầu vào cho grep
không thực sự cần thiết vì grep
chấp nhận đường dẫn tệp làm đối số, nhưng ví dụ đã minh họa cách xây dựng các lệnh kết hợp như vậy.
Các đường ống và phiên chuyển hướng là độc quyền, nghĩa là một nguồn chỉ có thể được ánh xạ tới một mục tiêu duy nhất. Tuy nhiên, ta có thể chuyển hướng đầu ra tới một tệp và vẫn nhìn thấy nó trên màn hình bằng chương trình tee
. Để làm điều này, chương trình đầu tiên sẽ gửi đầu ra của nó tới stdin của tee
và tên tệp sẽ được cung cấp cho chương trình sau để lưu trữ dữ liệu:
$ grep 'model name' </proc/cpuinfo | uniq | tee cpu_model.txt model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz $ cat cpu_model.txt model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz
Đầu ra của chương trình cuối cùng trong chuỗi được tạo bởi uniq
sẽ được hiển thị và lưu trữ trong tệp cpu_model.txt
. Để không ghi đè nội dung của tệp được cung cấp và nối thêm dữ liệu vào tệp, tùy chọn -a
phải đi cùng với tee
.
Chỉ có đầu ra tiêu chuẩn của một tiến trình mới được đường ống tiếp nhận. Giả sử người dùng phải trải qua một quá trình biên dịch dài trên màn hình, đồng thời phải lưu cả đầu ra tiêu chuẩn và lỗi tiêu chuẩn vào một tệp để kiểm tra sau. Nếu thư mục hiện tại không có Makefile, lệnh sau sẽ xuất ra lỗi:
$ make | tee log.txt make: *** No targets specified and no makefile found. Stop.
Mặc dù được hiển thị trên màn hình nhưng thông báo lỗi do make
tạo ra không được tee
ghi lại và tệp log.txt đã được tạo trống. Việc chuyển hướng cần phải được thực hiện trước khi một đường ống có thể bắt được stderr:
$ make 2>&1 | tee log.txt make: *** No targets specified and no makefile found. Stop. $ cat log.txt make: *** No targets specified and no makefile found. Stop.
Trong ví dụ này, stderr của make
đã được chuyển hướng đến stdout. Vì vậy, tee
có thể bắt nó bằng một đường ống, hiển thị nó trên màn hình và lưu nó trong tệp log.txt
. Trong những trường hợp như vậy, việc lưu các thông báo lỗi để kiểm tra sau sẽ trở nên hữu ích.
Trình thay thế Lệnh
Một phương pháp khác để bắt đầu ra của một lệnh là trình thay thế lệnh. Bằng cách đặt một lệnh bên trong dấu trích dẫn ngược (`), Bash sẽ thay thế lệnh bằng đầu ra tiêu chuẩn của nó. Ví dụ sau đây sẽ cho thấy cách sử dụng stdout của một chương trình làm đối số cho một chương trình khác:
$ mkdir `date +%Y-%m-%d` $ ls 2019-09-05
Đầu ra của chương trình date
(ngày hiện tại, được định dạng là năm-tháng-ngày) được sử dụng làm đối số để tạo một thư mục với mkdir
. Chúng ta có thể thu được một kết quả y hệt bằng cách sử dụng $()
thay vì dấu trích dẫn ngược:
$ rmdir 2019-09-05 $ mkdir $(date +%Y-%m-%d) $ ls 2019-09-05
Phương pháp tương tự có thể được sử dụng để lưu trữ đầu ra của lệnh dưới dạng một biến:
$ OS=`uname -o` $ echo $OS GNU/Linux
Lệnh uname -o
đã cho ra tên chung của hệ điều hành hiện tại được lưu trữ trong biến phiên OS
. Việc gán đầu ra của một lệnh cho một biến rất hữu ích trong các tệp lệnh vì nó giúp ta lưu trữ và đánh giá dữ liệu theo nhiều cách khác nhau.
Tùy thuộc vào đầu ra được tạo bởi lệnh được thay thế, trình thay thế lệnh tích hợp sẵn có thể sẽ không phù hợp. Một phương pháp phức tạp hơn để sử dụng đầu ra của một chương trình làm đối số cho một chương trình khác là áp dụng một phương thức trung gian được gọi là xargs
. Chương trình xargs
sẽ sử dụng nội dung mà nó nhận được qua stdin để chạy một lệnh nhất định sử dụng nội dung đó làm đối số. Ví dụ sau đây sẽ cho thấy xargs
đang chạy chương trình identify
với các đối số do chương trình find
cung cấp:
$ find /usr/share/icons -name 'debian*' | xargs identify -format "%f: %wx%h\n" debian-swirl.svg: 48x48 debian-swirl.png: 22x22 debian-swirl.png: 32x32 debian-swirl.png: 256x256 debian-swirl.png: 48x48 debian-swirl.png: 16x16 debian-swirl.png: 24x24 debian-swirl.svg: 48x48
Chương trình identify
là một phần của ImageMagick - một bộ công cụ dòng lệnh dùng để kiểm tra, chuyển đổi và chỉnh sửa hầu hết các loại tệp hình ảnh. Trong ví dụ này, xargs
đã lấy tất cả các đường dẫn được liệt kê bởi find
và đặt chúng làm đối số cho identify
, sau đó hiển thị thông tin cho từng tệp được định dạng theo yêu cầu của tùy chọn -format
. Các tệp được tìm thấy bởi find
trong ví dụ này là các hình ảnh có chứa biểu tượng phân phối trong hệ thống tệp Debian. -format
là tham số cho identify
chứ không phải cho xargs
.
Tùy chọn -n 1
yêu cầu xargs
chạy lệnh đã cho, mỗi lần với một đối số duy nhất. Trong trường hợp của ví dụ, thay vì chuyển tất cả các đường dẫn do find
tìm thấy dưới dạng danh sách đối số cho identify
, việc sử dụng xargs -n 1
sẽ thực thi lệnh identify
cho từng đường dẫn riêng biệt. Việc sử dụng -n 2
sẽ thực thi identify
với hai đường dẫn làm đối số, -n 3
với ba đường dẫn làm đối số, v.v. Tương tự, khi xargs
xử lý nội dung nhiều dòng — như trường hợp với đầu vào do find
cung cấp — tùy chọn -L
có thể được sử dụng để giới hạn số lượng dòng sẽ được sử dụng làm đối số cho mỗi lần thực thi lệnh.
Note
|
Việc sử dụng |
Nếu đường dẫn có ký tự khoảng trắng, chúng ta phải chạy find
với tùy chọn -print0
. Tùy chọn này sẽ hướng dẫn find
sử dụng một ký tự rỗng giữa mỗi mục nhập để danh sách có thể được phân tích cú pháp chính xác bởi xargs
(đầu ra đã bị nén):
$ find . -name '*avi' -print0 -o -name '*mp4' -print0 -o -name '*mkv' -print0 | xargs -0 du | sort -n
Tùy chọn -0
đã cho xargs
biết rằng ký tự rỗng nên được sử dụng làm dấu phân cách. Bằng cách đó, các đường dẫn tệp do find
cung cấp sẽ được phân tích cú pháp chính xác ngay cả khi chúng có các ký tự trống hoặc ký tự đặc biệt khác trong đó. Ví dụ trước đã cho chúng ta thấy cách sử dụng lệnh du
để tìm hiểu mức sử dụng đĩa của mọi tệp được tìm thấy và sau đó sắp xếp kết quả theo kích thước. Đầu ra đã bị nén cho ngắn gọn. Hãy lưu ý rằng, đối với mỗi tiêu chí tìm kiếm, ta phải đặt tùy chọn -print0
cho find
.
Theo mặc định, xargs
sẽ đặt các đối số của lệnh được thực hiện sau cùng. Để thay đổi hành vi đó, ta phải sử dụng tùy chọn -I
:
$ find . -mindepth 2 -name '*avi' -print0 -o -name '*mp4' -print0 -o -name '*mkv' -print0 | xargs -0 -I PATH mv PATH ./
Trong ví dụ trước, mọi tệp được tìm thấy bởi find
sẽ được chuyển đến thư mục hiện tại. Vì các đường dẫn nguồn phải được thông báo cho mv
trước đường dẫn mục tiêu nên một thuật ngữ thay thế sẽ được cung cấp cho tùy chọn -I
của xargs
và sau đó được đặt một cách thích hợp bên cạnh mv
. Bằng cách sử dụng ký tự rỗng làm dấu phân cách, ta sẽ không cần phải đặt thuật ngữ thay thế trong dấu trích dẫn kép.
Bài tập Hướng dẫn
-
Việc lưu ngày thực hiện các hành động được thực thi bởi các tệp lệnh tự động là rất tiện lợi. Lệnh
date +%Y-%m-%d
hiển thị ngày hiện tại ở định dạng năm-tháng-ngày. Làm thế nào để đầu ra của một lệnh như vậy có thể được lưu trữ trong một biến vỏ có tên làTODAY
bằng cách sử dụng trình thay thế lệnh? -
Bằng cách sử dụng lệnh
echo
, làm cách nào để gửi nội dung của biếnTODAY
đến đầu vào tiêu chuẩn của lệnhsed s/-/./g
? -
Làm cách nào để đầu ra của lệnh
date +%Y-%m-%d
được sử dụng làm Here string cho lệnhsed s/-/./g
? -
Lệnh
convert image.jpeg -resize 25% small/image.jpeg
tạo một phiên bản nhỏ hơn củaimage.jpeg
và đặt hình ảnh thu được vào một tệp có tên tương tự bên trong thư mục consmall
. Bằng cách sử dụngxargs
, làm thế nào để có thể thực hiện cùng một lệnh cho mọi hình ảnh được liệt kê trong tệpfilelist.txt
?
Bài tập Mở rộng
-
Một thói quen sao lưu đơn giản định kỳ sẽ tạo một hình ảnh của phân vùng
/dev/sda1
với lệnhdd < /dev/sda1 > sda1.img
. Để thực hiện kiểm tra tính toàn vẹn của dữ liệu trong tương lai, tiến trình cũng sẽ tạo một hàm băm SHA1 của tệp vớisha1sum < sda1.img > sda1.sha1
. Bằng cách thêm đường ống và lệnhtee
, hai lệnh này sẽ được kết hợp thành một như thế nào? -
Lệnh
tar
được sử dụng để lưu trữ nhiều tệp vào một tệp duy nhất trong khi vẫn giữ nguyên cấu trúc thư mục. Tùy chọn-T
cho phép chỉ định một tệp chứa các đường dẫn sẽ được lưu trữ. Ví dụ:find /etc -type f | tar -cJ -f /srv/backup/etc.tar.xz -T -
sẽ tạo một tệp tar nénetc.tar.xz
từ danh sách được cung cấp bởi lệnhfind
(tùy chọn-T -
cho biết đầu vào tiêu chuẩn dưới dạng danh sách đường dẫn). Để tránh các lỗi phân tích cú pháp có thể xảy ra do đường dẫn có chứa dấu cách, ta nên thêm các tùy chọn lệnh nào chofind
vàtar
? -
Thay vì mở một phiên vỏ từ xa mới, lệnh
ssh
chỉ có thể thực thi một lệnh được chỉ định làm đối số của nó:ssh user@storage "remote command"
. Giả sử rằngssh
cũng cho phép chuyển hướng đầu ra tiêu chuẩn của chương trình cục bộ sang đầu vào tiêu chuẩn của chương trình từ xa, làm cách nào để lệnhcat
dẫn một tệp cục bộ có tênetc.tar.gz
sang/srv/backup/etc.tar.gz
tạiuser@storage
thông quassh
?
Tóm tắt
Bài học này bao gồm các kỹ thuật giao tiếp giữa các tiến trình truyền thống được sử dụng bởi Linux. Dẫn ống Lệnh sẽ tạo một phương thức giao tiếp một chiều giữa hai tiến trình và trình thay thế lệnh sẽ cho phép lưu trữ đầu ra của một tiến trình trong một biến vỏ. Bài học đã đi qua các bước sau:
-
Cách các đường ống có thể được sử dụng để truyền đầu ra của một tiến trình sang đầu vào của một tiến trình khác.
-
Mục đích của lệnh
tee
vàxargs
. -
Cách bắt đầu ra của một tiến trình với trình thay thế lệnh, lưu trữ nó trong một biến hoặc sử dụng nó trực tiếp như một tham số cho một lệnh khác.
Các lệnh và tiến trình đã được nhắc tới là:
-
Dẫn ống lệnh bằng
|
. -
Thay thế lệnh bằng dấu trích dẫn ngược và
$()
. -
Các lệnh
tee
,xargs
vàfind
.
Đáp án Bài tập Hướng dẫn
-
Việc lưu ngày thực hiện các hành động được thực thi bởi các tệp lệnh tự động là rất tiện lợi. Lệnh
date +%Y-%m-%d
hiển thị ngày hiện tại ở định dạng năm-tháng-ngày. Làm thế nào để đầu ra của một lệnh như vậy có thể được lưu trữ trong một biến vỏ có tên làTODAY
bằng cách sử dụng trình thay thế lệnh?$ TODAY=`date +%Y-%m-%d`
hoặc
$ TODAY=$(date +%Y-%m-%d)
-
Bằng cách sử dụng lệnh
echo
, làm cách nào để gửi nội dung của biếnTODAY
đến đầu vào tiêu chuẩn của lệnhsed s/-/./g
?$ echo $TODAY | sed s/-/./g
-
Làm cách nào để đầu ra của lệnh
date +%Y-%m-%d
được sử dụng làm Here string cho lệnhsed s/-/./g
?$ sed s/-/./g <<< `date +%Y-%m-%d`
hoặc
$ sed s/-/./g <<< $(date +%Y-%m-%d)
-
Lệnh
convert image.jpeg -resize 25% small/image.jpeg
tạo một phiên bản nhỏ hơn củaimage.jpeg
và đặt hình ảnh thu được vào một tệp có tên tương tự bên trong thư mục consmall
. Bằng cách sử dụngxargs
, làm thế nào để có thể thực hiện cùng một lệnh cho mọi hình ảnh được liệt kê trong tệpfilelist.txt
?$ xargs -I IMG convert IMG -resize 25% small/IMG < filelist.txt
hoặc
$ cat filelist.txt | xargs -I IMG convert IMG -resize 25% small/IMG
Đáp án Bài tập Mở rộng
-
Một thói quen sao lưu đơn giản định kỳ sẽ tạo một hình ảnh của phân vùng
/dev/sda1
với lệnhdd < /dev/sda1 > sda1.img
. Để thực hiện kiểm tra tính toàn vẹn của dữ liệu trong tương lai, tiến trình cũng tạo một hàm băm SHA1 của tệp vớisha1sum < sda1.img > sda1.sha1
. Bằng cách thêm đường ống và lệnhtee
, hai lệnh này sẽ được kết hợp thành một như thế nào?# dd < /dev/sda1 | tee sda1.img | sha1sum > sda1.sha1
-
Lệnh
tar
được sử dụng để lưu trữ nhiều tệp vào một tệp duy nhất trong khi vẫn giữ nguyên cấu trúc thư mục. Tùy chọn-T
cho phép chỉ định một tệp chứa các đường dẫn sẽ được lưu trữ. Ví dụ:find /etc -type f | tar -cJ -f /srv/backup/etc.tar.xz -T -
sẽ tạo một tệp tar nénetc.tar.xz
từ danh sách được cung cấp bởi lệnhfind
(tùy chọn-T -
cho biết đầu vào tiêu chuẩn dưới dạng danh sách đường dẫn). Để tránh các lỗi phân tích cú pháp có thể xảy ra do đường dẫn có chứa dấu cách, ta nên thêm các tùy chọn lệnh nào chofind
vàtar
?Các tuỳ chọn
-print0
và--null
:$ find /etc -type f -print0 | tar -cJ -f /srv/backup/etc.tar.xz --null -T -
-
Thay vì mở một phiên vỏ từ xa mới, lệnh
ssh
chỉ có thể thực thi một lệnh được chỉ định làm đối số của nó:ssh user@storage "remote command"
. Giả sử rằngssh
cũng cho phép chuyển hướng đầu ra tiêu chuẩn của chương trình cục bộ sang đầu vào tiêu chuẩn của chương trình từ xa, làm cách nào để lệnhcat
dẫn một tệp cục bộ có tênetc.tar.gz
sang/srv/backup/etc.tar.gz
tạiuser@storage
thông quassh
?$ cat etc.tar.gz | ssh user@storage "cat > /srv/backup/etc.tar.gz"
hoặc
$ ssh user@storage "cat > /srv/backup/etc.tar.gz" < etc.tar.gz