Học bằng cách đọc theo thứ tự

Đọc đầu vào — while read xử lý từng dòng

Bài viết này là một phần của Khóa học Linux, giúp bạn thành thạo từ đầu những kỹ năng Linux thực tiễn, từ các lệnh cơ bản đến lập trình shell script.
Dùng while read name age để tách data.txt theo từng dòng thành các cột, xử lý lần lượt nhiều tệp truyền qua tham số bằng for f in "$@", và đưa đầu vào cho read qua pipe hay here-doc — minh họa và thực hành trên terminal trong trình duyệt.

Đọc tệp từng dòng một — while read

Bạn thường muốn xử lý một danh sách tên hay tệp cấu hình theo từng dòng.

Khi bạn viết while read line; do … done < file, nó đọc file từ trên xuống mỗi lần một dòng, đưa dòng đó vào biến line rồi lặp lại phần nằm giữa do … done.

Dù tệp có bao nhiêu dòng đi nữa, nó vẫn tự động lặp đến dòng cuối cùng.

read tách dòng nhận được theo khoảng trắng và đưa các phần vào nhiều biến khác nhau.

Khi bạn viết while read name age; do …, từ đầu tiên của mỗi dòng đi vào name, từ tiếp theo đi vào age.

Với dữ liệu được sắp xếp theo dạng "tên giá trị" trong một danh sách tên, bạn lấy được từng cột ra và định dạng lại.

printf 'web running\ndb stopped\n' > services.txt   # tạo tệp nguyên liệu
while read svc state; do                              # mỗi dòng vào svc và state
  echo "Service $svc is $state"
done < services.txt                                   # lấy services.txt làm đầu vào
# đầu ra: Service web is running / Service db is stopped
Xử lý tệp từng dòng một bằng while read
data.txtwhile read name agename = aliceage = 30từng dòng mộttừ thứ 1từ thứ 2lặp đến khi hết dòng
while read name age đọc tệp mỗi lần một dòng, tách theo khoảng trắng rồi đưa từ đầu vào name, từ tiếp theo vào age.
Cú phápÝ nghĩaVí dụ
while read v; do … done < fXử lý tệp f từng dòng một bằng biến vwhile read l; do echo "$l"; done < data.txt
while read a b; do …Tách một dòng theo khoảng trắng vào nhiều biếnwhile read name age; do echo "$name"; done < data.txt

Tạo một script đọc tệp danh sách tên từng dòng một bằng while read rồi định dạng lại thành dạng "name is age years old".

① Chạy printf 'alice 30\nbob 25\ncarol 41\n' > data.txt để tạo tệp nguyên liệu xếp theo dạng "tên tuổi".

