Các Lược Giảng Chuyên Sâu về Sử Dụng Văn Lệnh BASH trong Linux

Vietsciences-Làng Đậu     04/11/2006

Những bài cùng tác giả

Loạt bài " Các Lược Giảng Chuyên Sâu về Sử Dụng Văn Lệnh BASH trong Linux" của tác giả Làng Đậu giữ bản quyền 2006. Người đọc chỉ được sử dụng cho mục đích học tập hay giảng dạy cho cá nhân. Cấm mọi hình thức sao chép hay in lại nhằm mục đích mua bán hay trục lợi mà không có sự đồng ý chính thức của tác giả.  Mọi thông tin về việc phổ biến rộng rãi có tính quảng bá tài liệu này cho mụch đích giáo dục xin liên lạc về vo_quang_nhan@yahoo.com  

 

Bài 3:

 

9 Phép toán dùng với các biểu thức BASH:

 

Trong UNIX/Linux các biểu thức là các mệnh đề logic thường được BASH thẩm định giá trị và từ đó thực thi quyết định của các câu lệnh. Các mệnh đề này thành lập trong việc nối các toán tử (như là các biến các hằng số hay các hàm ...) với các phép toán theo một cú pháp chặt chẽ.  Chẳng hạn như các mệnh đề dùng trong câu lệnh if, case hay trong các điều kiện của vòng lặp như là for, while, until ....   Các biểu thức này được đặt trong các dấu ngoặc vuông [ ] và có thể nối nhau qua các phép toán logic

Thí dụ:

#correct form
if [ ! -f myfile ]; then    #if not exist myfile
        cat myfile
fi


#The following expression violate the syntax - missing space between [ and ! sign:
#if [! -f myfile ]; then
#    cat myfile
#fi
#similar incorrectness: [ ! -f myfile] or [ !-f myfile ]

Lưu ý: Kí tự khoảng trống (space) giữa các dấu ngoặc, các kí hiệu của phép toán, và các toán tử trong BASH quy định rất chặt chẽ.  Nếu thiếu hay thừa kí tự khoảng trống này sẽ gây ra lỗi cú pháp

 

9.1 Phép toán thử nghiệm tập tin (file test) trong các biểu thức

9 .1.1 Phép toán lên một hạng tử

9.1.1.1  [ -b <Tên_Tập_Tin> ] Trả về giá trị "true" nếu tập tin <Tên_Tập_Tin> là một thiết bị kiểu khối  (như là ổ mềm, cdrom, ổ cứng...) (Xem thêm phụ lục)

9.1.1.2   [ -c <Tên_Tập_Tin> ]  Trả về giá trị "true" nếu tập tin <Tên_Tập_Tin> là một thiết bị kiểu kí tự  (như là bàn phím, modem, ...) (Xem thêm phụ lục)

9.1.1.3   [ -d <Tên_Tập_Tin> ] Trả về giá trị "true" nếu tập tin  <Tên_Tập_Tin> là một thư mục

Thí dụ:

if [ -d /home/Bill ]; then
    echo "directory /home/Bill exist"
if

9.1.1.4 [ -e <Tên_Tập_Tin> ]  Trả về giá trị "true" nếu tập tin <Tên_Tập_Tin> tồn tại

Thí dụ:

if [ -e myfile ]; then
        echo "my file exists"
fi

9.1.1.5  [ -f <Tên_Tập_Tin> ]  Trả về giá trị "true" nếu tập tin <Tên_Tập_Tin> là tập tin thông thường

9.1.1.14  [ -g <Tên_Tập_Tin> ] Trả về giá trị "true" nếu bit setgid cài lên đó (Xem thêm phụ lục)

9.1.1.7   [ -k <Tên_Tập_Tin> ]  Trả về giá trị "true" nếu bit Sticky được cài lên tập tin  (Xem thêm phụ lục )

9.1.1.8  [ -O  <Tên_Tập_Tin> ]Trả về giá trị "true" nếu bạn là chủ tập tin <Tên_Tập_Tin> (Xem thêm phụ lục )

9.1.1.9    [ -p <Tên_Tập_Tin> ] Trả về giá trị "true" nếu tập tin  <Tên_Tập_Tin> có kiểu ống dẫn truyền (Xem thêm phụ lục)

9.1.1.10  [ -L <Tên_Tập_Tin> ] Trả về giá trị "true" nếu tập tin  <Tên_Tập_Tin> là liên kết mềm  (Xem thêm phụ lục)

9.1.1.11   [ -S <Tên_Tập_Tin> ] Trả về giá trị "true" nếu tập tin <Tên_Tập_Tin> là tập tin ổ nối (Xem thêm phụ lục)

9.1.1.12  [ -r <Tên_Tập_Tin> ] Trả về giá trị "true" nếu tập tin  <Tên_Tập_Tin> có thuộc tính đọc được (Xem thêm phụ lục)

9.1.1.13   [ -s <Tên_Tập_Tin> ] Trả về giá trị "true" nếu tập tin  <Tên_Tập_Tin> có độ dài lớn hơn 0 byte

9.1.1.14  [ -u <Tên_Tập_Tin> ] Trả về giá trị "true" nếu bit setuid cài lên đó (Xem thêm phụ lục)

9.1.1.15  [ -w <Tên_Tập_Tin> ]  Trả về giá trị "true" nếu tập tin <Tên_Tập_Tin> có thuộc tính cho phép viết (Xem thêm phụ lục)

9.1.1.16  [ -x <Tên_Tập_Tin> ]  Trả về giá trị "true" nếu tập tin <Tên_Tập_Tin> là tập tin thực thi được (Xem thêm phụ lục )

9.1.2 Phép toán lên hai hạng tử

9.1.2.1 [ <Tên_Tập_Tin_1> -nt <Tên_Tập_Tin_2> ]  Trả về giá trị "true" nếu tập tin <Tên_Tập_Tin_1> mới hơn tập tin  <Tên_Tập_Tin_2>

9.1.2.2 [ <Tên_Tập_Tin_1>  -ot <Tên_Tập_Tin_2> ] Trả về giá trị "true" nếu tập tin <Tên_Tập_Tin_1> cũ hơn tập tin  <Tên_Tập_Tin_2>

9.1.2.3 [ <Tên_Tập_Tin_1>  -ef <Tên_Tập_Tin_2> ] Trả về giá trị "true" nếu tập tin <Tên_Tập_Tin_1> và  <Tên_Tập_Tin_2> cùng liên két tới một tập tin .

Lưu ý:  Nếu  <Tên_Tập_Tin_1> và  <Tên_Tập_Tin_2> là hai loại tập tin  liên kết khác nhau thì phép toán luôn luôn trả về giá trị "false"  (một tệp là liên kết mềm còn tệp kia là liên kết cứng)

9.2 Phép toán so sánh trên các số nguyên
9.2.1 Toán tử -eq  Trả về giá trị "true" nếu cả hai hạng tử bằng nhau.  Thí dụ: [ $a -eq 5 ]

9.2.2 Toán tử -ne  Trả về giá trị "true" nếu cả hai hạng tử không bằng nhau.  Thí dụ:  [ $a -ne $b ]

9.2.3 Toán tử -gt   Trả về giá trị "true" nếu hạng tử thứ nhất lớn hơn hạng tử thứ hai

9.2.4 Toán tử -lt   Trả về giá trị "true" nếu hạng tử thứ nhất nhỏ hơn hạng tử thứ hai

9.2.5 Toán tử -le   Trả về giá trị "true" nếu hạng tử thứ nhất nhỏ hơn hay bằng hạng tử thứ hai

9.2.6 Toán tử -ge  Trả về giá trị "true" nếu hạng tử thứ nhất lớn hơn hay bằng hạng tử thứ hai
9.3 Phép toán so sánh trên các chuỗi kí tự

9.3.1 Toán tử =    Trả về giá trị "true" nếu cả hai hạng tử bằng nhau.  (Thí dụ: [ $a = "mystring" ] )

9.3.2 Toán tử != Trả về giá trị "true" nếu cả hai hạng tử không bằng nhau.  (Thí dụ: [ $a != $b ] )

9.3.3 Toán tử \< Trả về giá trị "true" nếu hạng tử thứ nhất nhỏ hơn hạng tử thứ hai theo bảng thứ tự ASCII  (Thí dụ: [ $a \< $b ] )

9.3.4 Toán tử \> Trả về giá trị "true" nếu hạng tử thứ nhất lớn hơn hạng tử thứ hai theo bảng thứ tự ASCII  ASCII order

9.3.5 Toán tử -z Trả về giá trị "true" nếu hạng tử duy nhất này có độ dài bằng 0  ( Thí dụ: if [ -z $mystr ]; then ...; fi)

9.3.6 Toán tử -n Trả về giá trị "true" nếu hạng tử duy nhất này có độ dài lớn hơn 0

9.4 Phép toán logic trên các mệnh đề con
9.4.1 Toán tử -a: phép logic  AND

Thí dụ:
if [ $x -eq 10 -a $y -gt 1 ]; then
    echo "If x equals 10 AND y greater than 1 then"
