Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Lập Trình Bash

Lập trình Bash Script chuyên sâu: Cú pháp, Công cụ & Best Practices

Shell là trình thông dịch lệnh cho hệ điều hành. Bash là shell yêu thích của tác giả, nhưng mọi shell Linux đều thông dịch lệnh do người dùng hoặc sysadmin gõ thành dạng hệ điều hành có thể dùng. Khi kết quả trả về cho chương trình shell, nó gửi chúng đến STDOUT mà theo mặc định hiển thị trong terminal. Tất cả shell mà tác giả biết đều cũng là ngôn ngữ lập trình.

Các tính năng như hoàn thành tab, nhớ và chỉnh sửa dòng lệnh, và lối tắt như alias đều góp phần vào giá trị của nó như một shell mạnh mẽ. Chế độ chỉnh sửa dòng lệnh mặc định dùng Emacs, nhưng một trong những tính năng Bash yêu thích của tác giả là có thể đổi sang chế độ Vi để dùng các lệnh chỉnh sửa đã trở thành memory cơ bắp.

Tuy nhiên, nếu bạn nghĩ Bash chỉ là một shell, bạn bỏ lỡ phần lớn sức mạnh thực sự của nó. Khi nghiên cứu cho khóa tự học Linux ba tập (chiến lược bài viết này dựa trên đó), tác giả học được điều về Bash chưa bao giờ biết trong hơn 20 năm làm việc với Linux. Một số kiến thức mới liên quan đến việc dùng nó như ngôn ngữ lập trình. Bash là ngôn ngữ lập trình mạnh mẽ, thiết kế hoàn hảo cho dòng lệnh và script shell.

Chuỗi ba phần này khám phá việc dùng Bash như ngôn ngữ lập trình giao diện dòng lệnh (CLI). Bài viết đầu tiên xem xét một số lập trình dòng lệnh đơn giản với Bash, biến và toán tử điều khiển. Các bài khác khám phá các loại file Bash; toán tử logic chuỗi, số, hỗn hợp cung cấp logic điều khiển luồng thực thi; các loại mở rộng shell khác nhau; và vòng lặp for, while, until cho phép thao tác lặp lại. Chúng cũng sẽ xem một số lệnh đơn giản hóa và hỗ trợ dùng các công cụ này.

Shell

Shell là trình thông dịch lệnh cho hệ điều hành. Bash là shell yêu thích của tác giả, nhưng mọi shell Linux đều thông dịch lệnh do người dùng hoặc sysadmin gõ thành dạng hệ điều hành có thể dùng. Khi kết quả trả về cho chương trình shell, nó hiển thị chúng trong terminal. Tất cả shell mà tác giả biết đều cũng là ngôn ngữ lập trình.

Bash là viết tắt của Bourne Again Shell vì shell Bash dựa trên shell Bourne cũ hơn do Steven Bourne viết năm 1977. Nhiều shell khác có sẵn, nhưng đây là bốn shell tác giả gặp nhiều nhất:

  • csh: C shell cho lập trình viên thích cú pháp ngôn ngữ C
  • ksh: Korn shell, do David Korn viết, phổ biến với người dùng Unix
  • tcsh: Phiên bản csh với nhiều tính năng dễ dùng hơn
  • zsh: Z shell, kết hợp nhiều tính năng của các shell phổ biến khác

Tất cả shell đều có lệnh built-in bổ sung hoặc thay thế lệnh core utilities. Mở man page của shell và tìm mục "BUILT-INS" để xem các lệnh nó cung cấp.

Mỗi shell có cá tính và cú pháp riêng. Một số sẽ hoạt động tốt hơn cho bạn. Tác giả đã dùng C shell, Korn shell, Z shell. Vẫn thích Bash shell hơn tất cả. Dùng shell phù hợp nhất cho bạn, dù có thể cần thử một số shell khác. May mắn là đổi shell khá dễ dàng.

Tất cả các shell này là ngôn ngữ lập trình, cũng như trình thông dịch lệnh. Dưới đây là tour nhanh về một số cấu trúc và công cụ lập trình là phần không tách rời của Bash.

Bash như ngôn ngữ lập trình

Hầu hết sysadmin đã dùng Bash để đưa ra lệnh thường khá đơn giản và trực tiếp. Nhưng Bash có thể vượt quá việc nhập lệnh đơn lẻ, và nhiều sysadmin tạo chương trình dòng lệnh đơn giản để thực hiện chuỗi tác vụ. Những chương trình này là công cụ phổ biến tiết kiệm thời gian và công sức.

