Viết Shell script trên Linux/Unix

    Khi lập trình trên nền tảng hệ điều hành Linux/Unix chúng ta thường phải sử dụng Shell script rất nhiều. Bài viết này được viết để giúp các ae mới làm quen với Linux/Unix có thể tiếp cận và hiểu được một số điều cơ bản của lập trình Shell script (còn goi là shell scripting).

Shell script là gì ?

Một Shell script là một file có chứa một loạt các lệnh được liệt kê theo thứ tự thực thi. Trong một Shell script thì ngoài các dòng lệnh thường có thêm các dòng chú thích – comment, các dòng comment bắt đầu bởi dấu #.

Chương trình Shell của hệ điều hành sẽ đọc Shell script và thực thi các lệnh trong đó giống như chúng được nhập trực tiếp trên cửa sổ dòng lệnh (terminal). Hầu hết những công việc có thể thực hiện được bằng dòng lệnh trên cửa sổ lệnh đều có thể được thực hiện thông qua Shell script, và ngược lại hầu hết những công việc mà có thể được thực hiện bằng Shell script đều có thể được thực hiện bằng dòng lệnh.

Các bước để viết và chạy Shell script

  1. Sử dụng bất kỳ trình soạn thảo nào như: vi, vim, nano, gedit… để viết Shell script
  2. Sau khi viết Shell script thì thiết lập quyền thực thi cho nó theo cấu trúc lệnh:

chmod permission script-name

Ví dụ →

  1. Thực thi script bằng 1 trong các cấu trúc lệnh sau:

bash script-name

hoặc

sh script-name

hoặc

./script-name

Ví dụ →

Muốn tạo một Shell script để in “Hello world” lên màn hình ta làm như sau:

Sau khi lưu file, thay đổi quyền truy cập cho file

Và chạy script này như sau:

Chú ý: Shell script file nên có đuôi là .sh để dễ phân biệt đó là shell script.

Các biến trong Shell script

Trong Linux/Unix Shell, có hai loại biến:

  1. Biến hệ thống – được tạo và duy trì bởi chính hệ điều hành. Loại biến này được định nghĩa bằng các ký tự hoa.
  2. Biến do người dùng định nghĩa (UDV – User Defined variables) – được tạo và duy trì bởi người dùng. Loại biến này được định nghĩa bằng các ký tự thường.

Bạn có thể xem danh sách các biến hệ thống bằng lệnh set hoặc env. Một vài biến hệ thống quan trọng:

BASH=/bin/bash – tên shell.

BASH_VERSION=1.14.7(1) – phiên bản của shell.

COLUMNS=80 – số cột cho màn hình.

HOME=/home/tuanpm – thư mục home.

LINES=25 – số dòng của màn hình.

LOGNAME=tuanpm – tên đăng nhập của người dùng.

OSTYPE=Linux – Loại hệ điều hành.

PATH=/usr/bin:/sbin:/bin:/usr/sbin – Thiết lập đường dẫn.

PS1=[\u@\h \W]\$ – thiết lập prompt.

PWD=/home/tuanpm/working – thư mục hiện hành.

SHELL=/bin/bash – tên shell.

USERNAME=tuanpm – user nào hiện đang đăng nhập vào PC.

==> Bạn có thể in ra bất kỳ biến môi trường nào bằng lệnh echo như sau:

Làm thế nào để định nghĩa UDV ?

* Sử dụng cấu trúc sau:

variable_name=value

Lệnh trên có nghĩa là giá trị value được gán cho biến variable_name. Ví dụ →

* Một số nguyên tắc đặt tên biến và gán giá trị cho biến (áp dụng cho cả UDV và biến hệ thống):

  1. Biến phải bắt đầu bằng ký tự alphanumeric hoặc underscore (_), theo sau bởi một hoặc nhiều ký tự alphanumeric.

Ví dụ, các tên biến hợp lệ: HOME, SYSTEM_VERSION, vech, no…

  1. Không có khoảng trắng giữa hai bên dấu bằng khi gán giá trị biến.

Ví dụ, các khai báo sau sẽ có lỗi →

  1. Phân biệt chữ hoa và thường.