fi

9.4.2 Toán tử -o: phép logic OR

Thí dụ:
if [ $x -eq 10 -o $y -gt 1 ]; then
    echo "If x equals 10 OR y greater than 1 then"
fi

9.5 Phép toán logic trên các biểu thức

Có hai toán tử quan trọng đuợc dùng để nối giữa các biểu thức, đó là || (tương đương với toán tử logicl OR),  && (tương đương với toán tử logic AND) và ! (tương đương với toán tử NOT). Các phép toán này thường dùng trong các mệnh đề điều kiện như là if, while, for, ...

Thí dụ:
echo -n "Erase this file (y/n) :? "
read ANSWER
if [ "$ANSWER" = "y" ] || [ "$ANSWER" = "yes" ] ; then
    rm -f $file
elif [ "$ANSWER" = "n" ] || [ "$ANSWER" = "no" ] ;  then
    echo "user abort the operation"
    exit 0
else
    echo "Wrong keyed!"
fi

Thí dụ2:
while [ $A -lt 8 ] && [ ! "$B" = "n" ]; do   #while A less than 4 and B is not equal "n" do
   let "A +=2"                               #increasing A (see  next paragraph)
   echo "stop with A=$A (y/n)? "             #asking for stop
   read B                                    #receiving user's decision in B
done
if [ $A -eq 8 ]; then
   echo "A reachs maximum value $A"
fi

10 Phép toán trên các biến

10.1 Phép toán lên chuỗi kí tự

Các phép toán trên chuỗi kí tự đã được trình bày chi tiết trong bài 2 phần 6, 7 và 8

10.2 Phép toán số học trên biến nguyên

Để áp dụng các phép toán số học như +, -, * / .. lên các biến có giá trị nguyên thì có thể dùng lệnh let "<Mệnh_đề_phép_toán_số_học>".  Tương đương như thế, , cũng có thể dùng ((<Mệnh_đề_phép_toán_số_học>)) để thực thi các phép toán. Nếu dùng let hay (( )) để gán giá trị nguyên lên biến thì có thể đặt các kí tự trống (space) ở giữa toán tử = mà không sợ bị vi phạm lỗi cú pháp (xem lại cách gán giá trị đơn giản). Ngoài ra, sau khi thi hành các phép tóan số học, let hay (( )) sẽ trả về giá trị 0 nếu giá trị của  <Mệnh_đề_phép_toán_số_học> khác không và trả về giá trị 1 cho các trường hợp còn lại.

10.2.1. Phép gán giá trị =   

Thí du: 
var1=0
var2=1
let "var1 = $var2"  # remember the assigned variable has NO '$' sign
let "var2= 4"
echo "var1=$var1 and var2=$var2"
((var2 += var2))
echo "var2"

10.2 .2. Cộng + 

 Thí dụ:  let "var1 = $var2 + 2 + $var1"

10.2.3. Trừ  -

10.2.4. Nhân *

10.2.5. Chia /

10.2.6. Lũy thưà **

10.2.7. Modulo %

Cạnh đó, BASH cũng hỗ trợ một số phép toán tương tự như trong ngôn ngữ C/C++:

10.2.8   Plus-equal +=

Thí dụ:

let "var1 +=5" 
# it equivalent to the command: let "var1 = $var1 +5" or command ((var1 += 5))

10.2.9   Minus-equal -=   

10.2.10   Multiply-equal *= 

10.2.11   Divide-equal /=

10.2.12  Modulo-equal %=

10.3 Phép toán nhị phân (Binary oparation) trên các biến nguyên
10.3.1. shift left << Đẩy các bit sang trái 1 bit (tương đương với nhân cho 2)

10.3.2. shift left-equal <<=<n> Đẩy các bit sang trái <n> bit

Thí dụ:

let "myvar <<= 2" #results in myvar is left-shifted 2 bits (or multiplied by 4)

10.3.3. Phép toán nhị phân shift right  >> Đẩy sang phải 1 bit

10.3.4. Phép toán nhị phân shift right-equal  >>=<n>

10.3.5.   Phép toán nhị phân AND &

10.3.6.   Phép toán nhị phân AND-equal &=

10.3.7.   Phép toán nhị phân OR |

10.3.8.   Phép toán nhị phân OR-equal |=

10.3.9.   Phép toán nhị phân nghịch đảo  ~

10.3.10. Phép toán nhị phân NOT !

10.3.11. Phép toán nhị phân XOR ^

10.3.12. Phép toán nhị phân XOR-equal ^=

10.3 Phép toán điểm chấm động (floating point) trên các biến