Mục tiêu của tác giả khi viết chương trình CLI là tiết kiệm thời gian và công sức (tức là trở thành sysadmin "lười"). Chương trình CLI hỗ trợ điều này bằng cách liệt kê một số lệnh theo trình tự cụ thể thực thi lần lượt, nên bạn không cần xem tiến trình lệnh này và gõ lệnh tiếp theo khi lệnh trước xong. Bạn có thể làm việc khác và không phải liên tục theo dõi tiến trình từng lệnh.

Chương trình là gì?

Từ điển điện toán miễn phí (FOLDOC) định nghĩa chương trình: "Các hướng dẫn được máy tính thực thi, trái ngược với thiết bị vật lý chúng chạy trên đó." WordNet của Princeton định nghĩa: "…một chuỗi hướng dẫn mà máy tính có thể thông dịch và thực thi…" Wikipedia cũng có mục tốt về chương trình máy tính.

Do đó, chương trình có thể bao gồm một hoặc nhiều hướng dẫn thực hiện tác vụ cụ thể, liên quan. Hướng dẫn chương trình máy tính cũng gọi là câu lệnh chương trình. Đối với sysadmin, chương trình thường là chuỗi lệnh shell. Tất cả shell có sẵn cho Linux, ít nhất những shell tác giả biết, đều có ít nhất dạng lập trình cơ bản, và Bash, shell mặc định cho hầu hết distro Linux, không ngoại lệ.

Mặc dù chuỗi này dùng Bash (vì nó phổ biến), nếu bạn dùng shell khác, khái niệm lập trình chung sẽ giống nhau, dù cấu trúc và cú pháp có thể khác một chút. Một số shell có thể hỗ trợ tính năng khác không có, nhưng tất cả cung cấp khả năng lập trình. Chương trình shell có thể lưu trong file để dùng lại, hoặc tạo trên dòng lệnh khi cần.

Chương trình CLI đơn giản

Chương trình dòng lệnh đơn giản nhất là một hoặc hai câu lệnh chương trình liên tiếp, có thể liên quan hoặc không, được nhập trên dòng lệnh trước khi nhấn Enter. Câu lệnh thứ hai trong chương trình, nếu có, có thể phụ thuộc vào hành động của cái đầu, nhưng không bắt buộc.

Cũng có một dấu câu cú pháp cần nêu rõ. Khi nhập một lệnh đơn lẻ trên dòng lệnh, nhấn Enter kết thúc lệnh với dấu chấm phẩy ngầm (;). Khi dùng trong chương trình shell CLI nhập trên một dòng, dấu chấm phẩy phải dùng để kết thúc mỗi câu lệnh và tách nó khỏi cái tiếp theo. Câu lệnh cuối trong chương trình CLI shell có thể dùng dấu chấm phẩy rõ ràng hoặc ngầm.

Một số cú pháp cơ bản

Các ví dụ sau sẽ làm rõ cú pháp này. Chương trình này bao gồm một lệnh đơn lẻ với bộ kết thúc rõ ràng:

[student@studentvm1 ~]$ echo "Hello world." ;
Hello world.

Điều đó có vẻ không phải chương trình gì, nhưng đó là chương trình đầu tiên tác giả gặp với mọi ngôn ngữ lập trình mới học. Cú pháp có thể hơi khác cho mỗi ngôn ngữ, nhưng kết quả giống nhau.

Mở rộng một chút chương trình tầm thường nhưng phổ biến này. Kết quả của bạn sẽ khác của tác giả vì tác giả đã làm thí nghiệm khác, trong khi bạn có thể chỉ có thư mục và file mặc định tạo trong tài khoản home directory lần đầu đăng nhập qua GUI desktop.

[student@studentvm1 ~]$ echo "My home directory." ; ls ;
My home directory.
chapter25 TestFile1.Linux dmesg2.txt Downloads newfile.txt softlink1 testdir6
chapter26 TestFile1.mac dmesg3.txt file005 Pictures Templates testdir
TestFile1 Desktop dmesg.txt link3 Public testdir Videos
TestFile1.dos dmesg1.txt Documents Music random.txt testdir1

Điều đó hợp lý hơn một chút. Kết quả liên quan, nhưng các câu lệnh chương trình độc lập nhau. Lưu ý tác giả thích đặt khoảng trắng trước và sau dấu chấm phẩy vì làm mã dễ đọc hơn. Thử chương trình CLI nhỏ đó lại không có dấu chấm phẩy rõ ràng ở cuối:

[student@studentvm1 ~]$ echo "My home directory." ; ls 

Không có khác biệt trong đầu ra.

Về biến (Variables)