Ví dụ, các biến sau là các biến khác nhau →

  1. Bạn có thể định nghĩa biến NULL như sau:

Khi in ra các biến NULL này, thì không có gì trên màn hình bởi vì chúng không có giá trị.

  1. Không sử dụng ?, *…để đặt tên cho biến.

* In và truy cập giá trị của UDV

$variablename

hoặc

echo $variablename

Tính toán trong Shell script

Sử dụng các phép toán số học sau theo cấu trúc sau:

expr parameter1 math-operator parameter2

Ví dụ →

Trích dẫn (quote)

Có 3 loại quote trong Shell script:

  • single quote:

Single quote được sử dụng để bao xung quanh đoạn text để ngăn không cho Shell biên dịch bất kỳ ký tự đặc biệt nào trong đó. Các dấu $, dấu cách, & , * và các ký tự đặc biệt khác đều được giữ nguyên khi được đóng trong dấu single quote.

  • double quote:

Double quote giống như single quote, ngoại trừ việc double quote vẫn cho phép Shell biên dịch các dấu $, ” và dấu \.

  • back quote:

Back quote hoàn toàn không giống như single quote và double quote. Nó không có tác dụng ngăn chặn việc biên dịch các ký tự đặc biệt, thay vào đó back quote sẽ thực thi các lệnh đặt bên trong nó. Sau khi các lệnh đó được thực thi, kết quả của chúng sẽ được thay thế vào chỗ của back quote trong dòng lệnh gốc.

Điều này sẽ rõ ràng hơn với một vài ví dụ sau →

=> in ra: $PATH

=> in ra: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

=> in ra: Today is Fri Feb 2 16:30:58 CET 2018.

Trạng thái kết thúc (exit status)

Mặc định trong Linux nếu lệnh/shell script được thực thi, nó sẽ trả về hai loại giá trị – được sử dụng để xem lệnh/shell script được thực thi thành công hay không.

  1. Trả về giá trị 0: thành công.
  2. Trả về giá trị khác 0: không thành công hoặc có một vài lỗi khi thực thi lệnh/shell script.

=> giá trị này gọi exit status.

Ví dụ → Nếu unknow1file không tồn tại trong đĩa cứng thì lệnh “rm unknow1file” sẽ in ra lỗi như sau:

rm: cannot remove `unkowm1file’: No such file or directory.

Và sau đó nếu bạn gõ lệnh $ echo $? thì nó sẽ in ra giá trị khác 0 để chỉ ra rằng có lỗi.

Đọc dữ liệu từ bàn phím

Để nhận dữ liệu từ bàn phím và lưu vào biến, ta sử dụng cú pháp sau:

read variable1, variable2, …,variableN

Ví dụ, script sau sẽ hỏi tên người dùng và chờ họ nhập vào từ bàn phím. Sau khi người dùng nhập tên và nhấn Enter thì tên sẽ được lưu trong biến name →

Chạy script:

Wild cards

  1. *: bất kỳ chuỗi hoặc nhóm ký tự nào.

Ví dụ →

$ ls * – hiển thị tất cả các file.

$ ls a* – hiển thị tất cả các file mà tên của nó bắt đầu bằng ký tự ‘a’.

$ ls *.c – hiển thị tất cả các file có bằng mở rộng là .c

  1. ?: bất kỳ 1 ký tự nào.

Ví dụ →

$ ls ? – hiển thị tất cả các file mà tên của nó có chiều dài 1 ký tự.

$ ls fo? – hiển thị tất cả các file mà tên của nó có 3 ký tự và tên file bắt đầu bằng fo.

  1. […]: bất kỳ 1 trong các ký tự trong dấu ngoặc.

Ví dụ →

$ ls [abc]* – thể hiện tất cả các file bắt đầu với mẫu tự a, b, c.

  1. […-…]: Một cặp các ký tự được phân chia bởi dấu trừ để ghi nhận một dãy.

Ví dụ →

$ ls /bin/[a-c]* – hiển thị tất cả các file bắt đầu với mẫu tự a, b, c.

Chú ý: Nếu ký tự đầu tiên theo sau [ là ! hoặc ^, thì bất kỳ ký tự nào không phù hợp sẽ được hiển thị.

$ ls /bin/[!a-o]

$ ls /bin/[^a-o]

=> sẽ hiển thị tất cả các file trong thư mục bin mà ký tự đầu tiên không phải là a, b, c…o.

Nhiều lệnh trên một dòng lệnh

command1;command2

Ví dụ →

sẽ in ra ngày hiện tại theo sau là tên người dùng đăng nhập hiện tại.

Nhập xuất dữ liệu sử dụng file

Hầu hết các lệnh đều kết xuất ra màn hình hoặc nhận tham số từ bàn phím nhưng chúng ta cũng có thể xuất dữ liệu ra file hoặc đọc dữ liệu từ file.

1) Xuất dữ liệu ra file sử dụng ‘>’

command > filename

xuất kết quả của lệnh ra file có tên là filename. Nếu như filename tồn tại, nó sẽ ghi đè lên, ngược lại file mới sẽ được tạo.

Ví dụ →

2) Xuất dữ liệu ra file sử dụng ‘>>’

command >> filename

xuất kết quả vào phần cuối của file có tên là filename. Nếu như filename tồn tại, nó sẽ được mở và kết quả của command sẽ được ghi vào cuối file, không sợ mất dữ liệu/thông tin trước. Và nếu filename không tồn tại, thì file mới được tạo.

Ví dụ →

3) Nhập dữ liệu từ file sử dụng ‘<‘

command < filename

đọc dữ liệu từ file thay vì bàn phím.

Ví dụ →

Filter và Pipe

Bạn có thể nối hai lệnh với nhau để đầu ra của lệnh này là đầu vào của lệnh tiếp theo. Hai hoặc nhiều lệnh được kết nối trong cách này tạo thành một Pipe. Để tạo một pipe, đặt ký hiệu | giữa hai lệnh trên dòng lệnh. Khi một lệnh nhận đầu vào từ một lệnh khác, thực hiện một vài xử lý trên đầu vào đó, và ghi kết quả ra thì nó được gọi là một Filter.

Một số filter phổ biến:
Filter Chức năng
head In ra n dòng dữ liệu đầu tiên.
tail In ra n dòng dữ liệu từ dưới lên.
sort Sắp xếp dữ liệu.
nl Hiện thị dòng dữ liệu kèm số thứ tự dòng.
wc Đếm ký tự, từ, dòng, byte.
cut Cắt dữ liệu.
sed Tìm kiếm và thay thế dữ liệu.
uniq Bỏ dòng trùng lặp.
tac Hiển thị dữ liệu từ dưới lên. Ngược lại so với cat.

Ví dụ →

=> Ở đây lệnh grep là một filter nhận input từ lệnh ls. Kết quả là dòng lệnh trên sẽ liệt kê các tên file và thư mục có chứa từ “php”

Process

Tiến trình là một chương trình (lệnh) để thực thi một công việc xác định. Trong Linux khi bạn khởi tạo một tiến trình, nó sẽ cho tiến trình đó một con số gọi là process-id (PID) , PID khởi đầu từ 0 đến 65535.

Ví dụ →

lệnh ls là một tiến trình, nó sẽ liệt kê các file trong thư mục hoặc tất cả thư mục con trong thư mục hiện hành.

Một số lênh khác liên quan đến process:

  • ps – xem tiến trình đang chạy.
  • kill – dừng bất kỳ tiến trình nào thông qua PID của nó.
  • killall – dừng tiến trình thông qua tên của nó.
  • ps -ag – lấy thông tin về tất cả các tiến trình đang chạy.
  • kill 0 – dừng tất cả các tiến trình trừ shell của bạn.
  • command & – chạy lệnh ở background.
  • ps aux – hiển thị chủ sở hữu của các tiến trình cùng với các tiến trình đó.
  • ps ax | grep process_id – xem từng tiến trình có id là  process_id đang chạy hay không chạy.
  • top – xem tiến trình đang chạy và các thông tin khác như bộ nhớ, CPU usage cùng với thời gian thực.
  • pstree – hiển thị cây các tiến trình.

— Sưu tầm —