BASH không hỗ trợ cho các tính toán điểm chấm động (floating point). Tuy nhiên, lệnh bc có thể giúp tiến hành các loại toán này.  Cú phép đơn giản để tiến hành là:  
 <TÊN_BIẾN>=$(echo "<Tham_Số>"; <Biểu_Thức_Số_Học>" | bc [<Tham_Số_Ngắn>])

Giá trị của <Tham_Số> Có thể là:  

  • length số chữ số có nghiã trong biểu thức
  • scale số chữ số thập phân sau dấu chấm trong biểu thức
  • sqrt (hay read).

Giá trị của <Tham_Số_Ngắn> Có thể là:

  • -l cho thư viện toán,
  • Các tham số khác như là  -i,-q,-s.  (xem thêm chi tiết bằng lệnh hướn dẫn  man bc)
    Khi dùng Tham số ngắn -l thì mệnh lệnh sẽ load từ trước thư viện toán bao gồm cả các hàm
      s(x) --sine, c(x) -- cosine,  a(x) -- arctan, l(x) logarith tự nhiên, e(x) -- mũ ơ số e, và j(n,x) -- Hàm Bessel của số nguyên n của x.

Lưu ý: lệnh bc hỗ trợ nhiều tính toán có khả năng lập trình được với nhiều tính năng khác bao gồm cả biến số, vòng lặp và phân nhánh. Xem thêm chi tiết bằng lệnh  man bc

Thí dụ1:

#this example calculates Pi number with 10 digit of accuracy
Pi = $(echo "scale=10; 4*a(1)" | bc -l)   # using arctan(1) to calculate Pi/4
echo "PI=$Pi"  #it should be 3.1415926532

Thí dụ2:

# this example call bc without the short_option -l
radius=1.234
Pi=3.1416
$result=$(echo "scale=4; $radius*$radius*$pi" |bc)  # we need only 4 digits
echo "the area occupied by this circle is :$result"

 10.4 Dùng expr để thực thi các phép toán

Đây là lệnh đánh giá một mệnh đề cuả BASH.  Nó trả về giá trị trong các phép toán số học (trả về giá trị nguyên), các so sánh (trả về 1 hay 0), các phép toán lên chuỗi kí tự.  Ta quy ước gọi ARG, ARG1ARG2 là các tham số trong biểu thức cần được đánh giá. (tham số này sẽ là giá trị của một biến (nếu biến đó là myvar thì ARG phải viết thành $myvar), của một string (các string phải để trong ngoặc " hay  ' ) , hay của một hằng

Lưu ý :  Lệnh expr có thể không hoạt động hữu hiệu trong một số cài đặt mặc định cho X Windows (chẳng hạn như chương trình "Terminal Emulation - trình đơn Shell Console của SUSE SLES 9.  Để chạy được lệnh expr phải dùng trình đơn Linux Console trong X Window). Muốn cho lệnh này hoạt động đúng chức năng, tốt nhất là chọn chế độ chạy trên đầu cuối trực tiếp

10.4.1 Phép toán số học: Dùng cú pháp expr ARG1 <Phép_Toán> ARG2

10.4.2 Phép so sánh: Trả về 1 nếu mệnh đề đúng và 0 nếu sai.  ARG1ARG2 của nó có thể là giá trị số nếu là phép so sánh hai số hay giá trị chuỗi kí tự nếu là phép so sánh hai string.  Các phép toán bao gồm

  • =  Kiểm nghiệm sự bằng nhau của hai hạng tử
  • \>  Kiểm nghiệm sự lớn hơn của hạng tử bên trái
  • \<  Kiểm nghiệm sự nhỏ hơn của hạng tử bên trái
  • \>= Kiểm nghiệm sự lớn hơn hoặc bằng hơn của hạng tử bên trái
  • \<= Kiểm nghiệm sự nhỏ hơn hoặc bằng hơn của hạng tử bên trái

Cú pháp chung là expr ARG2 <Phép_So_Sánh> ARG1

10.4.3 Phép toán lên chuỗi kí tự: Ngoài phép so sánh và phép tính số học, expr cung cấp thêm một số phép toán trên các string: ARG lúc này phải có giá trị của một string (đặt chúng trong ngoặc ' hay " ).  Để thực hiện thì đòi hỏi dùng thêm các từ khoá như  match, substr, index,length

  • expr substr ARG <Vi_Trí> <Độ_Dài> trả về một chuỗi kí tự con của ARG bắt đầu từ vị trí  <Vi_Trí> và có độ dài là <Độ_Dài>.  Nếu <Độ_Dài> lớn hơn độ dài còn lại của ARG thì nó chỉ trả về phần còn lại của ARG
  • expr index ARG string : Trả về chỉ số (hay vị trí) của string trong ARG nếu nó được tìm thấy
  • expr length ARG : Trả về độ dài của string ARG

Lưu ý: Ngoài ra, expr còn cung cấp một số phép toán khác như là phép logic hoặc  |, logic và &, và phép tương hợp với một dạng thức expr match ARG RE ; trong đó, RE là một biểu thức chính quy  (một cú pháp tương đương là expr ARG:RE ).  Sẽ trả về 0 nếu  ARG không tương hợp với RE; ngược lại sẽ là giá trị khác 0 thường là độ dài của RE. Tuy nhiên, trong nhiều trường hợp lệnh  expr này không tương thích được hoàn toàn với hệ điều hành và trở nên khó dùng nên tránh và có thể thay bằng các lệnh khác chẳng hạn như dùng các phần đa trình bài trong bài 2 phần 6, 7, và 8

10.4.4 Thí dụ: 

phép modulo
expr 8 % 3

# 2

Phép cộng
y=5
y=`expr $y + 1`
echo "\$y=$y"
# 6
x=10
sum=`expr $x + $y`
echo "\$sum=$sum"
# 16

Phép so sánh logic
x=2
y=4
res=`expr $x = $y`
echo "evaluation of expression = retunrs $res"
# 0

res=`expr $x \< $y`
echo "evaluation expression < ruturn $res"
# 1

Phép toán trên các string
mystr="abcd 12345abcddd"

#so sánh
str="2345"
if [ `expr $mystr = $str` = 0 ]; then
    echo "comparing of $mystring and $str returns false (0)"
else
    echo "comparing of $mystring and $str returns true (1)"
fi

#trả về chuỗi kí tự con
res=`expr substr "$mystr" 3 4`
echo "string at position 3 with the length of 4 from $mystr is: $res"
# "cd 1"

#trả về chỉ số của chuỗi kí tự con nếu tìm thấy
res=`expr index $mystr $str`
# 5

 10.5 Dùng cơ số trên các biến số

Trong mặc định thì BASH thông dịch các giá trị số trong cơ số thập phân. Có thể dùng lệnh let để sử dụng giá trị một số cơ số không thập phân.  Thí dụ sau đây sẽ chỉ ra cách tiến hành:

Thí dụ:

#Decimal
let    "d=12"
echo "value of d is $d" #it is 12

#Octal
let "o = 012"    #using number zero (0) as prefix for octal
echo "o =$o"     # it is now 12 (octal)=> 10

#Hexadecimal
let "h = Ox1c" #using '0x' as prefix for hexadecimal
echo "h = $h"   # it is now 1c (hex) => 28

#other base between 2 and 64
let "b = 32#15        #format as BASE#VALUE
echo "base 32 predefined value of b is $b" #result 40

11 Tham chiếu gián tiếp tới biến:

Trong thực tiễn, đôi khi người lập trình muốn truy đọc gía trị của một biến mà tên của biến đó lại là nội dung giá trị của một biến khác.  Tức là nếu myVar được mang giá trị "myIndirectVar", và nếu lại có một biến mang tên myIndirectVar chứa giá trị là "XYZ",  thì người ta có thể hoặc là dùng lệnh  eval myNewVar=\$$myVar hay myNewVar=${!myVar} để gán giá trị "XYZ" cho biến myNewVar

11.1 eval <TÊN_BIẾN>=\$$<TÊN_BIẾN_THAM_CHIẾU>: Lệnh này không cho phép xuất (output) trực tiếp giá trị thành một chuỗi kí tự nhưng cho phép gán giá trị (chuỗi kí tự) đó lên một biến khác

Thí dụ:

myVar="Indirect_Var"
#MyVar is assigned a value as "Indirect_Var"

Indirect_Var="Here is the 1st value"
#The Indirect_Var has bên declared and contained the value of "Here is the 1st value"

eval MSG=\$$myVar
echo "here is: $MSG"

Indirect_Var="Here is the 2nd value"

#reassign a new value for Indirect_Var,
#but it cannot be directly output
#and must use eval command to get it

eval MSG=\$$myVar
echo "here is after changing: $MSG"

11.2 <TÊN_BIẾN>=${!<TÊN_BIẾN_THAM_CHIẾU>}: Lệnh này cũng làm cùng một thao tác nhưng có thể cho phép hiển thị trực tiếp giá trị chuỗi kí tự mà nó tham chiếu gián tiếp tới

Thí dụ:

myVar=An_Indirect_Var                  
#MyVar is assigned a value as "An_Indirect_Var"

An_Indirect_Var="Here is the 1st value"
#An_Indirect_Var has a value as "Here is the 1st value"

echo "here is: ${!myVar}"

An_Indirect_Var="Here is the 2nd value" #you cannot directly output
echo "here is after changing: ${!myVar}"

12 Các câu lệnh phân nhánh

12.1 Câu lệnh If:

Như mọi ngôn ngữ lập trình, biểu thức if có thể dùng để tạo sự rẽ nhánh.  Lưu ý rằng trong BASH mỗi mệnh đề if luôn luôn phải có kết thúc bằng từ khoá fi

12.1.1: Câu lệnh if đơn giảnDùng cú pháp sau đây

if [ <Biểu thức_Điều_Kiện> ]; then
       
<Khối_Lệnh>
fi

Hay là :

if [ <Biểu thức_Điều_Kiện> ]
then
        
<Khối_Lệnh>
fi

Hay là :

if [ <Biểu thức_Điều_Kiện> ]; then  <Khối_Lệnh>; fi

Hay là  bất kì dạng tổ hợp nào khi thay thế mỗi kí tự đầu dòng bằng một kí tự ;

Lưu ý: Như một sự nhắc nhớ:  các khoảng trống (space) giữa dấu ngoặc vuông và biểu thức điều kiện cần phải được giữ đúng nếu không muốn bị trình dịch bắt lỗi cú pháp

12.1.2 Câu lệnh phức hợp

if [ <Biểu thức_Điều_Kiện1> ];then
   
<Khối_Lệnh1>
 
elif [
<Biểu thức_Điều_Kiện2> ]
   
<Khối_Lệnh2>

elif [
<Biểu thức_Điều_Kiện3> ]
   
<Khối_Lệnh3>

else   
   
<Khối_Lệnh4>

fi


Từ khoá elif có thể dùng để "kéo dài" số trường hợp phân nhánh ra hoặc rút ngắn lại thành câu lệnh if-else có cú pháp như sau

if [ <Biểu thức_Điều_Kiện> ];then
   
<Khối_Lệnh1>
 
else
   
<Khối_Lệnh2>

fi

Lưu ý:  Như đã biết, thay vì viết từ khoá then trong một hàng mới, bạn luôn luôn có thể thay kí tự xuống hàng bằng ; thành ;then và giữ từ khoá này trong cùng một hàng.  Tương tự cho từ khoá "do" trong các vòng lặp.  Thí dụ: 
if  [ $?=0 ]; then echo "no error found"; else echo "found error: $?"; fi.
Và điều này đúng cho bất kì tổ hợp câu lệnh nào trong BASH

Thí dụ: Đoạn mã sau đây tiến hành lệnh cp (chép tập tin file1 sang thành file2) và sau đó kiểm tra xem kết quả của lệnh cp (có trả về lỗi Khác không nào hay không).  Nếu có lỗi tiến hành lệnh cp, thì kiểm xem tồn tại LOG file thì chép nối vào log file.  Còn không có LOG file thì chỉ hiển thị ra màn hình

LOG="mylog.txt"
....
cp file1 file2
if [ $? = 0 ]; then
   echo "suceed!"
elif [ -e "$LOG" ]; then
   echo "failed" >> "$LOG"
else
   echo "copy failed"
fi

12.2 Phân nhánh dùng lệnh case:

Tương tự, câu lệnh case cho phép phân làm nhiều nhánh cùng lúc thay vì phải dùng tổ hợp if-else

case <TÊN_BIẾN> in
   
<Giá_Trị1>)
       
<Khối_Lệnh_1>
        ;;
   
<Giá_Trị2>)
       
<Khối_Lệnh_2>

        ;;
    ...
    *)   
#default
       
<Khối_Lệnh_Mặc_Định>

        ;;
esac

Thí dụ:

#this program can run only you have X-window;
#the example indicated that it is compatible with wild card characters.
echo -n "chose font color for xterm in x-window: "
read color
case "$color" in
[Bb]l??)
    xterm -fg blue &
    ;;
gree*)
   xterm -fg darkgreen &
   ;;
red | orange)                  #red or orange
   xterm -fg "$color" &
*)
   xterm -fn terminal &
   ;;
esac

