Lập trình shell

Phần 1 - Biến, tham số, điều kiện và các toán tử

Nguyễn Hải Châu (nhchau@gmail.com)
Trường Đại học Công nghệ, ĐHQGHN

Shell là gì

  • Là một chương trình thông dịch lệnh cho phép người sử dụng tương tác với hệ điều hành
  • shell đồng thời là một ngôn ngữ lập trình bậc cao
  • Có nhiều loại shell:
    • sh (/bin/sh): shell chuẩn trên UNIX/Linux, còn gọi là Bourne shell
    • bash (/bin/bash): GNU Bourne Again Shell
    • csh (/bin/csh): C shell
    • ksh (/bin/ksh): Korn shell
    • zsh...
  • Khuyến cáo:

Khi nào không nên dùng shell?

  • Khi cần tốc độ thực hiện cao
  • Tính toán dấu phẩy động
  • Tương thích giữa các platform
  • Các ứng dụng phức tạp, cấu trúc dữ liệu phức tạp như con trỏ, danh sách móc nối, ...
  • An ninh cao
  • Ứng dụng có yêu cầu vào/ra cao
  • Các ứng dụng cần GUI
  • Cần che giấu mã nguồn
  • Cần socket I/O, port

Tương tác với bash: các phím tắt

  • Tab: Tự động hoàn thành tên lệnh hoặc tên file
  • Ctrl-A, Ctrl-E: Về đầu dòng, cuối dòng
  • Ctrl-L, Ctrl-U (Ctrl-K), Ctrl-W, Ctrl-H: Xóa màn hình, xóa từ con trỏ về đầu dòng (cuối dòng), xóa từ ở trước con trỏ và xóa ký tự trước con trỏ
  • Ctrl-R: Tìm các lệnh đã thực hiện
  • Ctrl-C: Dừng chương trình đang chạy
  • Ctrl-D: Thoát khỏi bash
  • Ctrl-Z: Tạm thời dừng chương trình đang chạy để làm việc khác, khôi phục bằng lệnh fg
  • Ctrl-T, Alt-T: Đổi chỗ hai ký tự, hai từ ở trước con trỏ
  • Alt-F, Alt-B: Di chuyển con trỏ về sau hoặc trước một từ

Thực hiện một chương trình shell

  • sh script
  • chmod +rx script ; ./script hoặc
  • chmod 755 script ./script
  • Việc chỉ ra thư mục hiện hành kèm theo tên chương trình để đảm bảo an ninh
  • Một chương trình shell:
    • Gồm nhiều dòng lệnh
    • Mỗi dòng có thể có nhiều lệnh, cách nhau bởi các ký hiệu phân cách lệnh
    • Còn được gọi là một script

Câu lệnh trong shell

  • Trên một dòng lệnh shell có thể có một hoặc nhiều câu lệnh
  • Một câu lệnh: command [parameter...]
  • Nhiều câu lệnh được ghép từ một câu lệnh cách nhau bởi các dấu phân cách ; hoặc && hoặc || hoặc &
  • Ví dụ: ls -l ; date ; cal

Các ký hiệu đặc biệt trong shell

  • #
    • Chú thích dòng lệnh, có tác dụng từ vị trí của nó đến cuối dòng
    • Bắt đầu ở đầu dòng
    • Bắt đầu # ở giữa dòng
    • Có thể dùng các ký tự escape để làm cho dấu # mất hiệu lực
    • Sử dụng trong biểu thức chính qui
    • Chỉ ra thông dịch lệnh, ví dụ: #!/bin/bash, #!/usr/bin/python
  • ;: Phân tách hai lệnh trên cùng một dòng
  • ;;: Kết thúc một phương án trong case
  • ;;&, ;&: Kết thúc phương án trong case (với bash từ 4+ trở lên)

Các ký hiệu đặc biệt trong shell

  • . (dấu chấm):
    • Lệnh built-in, tương đương với source
    • Để ẩn tên một file, sử dụng . để bắt đầu tên file
    • Thư mục hiện hành
    • Chỉ một ký tự bất kỳ trong biểu thức chính qui
  • " (nháy kép): Làm mất tác dụng của các dấu phân cách từ như dấu trắng, ; xuất hiện trong string.
  • ' (nháy đơn): Hầu hết các ký tự đặc biệt của shell mất tác dụng trong 'string'.
  • , (dấu phảy): Liên kết một chuỗi các toán tử số học
  • \: Dùng làm mất hiệu lực các ký tự đặc biệt
  • /: Phân cách tên file, thư mục trong đường dẫn