② Mở roster.sh bằng vi roster.sh rồi nhấn i để vào insert mode. Viết script đọc từng dòng bằng while read name age rồi in ra "$name is $age years old" bằng echo (đặt #!/bin/sh ở dòng đầu). Viết xong thì nhấn Esc:wq để lưu.

③ Thêm quyền thực thi bằng chmod +x roster.sh, chạy ./roster.sh, và xác nhận ba dòng hiện ra đã được định dạng lại.

④ Nếu bạn chưa chắc nên viết gì, hãy sao chép nội dung trong bảng đáp án rồi dán vào insert mode của vi.

(Chạy đúng thì phần giải thích sẽ hiện ra.)

Linux console
0 / 4 đã hoàn thành
Loading Linux Terminal...

Nhận nhiều tệp qua tham số — "$@" và $(cat f)

Nếu bạn cố định tên tệp cần xử lý ngay trong script, bạn không dùng được nó cho tệp khác.

Khi bạn truyền tham số lúc chạy, ví dụ ./show.sh a.txt b.txt, thì $1 là tham số thứ nhất còn $2 là tham số thứ hai.

Bạn nhận được tất cả tham số cùng lúc bằng "$@".

Khi bạn viết for f in "$@"; do … done, từng tệp được truyền vào sẽ lần lượt vào biến f để bạn xử lý theo thứ tự.

Khi muốn dùng toàn bộ tệp như một chuỗi, hãy đưa nó vào biến bằng text=$(cat "$f").

Bạn biết được số lượng tham số qua $#, nên có thể cho script hiện cách dùng khi không có tham số nào.

printf 'line A1\nline A2\n' > a.txt              # nguyên liệu 1
printf 'line B1\n' > b.txt                       # nguyên liệu 2
vi show.sh   # mở bằng trình soạn thảo, viết nội dung sau rồi lưu bằng :wq
#   #!/bin/sh
#   for f in "$@"; do
#     echo "--- $f ---"
#     cat "$f"
#   done
chmod +x show.sh
./show.sh a.txt b.txt                            # hiện lần lượt 2 tệp kèm tiêu đề
Xử lý lần lượt nhiều tệp truyền qua tham số
./view.sh a.txt b.txt$1 = a.txt$2 = b.txtfor f in "$@" lần lượtthứ 1thứ 2nhận hết bằng "$@" rồi xử lý
Các tệp truyền lúc chạy đi vào $1 $2, và với "$@" bạn nhận hết rồi xử lý từng cái bằng for.
Cú phápÝ nghĩaVí dụ
for f in "$@"; do …Xử lý lần lượt từng tệp tham sốfor f in "$@"; do cat "$f"; done
$(cat f)Đưa toàn bộ tệp vào một chuỗi duy nhấttext=$(cat data.txt)
$#Số lượng tham số (nếu 0 thì hiện cách dùng)[ "$#" -eq 0 ] && echo usage

Tạo một script xử lý lần lượt từng tên tệp nhận qua tham số và hiển thị tiêu đề cùng nội dung theo thứ tự.

① Chạy printf 'apple\nbanana\n' > fruit.txtprintf 'red\nblue\n' > color.txt để tạo hai tệp nguyên liệu.

② Mở view.sh bằng vi view.sh rồi nhấn i để vào insert mode. Viết script nhận từng tham số một bằng for f in "$@", in tiêu đề bằng echo, rồi đưa toàn bộ tệp vào một biến và hiển thị nội dung (đặt #!/bin/sh ở dòng đầu). Viết xong thì nhấn Esc:wq để lưu.

③ Thêm quyền thực thi bằng chmod +x view.sh, chạy ./view.sh fruit.txt color.txt, và xác nhận hai tệp hiện ra theo thứ tự.

④ Nếu bạn chưa chắc nên viết gì, hãy sao chép nội dung trong bảng đáp án rồi dán vào insert mode của vi.

Linux console
0 / 5 đã hoàn thành
Loading Linux Terminal...

Đầu vào tương tác và đầu vào qua pipe — read và here-doc

read đọc được không chỉ tệp mà cả đầu vào do người dùng gõ vào hay đầu vào truyền qua pipe.

Có các dạng như nối bằng pipe kiểu echo hi | read v, hoặc đưa cả cụm đầu vào tại chỗ bằng here-doc <<'EOF' … EOF.

Đây là nền tảng khi bạn kết hợp script với các lệnh khác.

# đọc đầu ra của pipe từng dòng một
printf 'one\ntwo\n' | while read v; do echo "line: $v"; done
# đưa đầu vào tại chỗ bằng here-doc
while read v; do echo "value: $v"; done <<'EOF'
x
y
EOF
# nhập tay đúng một lần (gõ name rồi Enter)
# chạy dòng này thì nó sẽ chờ bạn nhập
read name; echo "hello, $name"
Đưa đầu vào cho read mà không cần tệp
printf 'cat\ndog'| while read vxử lý từng dòng<<'EOF' … EOFwhile read vxử lý từng dòngđưa vào bằng pipeđưa vào bằng here-doc
Không có tệp thì bạn vẫn đưa được đầu vào cho while read từng dòng một bằng pipe hoặc here-doc.
Cú phápÝ nghĩaVí dụ
cmd | while read v; do …Xử lý đầu ra của pipe từng dòng mộtcat data.txt | while read l; do echo "$l"; done
<<'EOF' … EOFĐưa đầu vào tại chỗ bằng here-docwhile read l; do echo "$l"; done <<'EOF'

Tạo một script đưa đầu vào cho while read bằng pipe và here-doc, không đi qua tệp.

① Mở pipe.sh bằng vi pipe.sh rồi nhấn i để vào insert mode. Viết script đưa đầu ra của printf 'cat\ndog\n' qua pipe vào while read v; do echo "animal: $v"; done (đặt #!/bin/sh ở dòng đầu). Viết xong thì nhấn Esc:wq để lưu.

② Thêm quyền thực thi bằng chmod +x pipe.sh, chạy ./pipe.sh, và xác nhận hai dòng hiện ra.

③ Xác nhận đầu ra là animal: catanimal: dog, cho thấy bạn xử lý được đầu vào từng dòng một mà không cần tạo tệp.

④ Nếu bạn chưa chắc nên viết gì, hãy sao chép nội dung trong bảng đáp án rồi dán vào insert mode của vi.

Linux console
0 / 3 đã hoàn thành
Loading Linux Terminal...
QUIZ

Kiểm tra kiến thức

Hãy trả lời từng câu hỏi một.

Câu 1while read line; do … done < file xử lý file như thế nào?

Câu 2Cách viết nào đại diện cho toàn bộ tham số đã truyền cho script?

Câu 3Làm sao để xử lý đầu ra của một lệnh từng dòng một bằng read mà không tạo tệp?