Lưu ý: trường hợp không muốn xác định giá trị cụ thể của <TÊN_BIẾN> mà vẩn muốn thi hành tiếp các lệnh điều khiển thì có thề dùng trường hợp *) như là sự mặc định giá trị của biến không thoả mãn bất kì trường hợp thử nào thì sẽ thi hành <Khối_Lệnh_Mặc_Định>  đó. Tuy nhiên, cũng không nhất thiết phải có sử dụng trường hơp *) này trong mỗi câu lệnh case.

13 Các câu lệnh vòng lặp:

Khi có một khối lệnh cần được thực thi nhiều lần, mỗi lần chỉ khác nhau một vài giá trị gán ban đầu của các biến thì người ta có thể dùng kĩ thuật vòng lặp. Những biến có chỉ thị để thay đổi giá trị cài đặt ban đầu cho mỗi lần thực thi khối lệnh của vòng lặp gọi là biến chỉ số (index variable).  Tập họp các giá trị được lần lược gán lên biến chỉ số X theo thứ tự được gọi là  danh sách giá trị của biến X (hay ngắn gọn hơn là <Danh_Sách> )

Bash hỗ trợ 4 loại câu lệnh cho vòng lặp là  for, while, until , và select .

Trước khi vào đề thì một trong những lưu ý quan trọng nhưng rất cổ điển là khi dùng vòng lặp phải nhớ hai điều:
  1. Vòng lặp phải có lối thoát nếu không muốn tạo ra tình trạng treo chương trình , treo máy.
  2. Các điều kiện cực biên phải chính xác.  Nếu điều kiện biên sai sót sẽ dẫn đến tình trạng chương trình có hỏng hóc khó sửa vì sự sai sót chỉ xãy ra ỏ các giá trị cực biên và thông thường chương trình tưởng chừng chạy hoàn hảo cho tới khi .... một trong các biến của vòng lặp đạt giá trị cực biên.

 Bên cạnh đó, còn có các lệnh chuyên dùng trong các vòng lặp để điều chỉnh hướng thực thi

13.1 Điều chỉnh vòng lặp: break continue

Nhiều mệnh lệnh như là break, continue,  exit, exec, ... có thể làm đổi hướng hay ngưng lập tức sự vận hành của các vòng lặp.  Trong phần này chúng ta chỉ giới thiệu sơ lược. Phần chi tiết sẽ bàn tới trong  13.7. Các thí dụ về chúng cũng sẽ được trình bày trong phần kế tiếp

13.1.1 break: Lệnh này sẽ chấm dứt vòng lặp nhỏ nhất chứa nó ngưng ngay lập tức và tiếp tục chạy các mã tiếp theo

13.1.2 continue: Lệnh này sẽ buộc ngưng thi hành phần còn lại của mã và bắt đầu vòng chu kì mới tiếp theo của vòng lặp

13.1.3 Ngoài ra vòng lặp còn có thể ngưng một khi lệnh exit, exec (hay các lệnh có tính kết thúc tiến trình hiện tại để thi hành việc khác hay để trả về hệ điều hành)

13.2 Cách thành lập <Danh_Sách> cho một vòng lặp

Như trong vòng lặp for của C/C++, trong 1 vòng lặp thì danh sách có thể là các số nguyên tăng (hay giảm) một hằng số nguyên và cách thành lập này hoàn toàn tương tự.  Bạn có thể xem lại cú pháp cho lệnh for.

Ngoài ra, BASH còn hỗ trơ các hình thức tạo lập danh sách khác dùng trong lệnh for và lệnh select như sau

13.1.1 Dùng mặc định

Theo mặc định thì một <Danh_Sách> dùng trong vòng lặp for có thể là một trong các dạng:

13.1.1.1 Các tên chuỗi kí tự phân cách bởi khoảng trống (space)

Thí dụ1: 

for FRUIT in  'apple' 'apricot' 'lemon' 'plum' 'orange'; do
     if [ "$FRUIT" = "lemon" ]; then
         echo "lemon found.  Using 'continue' key word to jump over this record"
         continue
     if
     echo "The fruit is $FRUIT"
done

13.1.1.2 Danh sách các số phân cách bởi khoảng trống (space)

Thí dụ2:

for i in 1 2 3 4; do
     echo "$i"
done

13.1.1.3 Danh sách các tên như thí dụ 1,2,3 nhưng phân cách bởi kí tự xuống hàng

Thí dụ3:

LIST="/root/file1
/etc/file2
/var/spool/file3"
for FILE in $LIST; do
     if [ -e $FILE ]; then
          echo "file $FILE exist"
     else
          echo "file $FILE not exist. Stop any loop by 'break' command"
          break
    fi 
done

13.1.1.4 Danh sách có thể cho được từ các tên dùng kí tự phỏng định

Thí dụ4:

for FILE in [abc]*; do
     rm -f $FILE
done
#this script will remove all files whose name beginning by character a, b, or c in current directory

13.1.1.5 Danh sách có thể cho được từ các dòng hiển thị của một khối lệnh (hay một mệnh lệnh):

Thí dụ5:

for file in $( find $directory -type l )   # -type l = symbolic links
do
  echo "$file"
done | sort
#this for loop will search all soft link file and output after sort them

13.1.2 Ngăn cách giữa các miền cho một  <Danh_Sách>: Thật ra, danh sách các tên tạo ra mà chúng ngăn cách nhau một cách mặc định đã được quy định trước bởi giá trị của biến ngăn cách (separator) IFS trong BASH.  Đó là các giá trị kí tự khoảng trống, kí tự nhảy bước, hay kí tự xuống hàng (space,tab,newline). Chúng ta hoàn toàn có thể cài đặt lại giá trị này (chứa trong $IFS ) để sử dụng và trả lại giá trị ban đầu sau khi dùng xong. Thí dụ sau đây minh hoạ cho thao tác này:

Thí dụ:
# file runnit
#!/bin/bash
fruits=plum:orange:grape:banana 
#the 'fruit' list using ':' as separate character
old="$IFS"   #save the current shell separator
IFS=":"      #reassign the new ':' separator
for FRUIT in $fruits
do
     echo "this is $FRUIT
done
IFS="$old"   #returning original value

13.3 for : Biến chỉ số <TÊN_BIẾN> sẽ lần lượt lấy các giá trị trong <Danh_Sách> và thi hành  theo thứ tự trừ khi bị ngắt ngang bởi các lệnh điều chỉnh vòng lặp như break, continue , hay exit, exec.  Sau đây là một số cú pháp tiêu biểu

  • for <TÊN_BIẾN> in <Danh_Sách>; do
          
    <KHỐI_LỆNH>
    done
     
  • for <TÊN_BIẾN> in <Danh_Sách>
    do
          
    <KHỐI_LỆNH>
    done
     
  • for ((a=MIN; a <= MAX; a++))
    do
         
    <KHỐI_LỆNH>
    done
     
  • for ((a=MIN1; b=MIN2;  a<MAX; a++; b++))
    do
         
    <KHỐI_LỆNH>
    done
  • for (( <Biểu_Thức1>; <Biểu_Thức2>; <Biểu_Thức3> )) ; do  <KHỐI_LỆNH> ; done

    Trong đó,  <Biểu_Thức1> sẽ được đánh giá sau đó là việc đánh giá của biểu <Biểu_Thức2> và mỗi lần <Biểu_Thức2> có giá trị khác 0, thì khối lệnh  sẽ được thi hành và sau đó sẽ là việc đánh giá <Biểu_Thức3>. Nếu bất kì biểu thức nào không có mặt thì chỗ thiếu vắng đó sẽ được xem như là một biểu thức luôn luôn có giá trị 1. Giá trị trả về của vòng lặp này là trạng thái thoát (exit status) của mệnh lệnh sau cùng trong khối lệnh, hay trả về giá trị "false" nếu các biểu thức là không hợp lệ.

Như là một bài tập, hãy thử nghiệm các vòng lặp for với biến chỉ số giảm dần thay vì tăng như trong cú pháp nêu trên; đồng thời hãy thử dùng giá trị cực tiểu (MIN) ỏ trên là số âm để xem khả năng hỗ trợ của BASH.

Lưu ý: Việc trình bày bất kì một khối lệnh hay câu lệnh nào trong BASH đều có thể thay các kí tự xuống hàng bởi các dấu ;

Thí dụ1: 
for fruit in "red apple" "yellow orange"   "green grape"
do
     echo "color fruit :  $fruit"
done

Thí dụ2:

for CHAR in `cat $myFile.txt`; do
     echo  "$CHAR"
done

Xem thêm thí dụ về vòng lặp for trong phần nói về danh sách

13.4  while : Biểu thức  <Biểu thức_Điều_Kiện> sẽ được kiểm nghiệm cho tới khi nó không còn được thoả mãn thì vòng lặp sẽ bị ngừng . Các cú pháp tương đương hay thấy là:

  • while [ <Biểu thức_Điều_Kiện> ]; do
        
     <KHỐI_LỆNH>

    done
  • while [ <Biểu thức_Điều_Kiện> ]
    do
          
     <KHỐI_LỆNH>
    done
  • while [ <Biểu thức_Điều_Kiện> ]; do <KHỐI_LỆNH>; done

