103.4 Bài 1
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: |
1 trên 2 |
Giới thiệu
Tất cả các chương trình máy tính đều tuân theo cùng một nguyên tắc chung: dữ liệu nhận được từ một số nguồn sẽ được chuyển đổi để tạo ra một kết quả dễ hiểu. Trong ngữ cảnh của vỏ Linux, nguồn dữ liệu có thể là tệp cục bộ, tệp từ xa, thiết bị (như bàn phím), v.v. Đầu ra của chương trình thường được hiển thị trên màn hình, nhưng việc lưu trữ dữ liệu đầu ra trong một hệ thống tệp cục bộ, gửi nó đến một thiết bị từ xa, phát nó qua loa âm thanh, v.v. cũng là khá phổ biến.
Các hệ điều hành lấy cảm hứng từ Unix (như Linux) cung cấp rất nhiều các phương thức xuất/ nhập. Đặc biệt, phương thức mô tả tệp (file descriptors) cho phép chúng ta liên kết động các số nguyên với các kênh dữ liệu để một tiến trình có thể tham chiếu chúng dưới dạng các luồng dữ liệu đầu vào/đầu ra.
Các tiến trình Linux tiêu chuẩn có ba kênh giao tiếp được mở theo mặc định: kênh đầu vào tiêu chuẩn (hầu hết được gọi đơn giản là stdin), kênh đầu ra tiêu chuẩn (stdout) và kênh lỗi tiêu chuẩn (stderr). Các mô tả tệp bằng số được gán cho các kênh này là 0
cho stdin, 1
cho stdout và 2
cho stderr. Các kênh giao tiếp cũng có thể được truy cập thông qua các thiết bị đặc biệt là /dev/stdin
, /dev/stdout
và /dev/stderr
.
Ba kênh giao tiếp tiêu chuẩn này cho phép các lập trình viên viết mã đọc và ghi dữ liệu mà không phải lo lắng xem nó đến từ hoặc sẽ đi tới loại phương tiện nào. Ví dụ: nếu một chương trình cần một tập hợp dữ liệu làm đầu vào, chương trình đó chỉ cần yêu cầu dữ liệu từ đầu vào tiêu chuẩn và khi đó bất kỳ thứ gì đang được sử dụng làm đầu vào tiêu chuẩn sẽ cung cấp dữ liệu đó. Tương tự như vậy, phương pháp đơn giản nhất mà một chương trình có thể sử dụng để hiển thị đầu ra là ghi nó vào đầu ra tiêu chuẩn. Trong một phiên vỏ tiêu chuẩn, bàn phím được xác định là stdin và màn hình điều khiển được xác định stdout và stderr.
Vỏ Bash có khả năng gán lại các kênh liên lạc khi tải chương trình (ví dụ: nó cho phép ghi đè màn hình làm đầu ra tiêu chuẩn và sử dụng một tệp trong hệ thống tệp cục bộ làm stdout).
Chuyển hướng
Việc gán lại bộ mô tả tệp của một kênh trong môi trường vỏ được gọi là một phiên chuyển hướng. Phiên chuyển hướng được xác định bởi một ký tự đặc biệt trong dòng lệnh. Ví dụ: để chuyển hướng đầu ra tiêu chuẩn của một tiến trình sang một tệp, ký hiệu lớn hơn >
sẽ được đặt ở cuối lệnh và theo sau là đường dẫn đến tệp sẽ nhận đầu ra được chuyển hướng:
$ cat /proc/cpuinfo >/tmp/cpu.txt
Theo mặc định, chỉ có nội dung đến stdout mới được chuyển hướng. Điều này xảy ra vì giá trị bằng số của bộ mô tả tệp phải được chỉ định ngay trước ký hiệu lớn hơn và khi không được chỉ định, Bash sẽ chuyển hướng đầu ra tiêu chuẩn. Do đó, việc sử dụng >
sẽ tương đương với việc sử dụng 1>
(giá trị bộ mô tả tệp của stdout là 1
).
Để nắm bắt nội dung của stderr, ta nên sử dụng chuyển hướng 2>
. Hầu hết các chương trình dòng lệnh đều gửi thông tin gỡ lỗi và thông báo lỗi đến kênh lỗi tiêu chuẩn. Ví dụ: để ghi lại thông báo lỗi được kích hoạt khi ta cố đọc một tệp không tồn tại:
$ cat /proc/cpu_info 2>/tmp/error.txt $ cat /tmp/error.txt cat: /proc/cpu_info: No such file or directory
Cả stdout và stderr đều được chuyển hướng đến cùng một mục tiêu bằng &>
hoặc >&
. Điều quan trọng là không đặt bất kỳ khoảng trắng nào bên cạnh dấu &
; nếu có, Bash sẽ coi đó là hướng dẫn để chạy tiến trình ở chế độ nền và không thực hiện chuyển hướng.
Mục tiêu phải là đường dẫn đến tệp có thể ghi được, chẳng hạn như /tmp/cpu.txt
hoặc bộ mô tả tệp có thể ghi. Mục tiêu của bộ mô tả tệp sẽ được biểu thị bằng dấu &
, theo sau là giá trị bằng số của bộ mô tả tệp. Ví dụ: 1>&2
sẽ chuyển hướng từ stdout sang stderr. Để làm ngược lại, tức là từ stderr sang stdout, ta sẽ sử dụng 2>&1
.
Mặc dù không hữu ích lắm vì có một cách ngắn hơn để thực hiện cùng một tác vụ, chúng ta vẫn có thể chuyển hướng từ stderr sang stdout và sau đó chuyển hướng nó đến một tệp. Ví dụ: một phiên chuyển hướng để ghi cả stderr và stdout vào một tệp có tên log.txt
có thể được viết là >log.txt 2>&1
. Tuy nhiên, lý do chính để chuyển hướng từ stderr sang stdout là để cho phép phân tích cú pháp gỡ lỗi và thông báo lỗi. Ta có thể chuyển hướng đầu ra tiêu chuẩn của một chương trình sang đầu vào tiêu chuẩn của chương trình khác, nhưng ta không thể chuyển hướng trực tiếp lỗi tiêu chuẩn sang đầu vào tiêu chuẩn của một chương trình khác. Do đó, các thông báo của chương trình được gửi đến stderr trước tiên cần được chuyển hướng đến stdout để stdin của chương trình khác có thể đọc được.
Để loại bỏ đầu ra của một lệnh, nội dung của nó có thể được chuyển hướng đến tệp đặc biệt /dev/null
. Ví dụ: >log.txt 2>/dev/null
lưu nội dung của stdout trong tệp log.txt
và loại bỏ stderr. Tệp /dev/null
có thể được ghi bởi bất kỳ người dùng nào, nhưng ta sẽ không thể khôi phục dữ liệu từ tệp này vì tệp không được lưu trữ ở bất kỳ một vị trí nào.
Một thông báo lỗi sẽ được hiển thị nếu mục tiêu đã chỉ định không thể ghi được (nếu đường dẫn trỏ đến một thư mục hoặc tệp chỉ cho phép đọc) và không có sửa đổi nào đối với mục tiêu được thực hiện. Tuy nhiên, một phiên chuyển hướng đầu ra sẽ ghi đè lên một mục tiêu có thể ghi hiện có mà không có bất kỳ xác nhận nào. Các tệp sẽ bị ghi đè bởi chuyển hướng đầu ra trừ khi tùy chọn Bash noclobber
được kích hoạt. Việc này có thể thực hiện trong phiên hiện tại bằng lệnh set -o noclobber
hoặc set -C
:
$ set -o noclobber $ cat /proc/cpu_info 2>/tmp/error.txt -bash: /tmp/error.txt: cannot overwrite existing file
Để huỷ tùy chọn noclobber
cho phiên hiện tại, hãy chạy set +o noclobber
hoặc set +C
. Để duy trì tùy chọn noclobber
, tùy chọn này phải được đưa vào hồ sơ Bash của người dùng hoặc trong hồ sơ toàn hệ thống.
Ngay cả khi bật tùy chọn noclobber
, ta vẫn có thể nối thêm dữ liệu được chuyển hướng vào nội dung hiện có. Điều này được thực hiện với một chuyển hướng được viết bằng hai ký hiệu lớn hơn >>
:
$ cat /proc/cpu_info 2>>/tmp/error.txt $ cat /tmp/error.txt cat: /proc/cpu_info: No such file or directory cat: /proc/cpu_info: No such file or directory
Ngay cả khi bật tùy chọn noclobber
, ta vẫn có thể nối thêm dữ liệu được chuyển hướng vào nội dung hiện có. Điều này được thực hiện với một chuyển hướng được viết bằng hai ký hiệu lớn hơn (>>
):
Nguồn dữ liệu đầu vào tiêu chuẩn của một tiến trình cũng có thể được chỉ định lại. Ký hiệu nhỏ hơn (<
) được sử dụng để chuyển hướng nội dung của tệp sang stdin của một tiến trình. Trong trường hợp này, dữ liệu sẽ đi từ phải sang trái: bộ mô tả được gán lại được coi là 0 ở bên trái của ký hiệu nhỏ hơn và nguồn dữ liệu (đường dẫn đến tệp) phải ở bên phải của ký hiệu nhỏ hơn. Lệnh uniq
, giống như hầu hết các tiện ích dòng lệnh dùng để xử lý văn bản, chấp nhận dữ liệu được gửi tới stdin theo mặc định:
$ uniq -c </tmp/error.txt 2 cat: /proc/cpu_info: No such file or directory
Tùy chọn -c
sẽ khiến uniq
hiển thị số lần một dòng lặp lại xuất hiện trong văn bản. Vì giá trị bằng số của bộ mô tả tệp được chuyển hướng đã được nén, lệnh mẫu sẽ tương đương với uniq -c 0</tmp/error.txt
. Việc sử dụng một bộ mô tả tệp khác 0
trong một phiên chuyển hướng đầu vào sẽ chỉ có ý nghĩa trong một số ngữ cảnh cụ thể vì chương trình có thể yêu cầu dữ liệu tại các bộ mô tả tệp 3
, 4
, v.v. Các chương trình có thể sử dụng bất kỳ số nguyên nào lớn hơn 2 để làm bộ mô tả tệp mới cho đầu vào/đầu ra dữ liệu. Ví dụ: mã C sau đây sẽ đọc dữ liệu từ bộ mô tả tệp 3
và chỉ sao chép nó vào bộ mô tả tệp 4
:
Note
|
Chương trình phải xử lý các bộ mô tả tệp như vậy một cách chính xác. Nếu không, chương trình có thể sẽ thực hiện một thao tác đọc hoặc ghi không hợp lệ và gặp phải sự cố. |
#include <stdio.h> int main(int argc, char **argv){ FILE *fd_3, *fd_4; // Open file descriptor 3 fd_3 = fdopen(3, "r"); // Open file descriptor 4 fd_4 = fdopen(4, "w"); // Read from file descriptor 3 char buf[32]; while ( fgets(buf, 32, fd_3) != NULL ){ // Write to file descriptor 4 fprintf(fd_4, "%s", buf); } // Close both file descriptors fclose(fd_3); fclose(fd_4); }
Để kiểm tra, hãy lưu mã mẫu dưới dạng fd.c
và biên dịch mã đó với gcc -o fd fd.c
. Chương trình này cần có các bộ mô tả tệp 3 và 4 để có thể được đọc và ghi vào chúng. Ví dụ: tệp /tmp/error.txt
được tạo trước đó có thể được sử dụng làm nguồn cho bộ mô tả tệp 3
và bộ mô tả tệp 4
có thể được chuyển hướng đến stdout:
$ ./fd 3</tmp/error.txt 4>&1 cat: /proc/cpu_info: No such file or directory cat: /proc/cpu_info: No such file or directory
Từ góc nhìn của lập trình viên, việc sử dụng bộ mô tả tệp sẽ tránh được việc xử lý các đường dẫn hệ thống tệp và phân tích cú pháp tùy chọn. Một bộ mô tả tệp thậm chí có thể được sử dụng làm cả đầu vào và đầu ra. Trong trường hợp này, bộ mô tả tệp được xác định trong dòng lệnh sẽ có cả ký hiệu nhỏ hơn và lớn hơn (như trong 3<>/tmp/error.txt
).
Here Document và Here String
Một cách khác để chuyển hướng đầu vào sẽ liên quan đến các phương thức Here document và Here string. Phiên chuyển hướng Here document sẽ cho phép nhập văn bản nhiều dòng được sử dụng làm nội dung được chuyển hướng. Hai ký hiệu nhỏ hơn (<<
) biểu thị cho một phiên chuyển hướng Here document:
$ wc -c <<EOF > How many characters > in this Here document? > EOF 43
Ở bên phải của hai ký hiệu nhỏ hơn <<
là thuật ngữ kết thúc EOF
. Chế độ chèn sẽ kết thúc ngay sau khi một dòng chỉ chứa duy nhất cụm từ kết thúc được nhập. Bất kỳ một thuật ngữ nào khác cũng đều có thể được sử dụng làm thuật ngữ kết thúc, nhưng chúng ta không được đặt các ký tự trống giữa ký hiệu nhỏ hơn và thuật ngữ kết thúc. Trong ví dụ trên, hai dòng văn bản đã được gửi đến stdin của lệnh wc -c
hiển thị số lượng ký tự. Như với các phiên chuyển hướng đầu vào cho các tệp, stdin (bộ mô tả tệp 0
) sẽ được tự giả định nếu bộ mô tả tệp được chuyển hướng được nén.
Phương thức Here string cũng giống như phương thức Here document nhưng chỉ dành cho một dòng:
$ wc -c <<<"How many characters in this Here string?" 41
Trong ví dụ này, chuỗi ở bên phải của ba dấu nhỏ hơn được gửi đến stdin của lệnh wc -c
dùng để đếm số lượng ký tự. Các chuỗi chứa khoảng trắng phải nằm trong dấu trích dẫn kép; nếu không, chỉ có từ đầu tiên được sử dụng làm Here string và các từ còn lại sẽ được chuyển làm đối số cho lệnh.
Bài tập Hướng dẫn
-
Ngoài các tệp văn bản, lệnh
cat
cũng có thể hoạt động với dữ liệu nhị phân, chẳng hạn như gửi nội dung của một thiết bị khối đến một tệp. Bằng cách sử dụng tác vụ chuyển hướng, làm cách nào đểcat
có thể gửi nội dung của thiết bị/dev/sdc
tới tệpsdc.img
trong thư mục hiện tại? -
Tên của kênh tiêu chuẩn được chuyển hướng bằng lệnh
date 1> now.txt
là gì? -
Sau khi cố gắng ghi đè lên một tệp bằng cách chuyển hướng, người dùng gặp lỗi thông báo rằng tùy chọn
noclobber
đã được kích hoạt. Làm cách nào để vô hiệu hoánoclobber
cho phiên hiện tại? -
Kết quả của lệnh
cat <<.>/dev/stdout
sẽ là gì?
Bài tập Mở rộng
-
Lệnh
cat /proc/cpu_info
hiển thị thông báo lỗi vì/proc/cpu_info
không tồn tại. Lệnhcat /proc/cpu_info 2>1
sẽ chuyển hướng thông báo lỗi đến đâu? -
Có thể loại bỏ nội dung được gửi tới
/dev/null
nếu tùy chọnnoclobber
đã được kích hoạt cho phiên vỏ hiện tại không? -
Nếu không sử dụng
echo
, làm cách nào để chuyển hướng nội dung của biến$USER
đến stdin của lệnhsha1sum
? -
Nhân Linux giữ các liên kết tượng trưng trong
/proc/PID/fd/
cho mọi tệp được mở bởi một tiến trình, trong đó, PID là số nhận dạng của tiến trình tương ứng. Làm cách nào để quản trị viên hệ thống có thể sử dụng thư mục đó để xác minh vị trí của các tệp nhật ký được mở bởinginx
(giả sử PID của nó là1234
)? -
Có thể thực hiện các phép tính số học chỉ bằng cách sử dụng các lệnh tích hợp sẵn của vỏ, nhưng các phép tính số chấm động (floating point) yêu cầu các chương trình cụ thể như
bc
(basic calculator - máy tính cơ bản). Vớibc
, ta thậm chí có thể chỉ định số vị trí thập phân với tham sốscale
. Tuy nhiên,bc
chỉ chấp nhận các hoạt động thông qua đầu vào tiêu chuẩn của nó thường được nhập trong chế độ tương tác. Bằng cách sử dụng chuỗi Here, làm cách nào để có thể gửi thao tác số chấm độngscale=6; 1/3
đến đầu vào tiêu chuẩn củabc
?
Tóm tắt
Bài học này bao gồm các phương pháp để chạy một chương trình và chuyển hướng các kênh giao tiếp tiêu chuẩn của nó. Các tiến trình Linux sử dụng các kênh tiêu chuẩn này làm _ bộ mô tả tệp_ chung để đọc và ghi dữ liệu, giúp ta tùy ý thay đổi chúng thành tệp hoặc thiết bị. Bài học đã đi theo các bước sau:
-
Bộ mô tả tệp là gì và vai trò của chúng trong Linux.
-
Các kênh giao tiếp tiêu chuẩn của mọi tiến trình: stdin, stdout và stderr.
-
Cách thực hiện chính xác một lệnh bằng cách sử dụng chuyển hướng dữ liệu cho cả đầu vào và đầu ra.
-
Cách sử dụng Here Documents và Here Strings trong chuyển hướng đầu vào.
Các lệnh và tiến trình được nhắc đến là:
-
Các toán tử chuyển hướng:
>
,<
,>>
,<<
,<<<
. -
Lệnh
cat
,set
,uniq
vàwc
.
Đáp án Bài tập Hướng dẫn
-
Ngoài các tệp văn bản, lệnh
cat
cũng có thể hoạt động với dữ liệu nhị phân, chẳng hạn như gửi nội dung của một thiết bị khối đến một tệp. Bằng cách sử dụng tác vụ chuyển hướng, làm cách nào đểcat
có thể gửi nội dung của thiết bị/dev/sdc
tới tệpsdc.img
trong thư mục hiện tại?$ cat /dev/sdc > sdc.img
-
Tên của kênh tiêu chuẩn được chuyển hướng bằng lệnh
date 1> now.txt
là gì?Đầu ra tiêu chuẩn hoặc stdout.
-
Sau khi cố gắng ghi đè lên một tệp bằng cách chuyển hướng, người dùng gặp lỗi thông báo rằng tùy chọn
noclobber
đã được kích hoạt. Làm cách nào để vô hiệu hoánoclobber
cho phiên hiện tại?set +C
hoặcset +o noclobber
-
Kết quả của lệnh
cat <<.>/dev/stdout
sẽ là gì?Bash sẽ vào chế độ nhập Heredoc, sau đó thoát ra khi một dấu chấm xuất hiện trong một dòng. Văn bản đã nhập sẽ được chuyển hướng đến stdout (được in trên màn hình).
Đáp án Bài tập Mở rộng
-
Lệnh
cat /proc/cpu_info
hiển thị thông báo lỗi vì/proc/cpu_info
không tồn tại. Lệnhcat /proc/cpu_info 2>1
sẽ chuyển hướng thông báo lỗi đến đâu?Đến một tệp có tên
1
trong thư mục hiện tại. -
Có thể loại bỏ nội dung được gửi tới
/dev/null
nếu tùy chọnnoclobber
đã được kích hoạt cho phiên vỏ hiện tại không?Có thể.
/dev/null
là một tệp đặc biệt không bị ảnh hưởng bởinoclobber
. -
Nếu không sử dụng
echo
, làm cách nào để chuyển hướng nội dung của biến$USER
đến stdin của lệnhsha1sum
?$ sha1sum <<<$USER
-
Nhân Linux giữ các liên kết tượng trưng trong
/proc/PID/fd/
cho mọi tệp được mở bởi một tiến trình, trong đó, PID là số nhận dạng của tiến trình tương ứng. Làm cách nào để quản trị viên hệ thống có thể sử dụng thư mục đó để xác minh vị trí của các tệp nhật ký được mở bởinginx
(giả sử PID của nó là1234
)?Bằng cách thực hiện lệnh
ls -l /proc/1234/fd
. Lệnh này sẽ hiển thị đích của mọi liên kết tượng trưng trong thư mục. -
Có thể thực hiện các phép tính số học chỉ bằng cách sử dụng các lệnh tích hợp sẵn của vỏ, nhưng các phép tính số chấm động (floating point) yêu cầu các chương trình cụ thể như
bc
(basic calculator - máy tính cơ bản). Vớibc
, ta thậm chí có thể chỉ định số vị trí thập phân với tham sốscale
. Tuy nhiên,bc
chỉ chấp nhận các hoạt động thông qua đầu vào tiêu chuẩn của nó thường được nhập trong chế độ tương tác. Bằng cách sử dụng chuỗi Here, làm cách nào để có thể gửi thao tác số chấm độngscale=6; 1/3
đến đầu vào tiêu chuẩn củabc
?$ bc <<<"scale=6; 1/3"