Các ký hiệu đặc biệt trong shell

  • `: Output của lệnh command có thể được gán vào một biến
  • :: Lệnh NULL, hoặc giá trị TRUE trong shell
  • !: Đảo hoặc phủ định logic
  • *: Ứng với mọi chuỗi ký tự, phép nhân
  • ?: Toán tử kiểm tra, hoặc một ký tự trong tên tệp
  • $: Lấy giá trị của biến
  • ${}: Thay thế tham số
  • $*, $@: Vị trí các tham số
  • $?: Giá trị trả lại của một lệnh
  • $$: Biến chứa process ID
  • (): Nhóm lệnh

Các ký hiệu đặc biệt trong shell

  • {}: Một block lệnh
  • {}: Text placeholder (dùng trong xargs)
  • {} \;: Đường dẫn (chủ yếu dùng trong find)
  • []:
    • test - kiểm tra điều kiện logic
    • Phần tử mảng
    • miền của các ký tự
  • >, &>, >>, <, <>: Định hướng lại vào/ra
  • |: Pipe
  • &: Thực hiện một tiến trình ở chế độ background
  • ||, &&: Toán tử logic OR, AND

Biến và tham số

  • Biến là một nhãn/tên, được gán cho một vị trí trong bộ nhớ.
  • Nội dung của biến chính là giá trị tương ứng trong bộ nhớ
  • Biến xuất hiện trong các toán tử số học, phân tích string...

Gán và thay thế giá trị biến

var1=23  # Gán 23 cho biến var1
echo 'var1 = $var1' # Không in ra giá trị biến var1
echo "var1 = $var1" # In ra giá trị biến var1
echo var1 # Không in ra giá trị biến var1
var2=$var1 # Biến var2 nhận giá trị của var1
echo "var2 = " $var2
# Lưu ý không có dấu cách trong phép gán =
## var1 = $var1
## var1 = 23
## var1
## var2 =  23

Gán và thay thế giá trị biến

a=6789
echo "Value of a is $a"
let "a=16+3"
echo "Value of a is changed to $a"
b=5
c=6
let "a=$b+$c"
echo $a
d=`date`
echo Date is $d
## Value of a is 6789
## Value of a is changed to 19
## 11
## Date is Sat Nov 5 12:18:05 ICT 2016

Gán và thay thế giá trị biến

  • Nhận giá trị biến từ bàn phím: read var
  • Giá trị biến là text có nhiều dòng:
read -d '' a << EOT ## Hoặc có thể dùng `cat << EOT ... EOT`
this
is a
text
EOT
echo "$a"
echo $a
## this
## is a
## text
## this is a text

Các kiểu biến đặc biệt

  • Biến cục bộ: Chỉ có tác dụng trong code block
  • Biến môi trường: Ảnh hưởng đến shell và giao diện người dùng
env | tail -20 | head -10
## R_HOME=/usr/lib64/R
## WINDOWPATH=2
## DISPLAY=:1
## XDG_RUNTIME_DIR=/run/user/1000
## XDG_CURRENT_DESKTOP=GNOME
## R_PLATFORM=x86_64-redhat-linux-gnu
## R_DOC_DIR=/usr/share/doc/R
## R_LIBS_USER=~/R/x86_64-redhat-linux-gnu-library/3.3
## R_SESSION_TMPDIR=/home/tmp/RtmpxRQAYq
## XAUTHORITY=/run/user/1000/gdm/Xauthority

Các kiểu biến đặc biệt

  • Các tham số vị trí:
    • $0: Tên script
    • $1: Tham số thứ nhất, ..., tham số thứ 9
    • ${11}, ${12}, ...: Tham số thứ 11, 12...
cat positionalpar.sh
./positionalpar.sh p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13
## echo Script name = $0
## set -v
## echo Parameter 5 = $5
## echo Parameter 12 = ${12}
## set +v
## Script name = ./positionalpar.sh
## echo Parameter 5 = $5
## Parameter 5 = p5
## echo Parameter 12 = ${12}
## Parameter 12 = p12
## set +v

Các kiểu biến đặc biệt

  • Toán tử shift đẩy các tham số 1, 2, 3, ... sang bên trái một vị trí
  • Khi đó tham số thứ n+1 trở thành tham số thứ n
cat shift.sh
./shift.sh p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13
## echo Number of parameters = $#
## shift
## echo Number of parameters after shift = $#
## echo Script name = $0
## echo Parameter 5 = $5
## echo Parameter 12 = ${12}
## 
## Number of parameters = 13
## Number of parameters after shift = 12
## Script name = ./shift.sh
## Parameter 5 = p6
## Parameter 12 = p13

Thực hành biến của bash

  • 2.1. Hãy viết một script tự nhân bản: Sau mỗi lần thực hiện, script copy chính nó ra một file có tên là backup.sh
  • 2.2. Viết một script in chính nó ra màn hình với thứ tự các dòng ngược lại
  • 2.3. Viết một script xác định các tham số dòng lệnh có là số nguyên hay không

Các biến đặc biệt khác

  • $*: Tất cả các biến vị trí trong một chuỗi
  • $@: Tất cả các biến vị trí như xuất hiện trên tham số dòng lệnh
  • $#: Số lượng tham số
cat special.sh
./special.sh p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13
## echo Number of arguments = $#
## echo All parameter = "$*"
## echo List parameters = "$@"
## 
## Number of arguments = 13
## All parameter = p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13
## List parameters = p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13

Quoting

ls -l [Ss]*.sh
echo $(ls -l s*sh)
echo "$(ls -l s*sh)"
## -rwxrwxr-x 1 chau chau 283 Oct 31 18:33 select.sh
## -rwxr-xr-x 1 chau chau 151 Oct 31 14:28 shift.sh
## -rwxrwxr-x 1 chau chau  85 Oct  4 14:45 special.sh
## -rwxrwxr-x 1 chau chau 500 Nov  1 10:23 statis.sh
## -rwxrwxr-x 1 chau chau 409 Nov  1 10:03 st.sh
## -rwxrwxr-x 1 chau chau 283 Oct 31 18:33 select.sh -rwxr-xr-x 1 chau chau 151 Oct 31 14:28 shift.sh -rwxrwxr-x 1 chau chau 85 Oct 4 14:45 special.sh -rwxrwxr-x 1 chau chau 500 Nov 1 10:23 statis.sh -rwxrwxr-x 1 chau chau 409 Nov 1 10:03 st.sh
## -rwxrwxr-x 1 chau chau 283 Oct 31 18:33 select.sh
## -rwxr-xr-x 1 chau chau 151 Oct 31 14:28 shift.sh
## -rwxrwxr-x 1 chau chau  85 Oct  4 14:45 special.sh
## -rwxrwxr-x 1 chau chau 500 Nov  1 10:23 statis.sh
## -rwxrwxr-x 1 chau chau 409 Nov  1 10:03 st.sh

Escaping

  • \n: Xuống dòng
  • \r: Về đầu dòng
  • \t: Tab
  • \v: Tab theo chiều dọc
  • \b: backspace
  • \a: ký tự chuông
  • \0aa: Ký tự có mã ASCII hệ 8 là aa
  • \0xbb: Ký tự có mã ASCII hệ 16 là bb
  • \": "
  • \$: $
  • \\: \

Exit

  • exit status: status là một số nguyên từ 0 đến 255
  • $?: status của lệnh vừa thực hiện

test: Kiểm tra điều kiện

  • Cấu trúc test:
    • [: thường dùng với if...then
    • [[: Được bổ sung từ bash 2.02
    • ((...))let: Kiểm tra được biểu thức phức tạp hơn [, [[

Các toán tử test với file

  • -e: File có tồn tại không?
  • -f: Regular file (tức là file không phải thiết bị, thư mục)
  • -s: File có cỡ > 0
  • -d: File là thư mục
  • -b: block device
  • -c: character device
  • -p: pipe
  • -h, -L: symbolic link
  • -S: socket
  • -t: file là terminal device
  • -r, -w, -x: Có quyền đọc, ghi, thực hiện với user hiện hành không?

Các toán tử test với file

  • -g, -u: Có quyền thực hiện setuid hoặc setguid không?
  • f1 -nt f2: f1 có mới hơn f2?
  • f1 -ot f2: f1 cũ hơn f2?
  • f1 -ef f2: f1 và f2 có hard link đến cùng một file không?

Các toán tử so sánh khác

  • So sánh số nguyên:
    • -eq: So sánh bằng
    • -ne: So sánh khác
    • -gt, -ge: Lớn hơn, lớn hơn hoặc bằng
    • -lt, -le: Nhỏ hơn, nhỏ hơn hoặc bằng
    • <, <=, >, >=: Sử dụng với (()). Ví dụ ((\$a <= \$b))

Các toán tử so sánh khác

  • So sánh chuỗi:
    • = so sánh bằng
    • == bằng
    • != khác
    • -z: chuỗi là NULL
    • -n: chuỗi khác NULL
    • <, >: Nhỏ hơn, lớn hơn thứ tự từ điển

Các toán tử

  • Toán tử gán: =
  • Toán tử số học:
    • Cộng (+), trừ (-), nhân (*), chia (/)
    • Mũ (^), modulo (%)
    • +=, -=, *=, /=, %=
  • Toán tử bitwise: <<, <<=, >>, >>=, &, &=, |, |=, ~, ^, ^= - tương tự C
  • Toán tử logic:
    • !: not
    • &&, ||
  • Toán tử khác: , - chủ yếu sử dụng trong vòng for

Các hằng số

  • Có thể biểu diễn ở hệ 10, 8 (bắt đầu bằng số 0) hoặc 16 (bắt đầu bằng 0x)
let "a=12,b=012,c=0x12"
echo $a $b $c
## 12 10 18

Cấu trúc (())

  • Cấu trúc (()) cho phép thực hiện các toán tử số học, tương tự như let
((a=12,b=012,c=0x12))
echo $a $b $c
d=$((2*3))
echo $d
## 12 10 18
## 6