Thí dụ1:  

var=0
MAX=10

while [ "$var" -lt "$MAX" ]
do
    echo -n "$var "        # -n suppresses new-line.
    var=$(($var+1))
done
#revise the above while statement using 'break' keyword
var=0
MAX=10
while (true); do
    echo -n "$var "        # -n suppresses new line.
    var=$(($var+1))
    if [ $var -eq 10 ]; then
        break
    fi
done

Thí dụ2:
#Example of multiple condition line of while statement
#!/bin/bash
var=init
pre=$var

while echo "previous  variable = $pre"
      echo
      pre=$var
      [ "$var" != "q" ]
      # Four conditions on "while", but only the last one controls loop.
do
    echo -n "Input variable (enter 'q' to exit) :"
    read var
  echo "variable = $var"
done

Thí dụ3:

#example while using C style
#BASH STYLE:"
LIMIT=10
a=1
while [ "$a" -le $LIMIT ]
do
  echo -n "$a "
  let "a+=1"
done           # No surprises, so far.
echo; echo

# Now,  with C style.
((a = 1))      # a=1
# Double parentheses permit space when setting a variable, as in C.
while (( a <= LIMIT ))   # Double parentheses, and no "$" preceding variables.
do
  echo -n "$a "
  ((a += 1))   # let "a+=1"
  # Double parentheses permit incrementing a variable
done

13.5 until: tương tự như vòng lặp while nhưng  <Biểu thức_Điều_Kiện> được kiểm nghiệm trước cho tới khi giá trị của nó là "true" thì vòng lặp sẽ bị ngừng

  • until [ <Biểu thức_Điều_Kiện> ]; do
        
    <KHỐI_LỆNH>

    done
  • until [ <Biểu thức_Điều_Kiện> ]
    do
       
    <KHỐI_LỆNH>
    done
  • until [ <Biểu thức_Điều_Kiện> ]; do <KHỐI_LỆNH>; done

Thí dụ:
#!/bin/bash
var=
until [ "$var" = "q" ] # Tests condition here, at top of loop.
do
     echo -n "Input variable (enter 'q' to exit :"
     read var
     echo "variable = $var"
done 

13.6 select:  Đây là dạng vòng lặp mô phỏng của trình bao Korn. Nó có ích để tạo một trình đơn (menu) lựa chọn từ một danh sách của tên mà biết chỉ số sẽ lần lược được gán.

  • PS3="<Dòng_Thông_Báo># it will be prompted in the 'select' statement
    ...
    select
    <TÊN_BIẾN> in [
    <Danh_Sách> ]
    do
        
    <KHỐI_LỆNH>
         break;
    done

Thí dụ:
#!/bin/bash
PS3='Choose your favorite fruit: ' # Sets the prompt string.
#this is default variable of 'select' statement
echo
select FRUIT in "orange" "grape" "banana" "plum" "mango"
do
      echo "Your fruit is $FRUIT."
      echo "Yummy!"
      echo
      break  # without  'break'  the loop will be infinity
done

13.7 Vòng lặp lồng nhau (net loop) và điều chỉnh vòng lặp: Để tránh lỗi mập mờ (ambiguity), lệnh break continue trong các  vòng lặp lồng nhau có hỗ trợ về cấp độ của vòng lặp chịu ảnh hưởng bởi sự thi hành của nó.

  • break <n> : Với <n> là cấp độ của vòng lặp sẽ bị điều chỉnh bởi  lệnh break .  Trong đó, break 1 (hay viết đơn giản break ) sẽ chỉ ảnh hưởng đến vòng lặp trong cùng nhất có chứa từ khoá  break . thí dụ break 2 sẽ chỉ có ảnh hưởng tới vòng lặp kế bao ngoài vòng lặp trong cùng (cấp 2)
  • continue <n> : Hoàn toàn tương tự continue n sẽ có hiệu lực đối với vòng lặp cấp n

Thí dụ:
#!/bin/bash
#name nestdemo
for month in jan feb mar apr may jun jul aug sep oct nov dec; do
    for week in 1 2 3 4; do 
        echo -n "Start processing the month of $month. (y/n)? :"
        read ans
        if [ "$ans" = "n" ] || [ -z "$ans" ]; then
             continue 2 
             #skip the rest go back to next value of $month
             # from the outer loop
        else
             echo -n "Start processing week $week of the month $month (y/n)?"
             read ans
             if [ "$ans" = "n" ] || [ -z "$ans" ]; then
                  continue     
                  #skip the this inner most loop
             else
                  echo "Now processing week $week og the month $month"
                  #do  some commands
                  echo "Done"
             fi
       fi
    done
done


A10.  Biểu thức chính quy (Regular Expressions)

Một  biểu thức chinh quy là một dạng thức chung của những chuỗi các kí tự khác nhau. Dạng thức này được biểu thị bởi một chuỗi kí tự đặc biệt thường  được dùng trong một cuộc tìm kiếm. Mỗi dạng thức như vậy thường được đóng giữa hai kí tự gọi là các kí tự giới hạn (delimited character) thường là kí tự / .  (Tuy nhiên, tuỳ theo nhu cầu, kí tự giới hạn có thể được định nghiã lại). 

Thí dụ: với biểu thức  /Bob/ thì dạng thức Bob sẽ tương hợp với các chuỗi kí tự (string) có chứa chuỗi "Bob" ở bất kì vì trí nào nó (như là các chuỗi kí tự BabyBob, my Boby,  Bob, 12Bob, (Bob) đều thoả mãn điều kiện tìm kiếm đối với dạng thức Bob.) Một biểu thức chính quy thường gọi tắt là RE (từ chữ Regular Expressions).  Một chuỗi kí tự S thoả mãn dạng thức của một biểu thức chính quy R thì S được gọi là  tương hợp (với dạng thức của biểu thức R)

Một biểu thức chính quy R có thể là tổ hợp của nhiều biểu thức chính quy thành phần SR. Khi đó, ta bảo SR là biểu thức chính quy con (hay gọn hơn là biểu thức con) của R

Để mô tả dạng thức của các chuỗi kí tự cần tìm, trong mỗi biểu thức chính quy, sẽ cần sử dụng một số kí tự mà các kí tự này được gán cho những ý nghiã mới khác với ý nghiã của một kí tự thông thường. Những kí tự được quy uớc để có riêng các ý nghiã đặc biệt được gọi là các siêu kí tự. Nhiều mệnh lệnh trong Linux sẽ chấp nhận và sử dụng các siêu kí tự này (và đôi khi với ý nghiã không hoàn toàn giống nhau cho từng lệnh) trong đó có các lệnh vi, grep, sed, awk, perl

Các siêu kí tự thông thường

