Bài viết này mục đích sưu tầm những kiến thức cơ bản cần biết để có thể viết được một vài script đơn giản. Hi vọng bài này sẽ giúp ích cho những ai mới bắt đầu học viết shellscript trên linux. Do trình mình cũng hơi chuối nên có thể sẽ sai sót, mong các bạn thông cảm nhé ^^
Biến đặc biệt
Biến đặc biệt | Ý nghĩa |
$0 | Chứa tên file script hiện tại |
$n ($1, $2,..$n) | Với n có thể là 1, 2,… tương ứng với các tham số đưa vào khi gọi script
VD: ./test.sh so01 so02 so03 Như VD trên thì có 3 tham số được đưa vào là so01, so02, so03 tương ứng với $1, $2, $3. |
$# | Tổng số tham số đã cung cấp cho script. Dựa theo VD trên thì kết quả khi dùng biến này sẽ ra 3 |
$* | Chứa tất cả tham số được đưa vào script. Nếu script nhận 3 tham số thì giá trị sẽ là $1 $2 $3 khi sử dụng |
[email protected] | Chứa tất cả tham số nhưng phân biệt thành những tham số riêng lẽ không như $* được đưa vào script. Nếu script nhận 3 tham số thì giá trị sẽ là $1 $2 $3 khi sử dụng |
$? | Trạng thái thoát ra của lệnh trước được chạy (thường la 0 đại diện cho lệnh trước chạy thành công, khác 0 là failed) Max range [0 – 255] |
$$ | Số tiến trình của shell hiện tại. Đối với Shell script đây là số ProcessID mà chúng đang chạy |
$! | Số tiến trình của lệnh background trước |
Sự khác nhau giữa $* và [email protected]
Về cơ bản thì $* và [email protected] giống nhau khi sử dụng với biến đợn không đặt trong dấu “”. Khi đặt vào trong dấu nháy kép “” thì:
- $*: Các tham số đưa vào sẽ được nối thành một chuỗi và được phân cách nhau bởi dấu cách.
- [email protected]: Các tham số đưa vào sẽ được phân biệt một cách riêng lẽ từng tham số một.
VD:
#!/bin/bash echo -e "\nUsing \"\$*\":" for str in "$*" do echo $str done echo -e "\nUsing \"\[email protected]\":" for str in "[email protected]" do echo $str done
Kết quả khi chạy script trên sẽ là: ./test.sh my name is Dang
Using "$*": my name is Dang
Khi sử dụng “$*” thì các tham số được xem là một chuỗi phân biệt bởi khoảng trắng
Using "[email protected]": my name is Dang
Khi sử dụng “[email protected]” thì các tham số được xem như các biến riêng lẽ được đặt trong một mảng
Mảng
Cú pháp khai báo:
<tên mảng>=(giatri1 giatri2 ... giatrin)
VD: list=(coffee water beer)
Lấy số lượng phần tử của mảng:
${#<tên mảng>[@]}
VD: elements = ${#list[@]}
Truy xuất phần tử trong mảng:
${<tên mảng>[<giá trị>]
VD: ${list[0]} sẽ cho giá trị đầu tiên trong mảng là coffee
Tìm và thay thế phần tử trong mảng:
${<tên mảng>[@]/pt_find/pt_replace}
VD: ${list[@]/coffee/milk} lệnh này sẽ thay thế phần tử có giá trị coffee thành milk
Các toán tử cơ bản
Về cơ bản thì shell linux sử dụng các toán tử cơ bản như các ngôn ngữ lập trình khác như C/C++, Java,… Tuy nhiên với toán tử nhân khi sử dụng nên dùng \* để phân biệt với lệnh bất kì * của linux
Để thực hiện phép tính cộng, trừ, nhân, chia có 3 cú pháp sau:
- Sử dụng cú pháp: `expr op1 <phép tính> op2` (để trong 2 dấu “ không phải nháy đơn đây là để thực thi lệnh shell expr)
VD:
echo `expr 2 + 3` sẽ hiển thị kết quả là 5 echo `expr 2 \* 3’ kết quả là 2 * 3 = 6
- Sử dụng cú pháp let “phép tính”
VD:
let “a=$a+3” let ”c=$a*$b”
Ngoài ra let có thể sử dụng các toán tử +=, -=, =+, =- khá tương tự như trong C/C++ và java
- Sử dụng cú pháp: $((…))
VD:
Z=$(($a + $b)) Z=$(($a * $b))
Đối với chuỗi thì khi sử dụng toán tử = có nghĩa là thực hiện so sánh 2 chuỗi với nhau chứ không phải toán tử gán dành cho số
Một số toán tử dành cho chuỗi
Giả sử có biến a giữ giá trị “abc” và b giữ giá trị “def”
Toán tử | Miêu tả | Ví dụ |
= | Kiểm tra giá trị của 2 chuỗi có bằng nhau hay không. Nếu bằng thì trả về giá trị đúng | $a = $b sẽ cho ra kết quả là sai |
!= | Kiểm tra sự khác nhau giữa 2 chuỗi. Nếu khác nhau thì trả về giá trị đúng | $a != $b sẽ ra kết quả là đúng |
Phép toán logic
Toán tử | Miêu tả | Ví dụ |
!<biểu thức> | Phép NOT | If [ ! “$a” == “$b” ] |
<biểu thức 1> -a <biểu thức 2> | Phép AND | if [ “$a” == “$b” -a “$c” == “$d” ] |
<biểu thức 1> -o <biểu thức 2> | Phép OR | if [ “$a” == “$b” -o “$c” == “$d” ] |
Lưu ý: Phải có các khoảng trắng giữa toán tử và biểu thức để tránh bị lỗi syntax
Câu lệnh điều kiện
Các mẫu câu lệnh:
- Lệnh if … fi
Cú pháp:
if [ condition ] then command fi
- Lệnh if … else … fi
if [ condition ] then command else command fi
- Lệnh if … elif … else … fi
if [ condition ] then command elif then command else command fi
Khi có nhiều lựa chọn về các điều kiện thì ta nên sử dụng cấu trúc case … esac thay vì sử dụng nhiều câu lệnh if lồng nhau.
Cấu trúc lệnh case … esac:
case “$luachon” in giatrimau_1) Các câu lệnh ;; giatrimau_2) Các câu lệnh ;; giatrimau_n) Các câu lệnh ;; *) Câu lệnh cho TH còn lại ;; (Giống default trong C/C++) esac
VD:
#!/bin/bash case "$1" in 1) echo 'Monday' ;; 2) echo 'Tuesday' ;; 3) echo 'Wednesday' ;; 4) echo 'Thursday' ;; 5) echo 'Friday' ;; 6) echo 'Saturday' ;; 7) echo 'Sunday' ;; *) echo "Don't match anything" exit 1 ;; esac exit 0
Lưu ý: Cú pháp đúng có khoảng trắng giữa condition với []
Case…asce mỗi trường hợp có thể lồng nhiều điều kiện chung nhóm tại giá trị mẫu và cách nhau bởi dấu |
Vòng lặp
Khi ta có một mảng và cần duyệt toàn bộ trên đó để lọc phần tử trong mảng để sử dụng cho mục đích nào đó ta không thể truy xuất từng phần tử trong mảng bằng lệnh if, nếu mảng có n phần tử thì ta phải viết n lệnh if tương ứng. Vì vậy mà shell cung cấp chúng ta vòng lặp như các ngôn ngữ khác để tiện hơn trong quá trình lập trình.
Có 4 loại vòng lặp trong shell là:
- Vòng lặp for: Hoạt động dựa trên danh sách của các mục. Nó lập đi lập lại một tập các câu lệnh cho mỗi mục có trong danh sách. Có thể sử duyệt mảng với điều kiện cho trước (khá giống với trong C/C++)
VD:
Vòng lặp for cho các danh mục có sẵn
for var in Mon Tue Wed Thurs Fri Sat Sun do echo $var done
Vòng lặp for dùng cho duyệt mảng
for (( i=0; $i < $elements; i++ )) do echo ${array[$i]} done
- Vòng lặp while: Chủ yếu sử dụng vòng lặp này khi ta muốn thực thi một tập lệnh lặp đi lặp lại nhiều lần trong khi điều kiện vẫn còn đúng. (Ở phần điều kiện nếu là true thì vòng lặp while sẽ được thực thi)
VD:
Vòng lặp while
a=0 while [ $a –le 5 ] do echo $a a=$(($a + 1)) done
- Vòng lặp until: Ngược lại với while, vòng lặp này sẽ thực thi tập lệnh của nó khi giá trị điều kiện là sai. Nó sẽ chạy tới khi thỏa mãn điều kiện là đúng.
VD:
a=0 until [ ! $a –le 5 ] do echo $a a=$(($a + 1)) done
- Vòng lặp select: Vòng lặp cung cấp một danh sách menu từ một tập các phần tử được cho có đánh số ở đầu để lựa chọn. (Đây là vòng lặp có sẵn trong ksh được điều chỉnh vào bash. Nó không có sẵn trong bash
VD:
#!/bin/ksh select DRINK in tea cofee water juice appe all none do case $DRINK in tea|cofee|water|all) echo "Go to canteen" ;; juice|appe) echo "Available at home" ;; none) break ;; *) echo "ERROR: Invalid selection" ;; esac done
Kết quả xuất ra sẽ cho 1 danh sách lựa chọn
[[email protected] shellpractice]# ./selectloop.sh 1) tea 2) cofee 3) water 4) juice 5) appe 6) all 7) none #?
Trạng thái Exit
Mặc định trong linux khi một lệnh hoặc một script được thực thi, nó sẽ trả về 2 loại giá trị để xác định xem là lệnh hoặc script đó thực thi có thành công hay không.
- Giá trị 0: Tức là lệnh hoặc script đã thực thi thành công.
- Giá trị trả về khác không: Tức là lệnh hoặc script khi thực thi đã gây lỗi.
Giá trị này gọi là trạng thái Exit
Các lệnh điều hướng
Đề thực hiện điều hướng lại output ra một file cũng như input lấy đầu vào từ một file ta có thể sử dụng các cú pháp sau:
Ký hiệu | Ý nghĩa |
> | Điều hướng output ra một file và ghi đè toàn bộ nội dung một file đã có. Nếu chưa có thì sẽ tạo file mới.
VD: ls > listfile |
>> | Điều hướng output ra một file đã có và ghi vào cuối file đó. Nếu file chưa có thì sẽ tạo file mới
VD: ls >> listfile |
< | Lấy dữ liệu cho câu lệnh linux từ một file |
Ngoài ra có một số trường hợp chúng ta không muốn kết quả output xuất ra file hoặc terminal ta có thể loại bỏ output bằng cách điều hướng nó vào tệp /dev/null. Đây là tệp đặc biệt sẽ tự động loại bỏ tất cả input của nó.
Có thể loại bỏ đầu ra của một lệnh và đầu ra bị lỗi của lệnh đó bằng việc thêm 2>&1 với 2 là STDERR được điều hướng lại ra 1 là đại diện cho STDOUT.
Cú pháp: command > /dev/null 2>&1
Hàm trong Shell
Cú pháp:
function_name() { list of comands }
Truyền tham số cho hàm tương tự như truyền tham số cho file script. Các biến tham số lần lượt là $1, $2, … $n. Tuy nhiên những biến này là biến local chỉ có giá trị trong hàm và không ảnh hưởng gì tới các biến tham số được đưa vào script. Các biến còn lại thì có giá trị toàn cục, chẳng hạn ta khai báo một biến trong hàm, khi ra khỏi hàm khi truy xuất ta vẫn nhận được giá trị của biến đó khi được gán trong hàm
VD:
#!/bin/bash Hello() { Echo “ Hello world $1 $2” } Hello Hai Dang
Lúc này kết quả output khi chạy script sẽ là Hello world Hai Dang
Trả về giá trị từ một hàm tương tự như thực hiện return giá trị trong các ngôn ngữ lập trình khác. Sử dụng $? Để lấy giá trị trả về (chỉ nên sử dụng return khi trả mã lỗi). Ngoài ra có thể sử dụng echo để lấy giá trị trả về, khi sử dụng echo thì chỉ nên echo 1 lần trong hàm với giá trị cần lấy, có nhiều echo thì kết quả từ lệnh echo đầu tiên trong hàm sẽ được gán cho biến bên ngoài, cũng có thể sử dụng biến global để lấy giá trị)
Lưu ý: Khuyến khích có chữ function trước tên hàm để dễ phân biệt function tenham()
Lệnh read – đọc giá trị nhập từ bàn phím, file,…
Dùng để lấy dữ liệu nhập từ bàn phím hoặc từ một file và lưu vào biến.
Cú pháp:
read var1 var2 var3... varN
Nếu sử dụng read nhưng không kèm tham số thì giá trị sẽ được lưu vào biến $REPLY
Khi có nhiều biến thì mỗi biến sẽ được gán một phần giá trị trong dòng mà read nhận được. Nó được phân cách bởi giá trị của biến $IFS, theo mặc định thì biến $IFS chứa ký tự khoảng trắng. Khi đó mỗi biến sẽ giữ một chuỗi được cách biệt bởi kí tự của $IFS chứ không phải là một dòng. Biến cuối cùng sẽ giữ các giá trị còn lại của dòng đó.
VD:
Minh họa cho ví dụ sử dụng nhiều biến để nhận giá trị nhập vào từ lệnh read
[[email protected]_Internal_LAP_21 script]$ read var1 var2 var3 day la vi du minh hoa cho read [[email protected]_Internal_LAP_21 script]$ echo -e "var1: $var1\nvar2: $var2\nvar3: $var3" var1: day var2: la var3: vi du minh hoa cho read
Như ví dụ trên thì giá trị của biến $var1 và $var2 được tách ra từ cả dòng nhập vào và được phân biệt bởi khoảng trắng (giá trị mặc định $IFS – Internal File Separator). Nếu ta muốn chỉnh lại cách phân biệt thì ta chỉ cần sửa lại giá trị cho biến $IFS.
[[email protected]_Internal_LAP_21 script]$ grep "dangtgh" /etc/passwd dangtgh:x:502:502::/home/dangtgh:/bin/bash [[email protected]_Internal_LAP_21 script]$ IFS=: [[email protected]_Internal_LAP_21 script]$ read var1 var2 var3 dangtgh:x:502:502::/home/dangtgh:/bin/bash [[email protected]_Internal_LAP_21 script]$ echo -e "var1: $var1\nvar2: $var2\nvar3: $var3" var1: dangtgh var2: x var3: 502:502::/home/dangtgh:/bin/bash [[email protected]_Internal_LAP_21 script]$ unset IFS
Ví dụ trên ta lấy một dòng trong file /etc/passwd và dùng dấu “:” để phân biệt các giá trị tách ra từ dòng.
Lưu ý: Khi thay đổi biến $IFS thì sau khi sử dụng xong ta nên trả lại giá trị mặc định cho IFS bằng lệnh unset
Đối với file thì ta sử dụng ký tự điều hướng < với cú pháp:
read var1 var2 ... varN < ten_file
Ngoài ra khi sử dụng nên thêm cờ -r để ngắt dòng khi nhận kí tự xuống dòng và không thực hiện ghi tiếp khi gặp kí tự “\”
Link:
http://tldp.org/LDP/Bash-Beginners-Guide/html/index.html