Giống như mọi ngôn ngữ lập trình, shell Bash có thể xử lý biến. Biến là tên tượng trưng tham chiếu đến vị trí bộ nhớ cụ thể chứa giá trị nào đó. Giá trị của biến có thể thay đổi, tức là nó biến đổi.

Bash không kiểu biến như C và ngôn ngữ liên quan, định nghĩa chúng là integer, floating point, hoặc kiểu chuỗi. Trong Bash, tất cả biến là chuỗi. Chuỗi là số nguyên có thể dùng trong toán học nguyên, đây là loại toán học duy nhất Bash có thể làm. Nếu cần toán học phức tạp hơn, lệnh bc có thể dùng trong chương trình CLI và script.

Biến được gán giá trị và có thể dùng để tham chiếu đến giá trị đó trong chương trình CLI và script. Giá trị biến được thiết lập dùng tên biến nhưng không có dấu $ ở trước. Gán VAR=10 thiết lập giá trị biến VAR thành 10. Để in giá trị biến, dùng câu lệnh echo $VAR. Bắt đầu với biến văn bản (phi số).

Biến Bash trở thành một phần của môi trường shell cho đến khi bị unset.

Kiểm tra giá trị ban đầu của biến chưa gán; nó nên null. Sau đó gán giá trị cho biến và in nó để kiểm tra giá trị. Bạn có thể làm tất cả trong một chương trình CLI đơn lẻ:

[student@studentvm1 ~]$ echo $MyVar ; MyVar="Hello World" ; echo $MyVar ;

Hello World
[student@studentvm1 ~]$

Lưu ý: Cú pháp gán biến rất nghiêm ngặt. Không được có khoảng trắng hai bên dấu bằng (=) trong câu lệnh gán.

Dòng trống chỉ ra giá trị ban đầu của MyVar là null. Thay đổi và thiết lập giá trị biến làm giống nhau. Ví dụ này cho thấy cả giá trị gốc và mới.

Như đã nói, Bash có thể thực hiện phép tính số nguyên, hữu ích cho tính tham chiếu vị trí phần tử trong mảng hoặc bài toán toán học đơn giản. Nó không phù hợp cho tính toán khoa học hoặc bất cứ thứ gì cần thập phân, như tính toán tài chính. Có công cụ tốt hơn cho loại tính toán đó.

Đây là phép tính đơn giản:

[student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var1*Var2))"
Result = 63

Điều gì xảy ra khi thực hiện phép toán cho kết quả số thực?

[student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var1/Var2))"
Result = 0
[student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var2/Var1))"
Result = 1
[student@studentvm1 ~]$

Kết quả là số nguyên gần nhất. Lưu ý phép tính được thực hiện như một phần của câu lệnh echo. Toán học được thực hiện trước lệnh echo bao quanh do thứ tự ưu tiên Bash. Xem chi tiết trong man page Bash tìm "precedence."

Toán tử điều khiển (Control operators)

Toán tử điều khiển shell là một trong các toán tử cú pháp để dễ dàng tạo một số chương trình dòng lệnh thú vị. Dạng đơn giản nhất của chương trình CLI chỉ là nối một vài lệnh theo trình tự trên dòng lệnh:

command1 ; command2 ; command3 ; command4 ; . . . ; etc. ;

Những lệnh đó đều chạy không vấn đề miễn không có lỗi xảy ra. Nhưng điều gì xảy ra khi lỗi xảy ra? Bạn có thể dự đoán và cho phép lỗi dùng toán tử điều khiển built-in && và || của Bash. Hai toán tử điều khiển này cung cấp một số điều khiển luồng và cho phép bạn thay đổi trình tự thực thi mã. Dấu chấm phẩy cũng được coi là toán tử điều khiển Bash, cũng như ký tự xuống dòng.

Toán tử && đơn giản nói: "nếu command1 thành công, thì chạy command2. Nếu command1 thất bại vì bất kỳ lý do nào, thì command2 bị bỏ qua." Cú pháp trông như sau:

command1 && command2

Bây giờ, xem một số lệnh sẽ tạo thư mục mới và—nếu thành công—làm nó trở thành thư mục làm việc hiện tại (PWD). Đảm bảo thư mục home (~) là PWD. Thử trước trong /root, thư mục bạn không có quyền truy cập:

[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir/ && cd $Dir
mkdir: cannot create directory '/root/testdir/': Permission denied
[student@studentvm1 ~]$

Lỗi được phát ra bởi lệnh mkdir. Bạn không nhận được lỗi chỉ ra file không thể tạo vì tạo thư mục thất bại. Toán tử điều khiển && cảm nhận mã trả về non-zero, nên lệnh touch bị bỏ qua. Sử dụng toán tử điều khiển && ngăn lệnh touch chạy vì có lỗi khi tạo thư mục. Loại điều khiển luồng chương trình dòng lệnh này có thể ngăn lỗi tích lũy và làm bể bở mọi thứ. Nhưng đã đến lúc phức tạp hơn một chút.

Toán tử điều khiển || cho phép bạn thêm câu lệnh chương trình khác thực thi khi câu lệnh ban đầu trả về mã lớn hơn không. Cú pháp cơ bản trông như sau:

command1 || command2 

Cú pháp này đọc: "Nếu command1 thất bại, thực thi command2." Điều đó ngụ ý nếu command1 thành công, command2 bị bỏ qua. Thử bằng cách cố tạo thư mục mới:

[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir || echo "$Dir was not created."
mkdir: cannot create directory '/root/testdir': Permission denied
/root/testdir was not created.
[student@studentvm1 ~]$

Đây chính xác là điều bạn mong đợi. Vì thư mục mới không thể tạo, lệnh đầu tiên thất bại, dẫn đến thực thi lệnh thứ hai.

Kết hợp hai toán tử này cung cấp cái tốt nhất của cả hai. Cú pháp toán tử điều khiển dùng một số điều khiển luồng có dạng chung sau khi dùng toán tử điều khiển && và ||:

preceding commands ; command1 && command2 || command3 ; following commands

Cú pháp này có thể nêu: "Nếu command1 thoát với mã trả về 0, thì thực thi command2, ngược lại thực thi command3." Thử nó:

[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir && cd $Dir || echo "$Dir was not created."
mkdir: cannot create directory '/root/testdir': Permission denied
/root/testdir was not created.
[student@studentvm1 ~]$

Bây giờ thử lệnh cuối cùng lại dùng thư mục home thay vì thư mục /root. Bạn sẽ có quyền tạo thư mục này:

[student@studentvm1 ~]$ Dir=~/testdir ; mkdir $Dir && cd $Dir || echo "$Dir was not created."
[student@studentvm1 testdir]$

Cú pháp toán tử điều khiển, như command1 && command2, hoạt động vì mọi lệnh gửi mã trả về (RC) cho shell chỉ ra nó hoàn thành thành công hay có thất bại trong thực thi. Theo quy ước, RC bằng không (0) chỉ ra thành công, và bất kỳ số dương nào chỉ ra loại thất bại nào đó. Một số công cụ sysadmin dùng chỉ trả về 1 để chỉ thất bại, nhưng nhiều dùng mã khác để chỉ loại thất bại xảy ra.

Biến shell Bash $? chứa RC từ lệnh cuối. RC này có thể kiểm tra rất dễ dàng bởi script, lệnh tiếp theo trong danh sách lệnh, hoặc thậm chí trực tiếp bởi sysadmin. Bắt đầu bằng chạy lệnh đơn giản và ngay lập tức kiểm tra RC. RC sẽ luôn là của lệnh cuối chạy trước khi bạn nhìn nó.

[student@studentvm1 testdir]$ ll ; echo "RC = $?"
total 1264
drwxrwxr-x 2 student student 4096 Mar 2 08:21 chapter25
drwxrwxr-x 2 student student 4096 Mar 21 15:27 chapter26
-rwxr-xr-x 1 student student 92 Mar 20 15:53 TestFile1
<snip>
drwxrwxr-x. 2 student student 663552 Feb 21 14:12 testdir
drwxr-xr-x. 2 student student 4096 Dec 22 13:15 Videos
RC = 0
[student@studentvm1 testdir]$

RC, trong trường hợp này, là 0, nghĩa là lệnh hoàn thành thành công. Bây giờ thử cùng lệnh trên thư mục home root, thư mục bạn không có quyền:

[student@studentvm1 testdir]$ ll /root ; echo "RC = $?"
ls: cannot open directory '/root': Permission denied
RC = 2
[student@studentvm1 testdir]$

Trong trường hợp này, RC là 2; nghĩa là quyền bị từ chối cho user non-root truy cập thư mục user không được phép truy cập. Toán tử điều khiển dùng các RC này để cho phép bạn thay đổi trình tự thực thi chương trình.

Tóm tắt

Bài viết này xem xét Bash như ngôn ngữ lập trình và khám phá cú pháp cơ bản cũng như một số công cụ cơ bản. Nó cho thấy cách in dữ liệu ra STDOUT và cách dùng biến và toán tử điều khiển. Bài viết tiếp theo trong chuỗi này xem xét một số trong nhiều toán tử logic Bash điều khiển luồng thực thi hướng dẫn.