(Lưu ý: Các dấu ' dùng trong bảng là để phân định biên giới của chuỗi thí dụ - nhưng không thuộc nội dung của các chuỗi kí tự này)

Siêu Kí tự Ý nghiã Thí dụ (RE) Một số string tương hợp
(Ngăn cách bởi dấu ,)
Sẽ không tương hợp với
(Ngăn cách bởi dấu ,)
^  Bắt đầu của dòng chữ  /^Hello/ Tất cả các dòng bắt đầu với  'Hello' 'A Hello',' Hello','_Hello'
$ Kết thúc dòng  /the END$/ Tất cả các dòng kết thúc bởi
'the END'
' END','the END.','the end'
.  Một kí tự bất kì ngoại trừ dấu xuống hàng  /B..l/ 'Bill', 'B12l', 'B#(l'; ... 'Bil','Tool','Boom'
* Dãy kí tự bất kì tiếp theo sau kí tự ngay trước nó  /ab*c/ 'abc','abbxbc', 'ac',... 'abb','abcc'
[ ] Tương hợp với một kí tự duy nhất nào đó bên trong ngoặc vuông  /t[ou]/ 'touch','tune','so stupid' 'do', 'tio', 'dual', 'two', 'tiu'
[x-y] Tương hợp với một kí tự duy nhất nào đó trong dãy thứ tự ASCII từ x đến y  /^[A-Z]ove/ Tất cả các string bắt đầu với một chữ in hoa và nối theo là 'ove' như là: Love, Move,... 'love', 'aove', 'myLove'
[^ ] Tương hợp với một kí tự sao cho nó không có mặt trong ngoặc vuông  /file[^1-9]/ 'file' , 'file0', myfileA file1','xfile2',..., 'myfile9'
\ Kí tự thoát cho các siêu kí tự. Khi đó  siêu kí tự đứng ngay sau dấu /sẽ không còn ý nghiã đặc biệt mà chỉmang ý nghiã thông thường  /the end\./ 'the end.','to the end. (y/n)?' 'the endy', 'the end\.'

Những siêu kí tự thêm vào có thể được hỗ trợ bởi nhiều chương trình UNIX/Linux
 
\< Bắt đầu biên giới của một từ (word)  /\<Poison/ 'OK Poison1' ' Poisoning' 'EulerPoison', '2Poison'
\> Kết thúc biên giới của một từ  /Cap\>/  'No Cap','HCap y', 'Cap.' 'Capital'
\(  \) Các thẻ quy ước \<n>: dùng để thay thế cho chuỗi các kí tự tương hợp trong ngoặc đơn.
\1, đại diện cho RE \(  \) thứ nhất,
\2
đại diện cho RE \(  \) thứ nhì ....
Có tổng cộng 9 thẻ quy ước \1, \2, ...\9
 /\(hard\)ship \1y/ RE  \(  \) đầu tiên từ bên trái kí hiệu bởi \1
RE  \(  \) kế tiếp kí hiệu là  \2,..
Thí dụ này tường đương với biểu thức
/hardship hardy/
 
x\{m\} lập lại kí tự x m lần   /ab\{3\}c/  tương đương với RE /abbbc/  
x\{m,n\} Lập lại kí tự x ít nhất m lần và không quá n lần  /ab\{3,5\}cd/  'abbbcd', ' abbbbcde', 'abbbbbcdt' 'abbcd',  'abbbbbbcd'

Thí dụ:

/[a-z0-9]/    : là RE tương hợp với tất cả các dòng chữ có chữ cái không viết hoa hay các kí tự số
/[0-9]*\*$/   :
là RE tương hợp với tất cả các dòng chữ có một kí tự số theo sau đó là một dãy kí tự bất kì và dòng này kết thúc bởi dấu *
/^A\<f.th\>/  :
là RE tương hợp với tất cả các dòng chữ bắt đầu bằng từ "A" sau đó từ thứ nhì bắt đầu bằng chữ f tiếp theo là một kí tự nào đó và kết thúc của từ này bằng hai kí tự th.

Trong các phụ lục của bài này và các bài sau đó sẽ có hướng dẫn dùng các lệnh quan trọng của Linux có hỗ trợ chế độ dùng RE

A11 Lệnh grep

Lệnh grep rất thông dụng để tìm và hiển thị lại các dòng tương hợp của một dạng thức cho sẵn trong một hay nhiều tập tin. Nếu dạng thức có chứa các kí tự không thấy được (như là các kí tự khoảng trống, nhảy bước, hay xuống hàng) thì dạng thức dùng trong mệnh lệnh phải được để trong các dấu ngoặc dùng cho chuỗi kí tự. grep in ra kết qủa lên stdout và sẽ không thay đổi nội dung hay thuộc tính cài đặt từ trước của các tập tin mà nó đọc.  Ngoài các siêu kí tự thông thường,  grep hỗ trợ khá nhiều các siêu kí tự thông thường và hỗ trợ thêm một số khác nữa. 

A11.1 lệnh grep:

Cú pháp cho lệnh grep là:

grep <Tham_Số> <Dạng_thức> <TÊN_CÁC_TẬP_TIN>

Lưu ý: như các lệnh khác, lệnh grep thường hay được dùng thông qua một ngỏ xuất từ một ống dẫn truyền một cách rất tiện lợi (xem thí dụ dưới đây).

Thí dụ1:

#print the line of name.txt that contains substring 'Thomas' or 'thomas'
grep '[Tt]homas' /data/name.txt  name2.txt 

# using pipe output from ps -A command find all lines contains tty followed by any character
ps -A | grep 'tty.'  

A11.2 lệnh grep mở rộng

Ngoài ra, nếu dùng tham số -E thì lệnh grep được gọi là grep mở rộng (extended grep). Lệnh mở rộng này có định nghiã thêm một số siêu kí tự mới. Và như vậy cú pháp viết thành:

grep -E <Tham_Số_Khác> <Dạng_thức> <TÊN_CÁC_TẬP_TIN>

Các siêu kí tự thêm vào để dùng với lệnh grep -E

Siêu kí tự Ý nghiã Thí dụ (RE) Tương hợp với  Không tương hợp với
\w kí tự là một chữ cái hay một chữ số  f\w*\.dat  file.dat, f1.dat, ...  f_.dat, f-i.dat
\W kí tư không phải là chữ cái hay chữ số  file\W  tương đương với RE: file[^a-zA-Z0-9]  f1, file
\b biên giới của một từ  \bfile\b  ' file', ' file '  myfile, file1
+ Ít nhất một kí tự (hay nhiều hơn) tương hợp
(trùng) với kí tự đứng liền ngay trước dấu +
 [a-z]+hood  childhood, robinhood, thood  ROBINhood, 1hood
? Có tối đa một kí tự tương hợp
(trùng)với kí tự liền trước nó
 bo?t  boot, bot  booot, bt
| Tương thích hoặc  love|like| want  love, like, want  
( ) Các dạng thức nhóm trong ngoặc  want(ed|ing|er)  wanted, wanting, wanter  

Thí dụ: 
nội dung của file.dat:

#remark line
#this is data file of my flight
  SouthWest   SW  Houston   Dallas         8:00  9:00  40
  NorthWest   NW  NewYork   OrangeCounty   6:00  12:00 65
  continental CT  Hongkong  L.A.           7:00  8:00  125
  -a NEW      CTT Hongkong  LA          6:00  19:00 212
  Western     CT2 Hanoi     Houston  


Grep commands
cat file.dat | grep West   
(Hai dòng đầu và dòng cuối sẽ tìm thấy và in ra)
grep -E '\bCT\b'  file.dat
(Chỉ tìm thấy dòng thứ 3 và in ra)

POSIX:  Bên cạnh các siêu kí tự kể trên, grep -E còn hỗ trợ các dạng POSIX ( portable operating system Interface  nghiã là "giao diện hệ điều hành khả xuất"). Đây là tiêu chuẩn kĩ nghệ để bảo đảm các chương trình là khả xuất (portable) qua các hệ điều hành

Kí tự POSIX hỗ trợ bởi grep
(Tất cả các thí dụ đưới đây dùng cùng một nguồn là file.dat)

Lớp ngoặc vuông Ý nghiã Thí dụ Trả về từ file.dat
[:alnum:]  các kí tự chữ và số  grep -E 'CT[[:alnum:]]' file.dat  Hiển thị hai hàng cuối
[:alpha:]  kí tự chữ  grep -E 'CT[[:alpha:]]' file.dat  Dòng thứ 4
[:cntrl:]  kí tự điều khiển
(như F1, F2,...)
   
[:digit:]  kí tự số  grep -E 'CT[[:digit:]]' file.dat  Dòng cuối cùng
[:graph:]  kí tự không phải là
 các khoảng trống
 grep -E '^[[:graph:]]' file.dat  Hai dòng đâu
[:lower:]  kí tự dạng chữ thường    
[:print:]  giống như [:graph:] nhưng  cộng
 thêm các kí tự khoảng trống
   
[:punct:]  kí tự chấm câu  grep -E 'L[[:punct:]]' file.dat  Dòng thứ 3
[:space:]  các kí tự khoảng trống
(đầu dòng, không khí, tab)
 grep -E '^[[:space:]][[:space]]' file.dat  Tất cả các dòng ngoại trừ hai dòng đâu tiên.
[:upper:]  Viết Hoa    
[:xdigit:]  số trong hexadecimal    

A11.2 Các tham số thông dụng:

Sau đây là một số tham số rất thông dụng của lệnh grep:

  • -A <Số_Dòng> : Hiển thị dòng tìm thấy tương hợp và hiển thị tiếp số <Số_Dòng> các dòng tiếp sau dòng tương hợp này

  • -a : xử lí tập tin nhị phân xem như nó là tập tin văn bản.  Thí dụ: Hãy thử so sánh hiển thị trả về của hai lệnh 
    grep 'do not sort' /bin/ls
      và
    grep -a 'do not sort' /bin/ls

  • -B <n>: in ra luôn số dòng của các dòng chữ tương hợp ở đầu mỗi dòng hiển thị (chẳng hạn: grep -B 2 'Hanoi' file.dat )

  • -b: in thứ tự byte (byte offset) của  các dòng tương hợp xuất hiện trong tập tin  (chẳng hạn: grep -b 'Hanoi' file.dat )

  • -c : Chỉ in ra số các dòng tương hợp (mà không in bản thân dòng có chuỗi kí tự tương hợp này) (thí dụ: grep -c Houston file.dat )

  • -i: Không phân biệt chữ in hoa hay in thưòng  (thí dụ: grep -i hongkong file.dat )

  • -l: In tên tập tin có dòng tương hợp thay vì dùng định dạng thông thường.( grep -l Hongkong file.dat )

  • -m <n>: ngưng đọc từ ngỏ vào nếu như đã đạt tới n dòng tương thích ( grep -m 1 Hongkong file.dat)

  • -n: In số thự tự của dòng trước khi in mỗi dòng tương hợp  ( grep -n Hongkong file.dat)

  • -r: đọc thi hành lên tất cả các tập tin có trong thư mục (thi hành grep đệ quy)

A12 Lệnh xargs:

Đây là một lệnh rất hữu hiệu để chuyển các ngỏ ra hay các thông báo từ các ngỏ ra chuẩn thành các tham số của những mệnh lệnh khác nhằm thực thi cùng thao tác (một lệnh hay 1 nhóm lệnh) trên nhiều đối tượng khác nhau (các đối tượng này đóng vai trò tham số)

xargs sẽ đọc từng dòng từ ngỏ vào chuẩn của nó để chuyển  thành một tham số để thi hành cùng một lệnh cho mỗi dòng đó lần lượt. 

Chẳng hạn, nếu ngỏ ra cuả một mệnh lệnh nào đó bao gồm  3 dòng X1, X2, X3 và ngỏ ra này được ống dẫn truyền (pipe)  cung cấp cho lệnh xargs để thực thi một lệnh L thì lệnh xargs sẽ thự thi tổng cộng 3 dòng lệnh L1, L2, L3. Trong đó, dòng lệnh L1 sẽ lấy X1 làm tham số. L2 sẽ lấy X2 làm tham số và L3 lấy X3 làm tham số.  Các thí dụ sẽ minh hoạ rõ hơn điều này.

Cú pháp thông dụng nhất là:

<Lệnh_1> | xargs <Tham_Số> <Lệnh_2>

Nếu <Lệnh_2> không có mặt thì xargs -i chỉ hiển thị ra màn hình theo sự cài đặt của <Tham_Số>.

<Lệnh_1> thường là các lệnh xuất ra nhiều dòng tới stdout, mỗi dòng (hay một vài dòng)  như vậy sẽ được gom lại vào thành một bộ tham số cho các dòng <Lệnh_2> theo thứ tự một cách lần lượt cho đến khi không còn dòng xuất nào nữa từ <Lệnh_1>

A12.1 Ứng dụng

  • Một trong những kết hợp hay thấy nhất là dùng lệnh find để tìm các tập tin đặc trưng rồi từ đó dùng lệnh xargs để chuyển chúng sang thực thi các mệnh lệnh khác lên tên các tập tin vưà tìm thấy. (Sự kết hợp các lệnh có thể dùng với rất nhiều lệnh khác như grep, sed hay awk

Thí du1:

Thí dụ sau sẽ chuyển tất cả các tập tin bắt đầu bằng my và có phần mở rộng là .txt trong thư mục test (kể cả các tập tin trong các thư mục con của test) vào trong một thư mục mới tên là mydir
find ./test -name "my*.txt" | xargs -i mv {} ./mydir

Ở đây,  kí hiệu {} thay thế cho các dòng xuất ra từ lệnh find ./test -name "my*.txt". Như vậy, cứ mỗi tập tin dạng my*.txt tìm được qua lệnh find thì tên (đầy đủ) của tập tin này sẽ được thay vào trong ngoặc {} để lần lượt được lệnh mv <tên_tập_tin_cung cấp_từ_lệnh_find> ./mydir  di chuyển nó vào trong thư mục ./mydir.

Lưu ý:  trong một số trường hợp (như là khi dùng với các lệnh hỗ trợ biểu thức chính quy chẳng hạn) thì người dùng thay vì dùng kí hiệu {}, có thể định nghiã lại một cặp kí hiệu khác chẳng hạn như căp kí hiệu ~~, $$, [], ##, hay bất kì kí hiệu nào ... Tuy nhiên, một số kí hiệu cần phải dùng chung với kí tự thoát \ để tránh gây lỗi mập mờ (ambiguity).

Như vậy lệnh trên hoàn toàn tương đương với lệnh
find ./test -name "my*.txt" | xargs -i[] mv [] ./mydir 

Ở đây, {} đã được thay bằng cặp kí tự []
Trong thí dụ trên, người dùng có thể thay lệnh mv và lệnh find bởi các lệnh thích hợp để làm các thao tác khác

Thí du2:  Lệnh grep sau đây tìm tất cả các tập tin kết thúc bởi txt được liệt kê trong filelist và chuyển cho lệnh xargs lấy làm tham số cho lệnh rm để xoá các tập tin đó.

#content of "filelist"
./test/mytest.txt
./myfiletxt
./introduction.pdf

Khi thực thi lệnh
grep 'txt$' filelist |xargs -i rm -f {}
thì 2 dòng
./test/mytest.txt
./myfiletxt
sẽ được grep lọc lựa và hiển thị thông qua ống dẫn truyền sẽ đưuợc lệnh xargs tiến hành thành 2 dòng lệnh:
rm -f ./test/mytest.txt
rm -f ./myfiletxt
Hậu quả là 2 tập tin trên sẽ bị xoá (nếu có)

  • Dùng để kết hợp nhiều dòng hiển thị trong ngỏ ra chuẩn thành 1 dòng duy nhất

Thí dụ3:

Lệnh sau đây sẽ hiển thị thời gian và chào người ra lệnh trong cùng một hàng.
(date; echo "Hello, `whoami`.") |xargs

  • Dùng để định dạng lại việc hiển thị các tập tin dữ liệu có cấu trúc đơn giản, hay hiển thị lại thành 3 cột dữ liệu của một tập tin bất kì thay vì chỉ có một cột

Thí dụ4:

Lệnh sau sẽ hiển thị lại tên, họ, ngày sinh, nghề nghiệp của một tâp tin
#content of personal.txt
#first line is name
#secondline is DOB
#last line is Ocupation

Hung Nguyen
12/12/90
Engineer

PhuongDung Vo
04/28/63
Pharmacist

Long Le
08/03/79
Dentist

#End of file personal.txt

Lệnh đơn giản sau đây sẽ hiển thị các thông tin về một người (gồm 3 dòng) trong một dòng. Dòng bỏ trắng sẽ bị tự động loại ra (bởi lệnh xargs), các đòng bị chú sẽ bị loại bỏ bởi lệnh grep -v
grep -v "^#" personal.txt | xargs -n3

Một ứng dụng khác của tham số -n là dùng để cung cấp nhiều tham số cho cùng một lệnh.

Thí dụ5:  Thực thi lệnh chép các tập tin có tên mysourcefile<i> thành mytargetfile<i>   mà danh sách đã được chuẩn bị sẵn trong tập tin myhandle như sau:

#content of myhandle
mysourcefile1
mytargetfile1

mysourcefile2
mytargetfile2

mysourcefile3
mytargetfile3
#End of myhandle

grep -v '^#' myhandle |xargs -i -n2 cp -f

Trong thí dụ trên, lệnh grep sẽ hiển thị nội dung của tập tin myhandle ngoại trừ các dòng bắt đầu bằng dấu # (tức là loại trừ các dòng bị chú). Sau đó, lệnh xargs sẽ đưa một lúc 2 dòng (tương ứng với -n2) vào cho lệnh cp. Dòng đầu sẽ là tên tập tin nguồn (mysourcefile<i> ) cho tham số thứ nhất của lệnh cp và dòng thứ nhì (mytargetfile<i> ) trở thành tham số thứ nhì cho lệnh copy.  Nghiã là Nếu các mysourcefile<i> tồn tại thì chúng sẽ lần lượt được chép (copy) thành mytargetfile<i> một cách tương ứng.

A12.2 Các tham số thông dụng cho lệnh xargs:

  • -a <Tên_Tập_Tin> : Đọc ngỏ vào từ <Tên_Tập_Tin> thay vì từ ngỏ vào chuẩn. Nếu dùng tham số này, stdin sẽ không thay đổi khi các lệnh đang chạy. Ngoài ra, stdin sẽ được chuyển hướng tới /dev/null.

  • -0 Các dòng thông tin đọc từ ngỏ vào được kết thúc bởi kí tự null thay vì bởi các kí tự khoảng trắng (whitespace); ngoài ra, các dấu ngoặc (" , ' ) cũng như dấu nghiên về (backslash) \   không còn được mang ý nghiã đặc biệt nữa. Hữu dụng khi ngỏ vào có chứa các kí tự trắng, các dấu ngoặc, hay dấu nghiêng về. Lệnh dùng tham số dạng find -print0 của GNU sẽ được tiện lợi với cách gọi này.

  • -i<Chuỗi_Thay_Thế> Thay vì dùng cặp kí hiệu mặc định là {}, có thể dùng bất kì kí hiệu thay thế nào.  Cặp kí hiệu đó sẽ trong khi thực thi sẽ được thay thế bằng mỗi dòng xuất ra từ stdout và thi hành lần lượt  cho đến khi không còn dòng nào từ stdout nữa.
    Lưu ý: Một khi dùng <Chuỗi_Thay_Thế> (hay dùng {} trong mặc định) để cung cấp tham số cho mệnh lệnh cần thi hành (<Lệnh_2>) thì tham số này sẽ lấy quyền ưu tiên đối với tham số -n<Số_Đối_Số_Tối_Đa> và do đó, có thể tạo ra các hiệu ứng không mong muốn.  Do đó, nên tránh viết <Chuỗi_Thay_Thế> (hay {} ) vào trong dòng lệnh như là tham số nếu người dùng dự tính khai thác tham số -n

  • -n<Số_Đối_Số_Tối_Đa> Sử dụng <Số_Đối_Số_Tối_Đa> làm các đối số cho mệnh lệnh mà xargs gọi.

  • -p Hiển thị câu hỏi sự chuẩn y của người dùng trước khi lệnh được thi hành.  Lệnh được thi hành chỉ khi người dùng trả lời `y ' hay `Y '

  • -r Nếu stdin của xargs chỉ là các dòng trắng thì không thực thi mệnh lệnh

A13 Ôn luyện và đào sâu về sử dụng đổi hướng và Ống truyền tên

A13.1 Đổi hướng

Đổi hướng: là quá trình bắt các thông tin ngỏ ra từ một mệnh lệnh, một tập tin, một chương trình hay ngay cả một đoạn mã và gửi chúng vào ngỏ vào của một mệnh lệnh, một tập tin hay một chương trình khác. trong bài 1 chúng ta đã nói về việc đổi hướng. Giờ là lúc đào sâu thêm các chi tiết.

Bộ mô tả tập tin (file descriptor): Tương tự C/C++ hay các ngôn ngữ khác, mỗi tập tin được mở có thể được gán lên đó một bộ mô tả tập tin (gọi tắt là bộ mô tả).  Trong các văn lệnh, mỗi bộ mô tả có thể được gán cho một con số. Riêng các con số 0, 1, 2 được mặc định lần lược dùng cho các bộ mô tả stdin, stdout, và  stderr. Các con số khác từ 3 tới 9 có thể được dùng để gán cho bất kì tập tin được mở nào khác.

Việc đổi hướng có thể được hoàn tất bằng cách dùng các kí tự đổi hướng,  bằng cách kết hợp với lệnh exec, hay  bằng cách kết hợp với các vòng lặp và rẽ nhánh

A13.1.1 Dùng kí tự đổi hướng

  • > <TÊN_TẬP_TIN> :   Đổi hướng vào <TÊN_TẬP_TIN> xoá nội dung có sẵn từ trước của <TÊN_TẬP_TIN>

Thí dụ1:  ls -l > list_file.txt
cat list_file.txt

Thí dụ2:  mount /dev/df0 /mnt/flopy  1>/dev/null  2>/dev/null   
# redirect output and error messages into  NULL (hide messages released from 'mount')

  • : > <TÊN_TẬP_TIN>  : Cắt bỏ <TÊN_TẬP_TIN> thành tập tin có độ dài 0 byte

  • >> <TÊN_TẬP_TIN> :  Chép nối stdout vào nội dung của  <TÊN_TẬP_TIN> (tạo ra tập tin mới nếu nó chưa tồn tại)

Thí dụ3: 

LOG=./logfile.txt
echo  "date" > $LOG
echo "the content of /home directory :" >> $LOG
ls -l >> $LOG
# to read what is in log file, invoke command cat ./logfile.txt

  • 2>&1 đổi hướng stderr sang stdout ( tức là chuyển các thông bao lỗi vào cùng một ngỏ ra chuẩn)

  • m>&n đổi hướng tập tin có bô mô tả m sang thành n

  • >&n    đổi hướng stdout sang n

  • n<> <TÊN_TẬP_TIN>  : mở <TÊN_TẬP_TIN> cho việc đọc và viết

  • n<&- đóng ngỏ vào của bộ mô ta n

  • <&- Đóng stdin .

  • n>&- Đóng ngỏ ra của bộ mô tả n

  • >&- Đóng stdout

  • < <TÊN_TẬP_TIN> : nhận ngỏ vào từ <TÊN_TẬP_TIN>

Thí dụ4: read all content of hard drive /dev/hdb and save into a file named as rawbackup.dat
dd < /dev/sdb > ./rawbackup.dat

note:  you may also compress the output file rawbackup.dat to save space! However, the file must be decompressed in each restore process

Thí dụ5:  restore the rawbackup.dat into the hard drive /dev/hdc
dd <./rawbackup.dat >/dev/hdc

A13.1.2 Dùng kết hợp với lệnh exec: 

exec < <TÊN_TẬP_TIN> được dùng để đổi hướng đọc từ stdin sang đọc từ một tập tin. Cho nên sau khi sử dụng lệnh này, mọi dữ liệu đều được đọc từ tập tin  <TÊN_TẬP_TIN> thay vì từ stdin. Ứng dụng của nó là có thể dùng văn lệnh để đọc hay điều chỉnh nội dung của một tập tin

Thí dụ6:
#!/bin/bash
# Redirect stdin by 'exec'.

exec 4<&0             # save stdin to descriptor #4.
exec < data.txt       # stdin  is now replaced by file "data.txt"
read line1                # Read the first line of file "data.txt".
read
line2                # Read second line of file "data.txt"
read
line3                # and the third line

echo
echo "3 first lines read from data.txt are:"
echo "1st line: $
line1
echo "2rd line: $
line2
echo "3th line: $
line3"
echo; echo; echo
exec 0<&4 4<&-   # restore the original stdin back
exit 0

Thí dụ7:
echo 1234567890 > MyFile    # Write string to "File".
exec 5<> MyFile             # Open "File" and assign fd 5 to it.
read -n 2 <&5               # Read only 1st 2 characters; current cursor move to offset 2
echo -n 'hi' >&5            # Write a 'hi' there.
exec 5>&-                   # Close fd 5.
cat MyFile                  # Should be 12hi567890

A13.1.3 Dùng kết hợp với các vòng lặp hay mã rẽ nhánh

Các vòng lặp hay mã rẽ nhánh có thể được dùng kết hợp qua kí tự đổi hướng <

Thí dụ8: Giả sử tập tin file.dat kết thúc bằng dòng chữ "THE END"
while [ ! "$line" = "THE END." ] 
do
  read line                 # Reads from file.dat, rather than stdin.
  echo $line
  let "count += 1"
  if [ $count -gt 3 ]; then
      echo "out of range"
      break
  fi
done <"file.dat"           # Redirects stdin to file file.dat.
echo "$count"              #display content of file.dat

A13.2 Ống Dẫn Truyền Tên (named pipe):

Trong UNIX/Linux, một kênh trao đổi giữa các tiến trình, FIFO (từ chữ "first-in, first-out"), thường được dùng để chuyển các ngỏ ra chuẩn của một mệnh lệnh lên ngỏ vào chuẩn của một mệnh lệnh khác đang chạy cùng lúc. Đôi khi các tiến trình (process) có thể tự mở các ống dẫn truyền đến các lệnh mà chúng khởi động
Quá trình đổi hướng có thể được xem là một ống dẫn truyền dạng đơn giản. Chúng ta sẽ khai thác kĩ hơn 2 dạng named pipe đơn giản.

A13.2.1 command1 | command2 | command3 ... : Dùng để xử lí một dãy các lệnh mà ngỏ ra của lệnh trước chuyển thành ngỏ vào của của sau và chúng ngăn cách nhau bởi dấu |

Thí dụ1: Xếp thứ tự tất cả các dữ liệu xuất từ các tập tin *.dat, xoá các dòng trùng nhau, và lưu trữ lại trong tệp newdata
cat *.dat | sort | uniq > newdata

Thí dụ2: tìm tất cả tệp *.txt trong thư mục hiện hoạt rồi xử lí qua lệnh ls -l và sau cùng lọc bằng lệnh grep các tập tin được tạo ra trong tháng 12
find ./ -name *.txt | xargs -i ls -l | grep "Dec"
#this command may be used to find all *.txt file that created in December

A13.2.2 <(command) : Dùng để gửi ngỏ ra của một lệnh vào một tiến trình khác

Thí dụ3  Lệnh diff so sánh thuộc tính của mọi tập tin trong thư mục dir1 với thư mục dir2 mà các thuộc tính đó lại được rút ra từ hai lệnh ls-l
diff <(ls -l dir1) <(ls -l dir2)

Thí dụ4: Một số lệnh tạo ra tập tin nén dạng *.tar.gz:
tar cpf >(gzip -9  > myfile.tar.gz) myDir
#
The command  will create a tar of myDir and then compress this into a file named as myfile.tar.gz
# It is equivalent to
gzip -9 < pipe > myfile.tar.gz &
tar cpf pipe myDir
rm pipe

Thí dụ5:

đọc thông tin từ lệnh lsmod (hiển thị gồm 3 cột) lần lượt vào các biến record<i> và hiển thị chúng
while read record1 record2 record3; do
  echo $record1 $record2 $record3
done < <(lsmod) 

# Output may look like:
# Modules       Size   Use by   Not tained
# smbfs         40352  2  (Autoclean)
# nls_iso8859-1  3520  0  (Autoclean)
#let try to compare with the command lsmod itself
#The similar pipe command is
lsmod |  while read record1 record2 record3; do  
    echo $record1 $record2 $record3
done  # Same output as above.

Lưu ý: các phương pháp dùng pipe trên tương dương với việc sử dụng FIFO trong C. (thông qua các hàm: popen, pclose ...)

Bài kì tới: Biến môi trường, Các ki hiệu quy ước riêng của BASH, Hàm, sed,  và daemon

© http://vietsciences.free.fr , http://vietsciences.org  và http://vietsciences2.free.fr  Làng Đậu