Bash là ngôn ngữ lập trình mạnh mẽ, thiết kế hoàn hảo cho dùng trên giao diện dòng lệnh (CLI) và trong shell script. Serie ba phần này, dựa trên khóa tự học Linux ba tập của tôi, khám phá dùng Bash như ngôn ngữ lập trình trên CLI.
Bài đầu tiên trong serie khám phá lập trình dòng lệnh đơn giản với Bash, bao gồm dùng biến và toán tử điều khiển. Bài thứ hai xem các loại toán tử logic file, chuỗi, số, và khác cung cấp logic kiểm soát luồng thực thi và các loại mở rộng shell trong Bash. Bài thứ ba (và cuối cùng) này xem xét dùng vòng lặp cho các loại hoạt động lặp và cách kiểm soát vòng lặp đó.
Vòng lặp
Mọi ngôn ngữ lập trình tôi từng dùng có ít nhất vài loại cấu trúc vòng lặp cung cấp khả năng thực hiện hoạt động lặp. Tôi dùng vòng lặp for khá nhiều nhưng cũng thấy vòng lặp while và until hữu ích.
Vòng lặp for
Triển khai lệnh for của Bash, theo ý kiến tôi, hơi linh hoạt hơn hầu hết vì nó xử lý được giá trị không phải số; ngược lại, ví dụ, vòng lặp for chuẩn của ngôn ngữ C chỉ xử lý giá trị số.
Cấu trúc cơ bản của phiên bản Bash lệnh for đơn giản:
for Var in list1 ; do list2 ; done
Điều này dịch thành: "Với mỗi giá trị trong list1, đặt $Var thành giá trị đó rồi thực hiện câu lệnh chương trình trong list2 dùng giá trị đó; khi tất cả giá trị trong list1 đã dùng xong, kết thúc, thoát vòng lặp." Giá trị trong list1 có thể là chuỗi giá trị tường minh đơn giản, hoặc kết quả của command substitution (mô tả bài thứ hai trong serie). Tôi dùng cấu trúc này thường xuyên.
Để thử, đảm bảo ~/testdir vẫn là thư mục làm việc hiện tại (PWD). Dọn thư mục, xem ví dụ tầm thường vòng lặp for bắt đầu với danh sách giá trị tường minh. Danh sách này trộn giá trị alphanumeric—đừng quên tất cả biến là chuỗi và có thể đối xử như vậy.
[student@studentvm1 testdir]$ rm *
[student@studentvm1 testdir]$ for I in a b c d 1 2 3 4 ; do echo $I ; done
a
b
c
d
1
2
3
4
Đây là phiên bản hữu ích hơn với tên biến có ý nghĩa hơn:
[student@studentvm1 testdir]$ for Dept in "Human Resources" Sales Finance "Information Technology" Engineering Administration Research ; do echo "Department $Dept" ; done
Department Human Resources
Department Sales
Department Finance
Department Information Technology
Department Engineering
Department Administration
Department Research
Tạo thư mục (và hiển thị thông tin tiến trình):
[student@studentvm1 testdir]$ for Dept in "Human Resources" Sales Finance "Information Technology" Engineering Administration Research ; do echo "Working on Department $Dept" ; mkdir "$Dept" ; done
Working on Department Human Resources
Working on Department Sales
Working on Department Finance
Working on Department Information Technology
Working on Department Engineering
Working on Department Administration
Working on Department Research
[student@studentvm1 testdir]$ ll
total 28
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Administration
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Engineering
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Finance
drwxrwxr-x 2 student student 4096 Apr 8 15:45 'Human Resources'
drwxrwxr-x 2 student student 4096 Apr 8 15:45 'Information Technology'
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Research
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Sales
Biến $Dept phải trong ngoặc kép trong câu lệnh mkdir; nếu không, tên bộ phận hai từ (như "Information Technology") sẽ được đối xử như hai bộ phận riêng. Điều đó nhấn mạnh thực hành tốt tôi thích: tất cả tên file và thư mục nên là một từ. Dù hầu hết OS hiện đại xử lý được khoảng trắng trong tên, nhưng cần công việc thêm cho sysadmin đảm bảo trường hợp đặc biệt được xem xét trong script và chương trình CLI. (Chắc chắn nên xem xét, dù phiền vì bạn không bao giờ biết file nào bạn sẽ có.)
Vậy, xóa mọi thứ trong ~/testdir—lại—and làm thêm một lần:
[student@studentvm1 testdir]$ rm -rf * ; ll
total 0
[student@studentvm1 testdir]$ for Dept in Human-Resources Sales Finance Information-Technology Engineering Administration Research ; do echo "Working on Department $Dept" ; mkdir "$Dept" ; done
Working on Department Human-Resources
Working on Department Sales
Working on Department Finance
Working on Department Information-Technology
Working on Department Engineering
Working on Department Administration
Working on Department Research
[student@studentvm1 testdir]$ ll
total 28
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Administration
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Engineering
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Human-Resources
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Information-Technology
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Research
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Sales
Giả sử ai đó yêu cầu danh sách tất cả RPM trên máy Linux cụ thể và mô tả ngắn mỗi cái. Điều này xảy ra khi tôi làm cho Bang North Carolina. Vì mã nguồn mở không được "phê duyệt" dùng bởi cơ quan nhà nước lúc đó, và tôi chỉ dùng Linux trên desktop, các sếp tóc xù (PHBs) cần danh sách từng phần mềm cài trên máy tôi để họ "phê duyệt" ngoại lệ.
Bạn sẽ tiếp cận như thế nào? Đây là một cách, bắt đầu từ kiến thức lệnh rpm –qa cung cấp mô tả hoàn chỉnh RPM, bao gồm hai mục PHBs muốn: tên phần mềm và tóm tắt ngắn.
Xây dựng kết quả cuối từng bước. Trước, liệt kê tất cả RPM:
[student@studentvm1 testdir]$ rpm -qa
perl-HTTP-Message-6.18-3.fc29.noarch
perl-IO-1.39-427.fc29.x86_64
perl-Math-Complex-1.59-429.fc29.noarch
lua-5.3.5-2.fc29.x86_64
java-11-openjdk-headless-11.0.ea.28-2.fc29.x86_64
util-linux-2.32.1-1.fc29.x86_64
libreport-fedora-2.9.7-1.fc29.x86_64
rpcbind-1.2.5-0.fc29.x86_64
libsss_sudo-2.0.0-5.fc29.x86_64
libfontenc-1.1.3-9.fc29.x86_64
<snip>
Thêm lệnh sort và uniq sắp xếp danh sách và in cái duy nhất (vì có thể RPM trùng tên cài):
[student@studentvm1 testdir]$ rpm -qa | sort | uniq
a2ps-4.14-39.fc29.x86_64
aajohan-comfortaa-fonts-3.001-3.fc29.noarch
abattis-cantarell-fonts-0.111-1.fc29.noarch
abiword-3.0.2-13.fc29.x86_64
abrt-2.11.0-1.fc29.x86_64
abrt-addon-ccpp-2.11.0-1.fc29.x86_64
abrt-addon-coredump-helper-2.11.0-1.fc29.x86_64
abrt-addon-kerneloops-2.11.0-1.fc29.x86_64
abrt-addon-pstoreoops-2.11.0-1.fc29.x86_64
abrt-addon-vmcore-2.11.0-1.fc29.x86_64
<snip>
Vì đây cho danh sách RPM đúng bạn muốn xem, bạn dùng làm danh sách đầu vào vòng lặp in chi tiết mỗi RPM:
[student@studentvm1 testdir]$ for RPM in `rpm -qa | sort | uniq` ; do rpm -qi $RPM ; done
Mã này sinh ra dữ liệu nhiều hơn bạn muốn. Lưu ý vòng lặp hoàn chỉnh. Bước tiếp theo trích xuất chỉ thông tin PHBs yêu cầu. Vậy thêm lệnh egrep, chọn ^Name hoặc ^Summary. Dấu mũ (^) chỉ đầu dòng; do đó, bất kỳ dòng nào có Name hoặc Summary ở đầu dòng được hiển thị.
[student@studentvm1 testdir]$ for RPM in `rpm -qa | sort | uniq` ; do rpm -qi $RPM ; done | egrep -i "^Name|^Summary"
Name : a2ps
Summary : Converts text and other types of files to PostScript
Name : aajohan-comfortaa-fonts
Summary : Modern style true type font
Name : abattis-cantarell-fonts
Summary : Humanist sans serif font
Name : abiword
Summary : Word processing program
Name : abrt
Summary : Automatic bug detection and reporting tool
<snip>
Bạn thử grep thay egrep trong lệnh trên, nhưng sẽ không chạy. Bạn cũng có thể pipe đầu ra qua bộ lọc less khám phá kết quả. Chuỗi lệnh cuối trông như sau:
[student@studentvm1 testdir]$ for RPM in `rpm -qa | sort | uniq` ; do rpm -qi $RPM ; done | egrep -i "^Name|^Summary" > RPM-summary.txt
Chương trình dòng lệnh này dùng pipeline, redirection, và vòng lặp for—trên một dòng. Nó chuyển hướng đầu ra chương trình CLI nhỏ vào file dùng cho email hoặc đầu vào cho mục đích khác.
Quá trình xây dựng chương trình từng bước cho phép xem kết quả mỗi bước và đảm bảo nó chạy như kỳ vọng và cho kết quả mong muốn.
Từ bài tập này, PHBs nhận danh sách hơn 1.900 gói RPM riêng biệt. Tôi nghiêm trọng nghi ngờ ai đọc danh sách đó. Nhưng tôi cho họ chính xác thứ họ hỏi, và không bao giờ nghe phàn nàn thêm từ họ.
Các vòng lặp khác
Có hai loại cấu trúc vòng lặp khác trong Bash: cấu trúc while và until, rất giống nhau về cú pháp và chức năng. Cú pháp cơ bản của cấu trúc vòng lặp này đơn giản:
while [ expression ] ; do list ; done
và
until [ expression ] ; do list ; done
Logic cái đầu đọc: "Khi biểu thức đánh giá true, thực thi danh sách câu lệnh chương trình. Khi biểu thức đánh giá false, thoát vòng lặp." Và cái sau: "Cho đến khi biểu thức đánh giá true, thực thi danh sách câu lệnh. Khi biểu thức đánh giá true, thoát vòng lặp."
Vòng lặp while
Vòng lặp while dùng thực thi chuỗi câu lệnh chương trình while (miễn là) biểu thức logic đánh giá true. PWD của bạn nên vẫn là ~/testdir.
Đơn giản nhất while là vòng lặp chạy mãi. Dạng sau dùng câu lệnh true luôn sinh mã trả về "true". Bạn cũng có thể dùng đơn giản "1"—và chạy y hệt—but this illustrates the use of the true statement:
[student@studentvm1 testdir]$ X=0 ; while [ true ] ; do echo $X ; X=$((X+1)) ; done | head
0
1
2
3
4
5
6
7
8
9
[student@studentvm1 testdir]$
Chương trình CLI này nên có ý nghĩa hơn bây giờ bạn đã học các phần. Trước, đặt $X thành 0 phòng giá trị thừa từ chương trình hoặc lệnh CLI trước. Sau đó, vì biểu thức logic [ true ] luôn đánh giá 1, là true, danh sách chỉ dẫn giữa do và done thực thi mãi—hoặc cho đến khi bạn nhấn Ctrl+C hoặc gửi signal 2. Những chỉ dẫn đó là mở rộng số học in giá trị hiện tại $X rồi tăng 1.
Một trong mười điều cốt lõi của The Linux Philosophy for Sysadmins là phấn đấu sự thanh lịch, và một cách đạt được thanh lịch là đơn giản. Bạn có thể đơn giản chương trình bằng toán tử tăng biến, ++. Trong trường hợp đầu, giá trị hiện tại biến in ra, rồi biến tăng. Điều này chỉ bằng đặt toán tử ++ sau biến:
[student@studentvm1 ~]$ X=0 ; while [ true ] ; do echo $((X++)) ; done | head
0
1
2
3
4
5
6
7
8
9
Bây giờ xóa | head từ cuối chương trình và chạy lại.
Trong phiên bản này, biến tăng trước khi giá trị in. Điều này chỉ bằng đặt toán tử ++ trước biến. Bạn thấy khác biệt?
[student@studentvm1 ~]$ X=0 ; while [ true ] ; do echo $((++X)) ; done | head
1
2
3
4
5
6
7
8
9
Bạn giảm hai câu lệnh thành một in giá trị biến và tăng giá trị đó. Cũng có toán tử giảm, --.
Bạn cần phương pháp dừng vòng lặp ở số cụ thể. Để làm đó, thay biểu thức true bằng biểu thức đánh giá số thực. Cho chương trình lặp đến 5 và dừng. Trong code ví dụ dưới, bạn thấy -le là toán tử logic số "less than or equal to." Nghĩa là: "Miễn $X nhỏ hơn hoặc bằng 5, vòng lặp tiếp tục. Khi $X tăng thành 6, vòng lặp kết thúc."
[student@studentvm1 ~]$ X=0 ; while [ $X -le 5 ] ; do echo $((X++)) ; done
0
1
2
3
4
5
[student@studentvm1 ~]$
Vòng lặp until
Lệnh until rất giống while. Khác biệt là nó tiếp tục lặp cho đến khi biểu thức logic đánh giá "true." Xem dạng đơn giản cấu trúc này:
[student@studentvm1 ~]$ X=0 ; until false ; do echo $((X++)) ; done | head
0
1
2
3
4
5
6
7
8
9
[student@studentvm1 ~]$
Nó dùng so sánh logic đếm đến giá trị cụ thể:
[student@studentvm1 ~]$ X=0 ; until [ $X -eq 5 ] ; do echo $((X++)) ; done
0
1
2
3
4
[student@studentvm1 ~]$ X=0 ; until [ $X -eq 5 ] ; do echo $((++X)) ; done
1
2
3
4
5
[student@studentvm1 ~]$
Tóm tắt
Serie này khám phá nhiều công cụ mạnh xây dựng chương trình dòng lệnh Bash và shell script. Nhưng mới chỉ xẻng bề mặt những điều thú vị bạn làm với Bash; phần còn lại thuộc về bạn.
Tôi khám phá cách tốt nhất học lập trình Bash là làm nó. Tìm dự án đơn giản yêu cầu nhiều lệnh Bash và làm chương trình CLI từ chúng. Sysadmin làm nhiều tác vụ phù hợp lập trình CLI, nên tôi chắc bạn dễ dàng tìm tác vụ tự động hóa.
Nhiều năm trước, dù quen shell khác và Perl, tôi quyết định dùng Bash cho tất cả tác vụ tự động hóa sysadmin. Tôi khám phá—thỉnh thoảng với chút tìm kiếm—tôi dùng Bash hoàn thành mọi thứ cần.