Monday, August 17, 2009

การเขียน Shell Script

เอกสารนี้จัดทำขึ้นเพื่อแนะนำ และใช้สำหรับการอ้างอิงให้กับผู้ที่จะเขียนโปรแกรมบน Shell โดยจะกล่าวถึงโครงสร้างโดยทั่วไปของ Shell แสดงการใช้คำสั่งต่างๆ พร้อมตัวอย่างประกอบ ทั้งนี้ตัวอย่างดังกล่าวจะเปรียบเทียบระหว่าง bash , ksh และ tcsh เป็นหลัก โดยข้อมูลในเอกสารนี้จะเน้นเพียงข้อมูลที่เป็นมาตรฐานของ Shell ทั่วไปเท่านั้น เนื่องจากในปัจจุบัน Shell หมายๆ ตัวได้มีการปรับปรุงให้ทำงานได้กว้างมากขึ้น และ เขียนง่ายยิ่งขึ้นเช่น bash บน Linux ซึ่งจะรวมความสามารถของ ksh และ tcsh หลายอย่างไว้ด้วยกัน เป็นต้น

ลักษณะทั่วไปของ Shell

หน้าที่พื้นฐานของ Shell โดยทั่วไปนั้นคือการรับคำสั่งจาก User เพื่อทำการบอก OS ให้ Load และ Run Program ต่างๆ อย่างไรก็ตามได้มีการพัฒนาให้ Shell มีความสามารถในการประมวลผลคำสั่งต่างๆ เพื่อให้ทำงานในลักษณะของ Batch ได้ ทั้งนี้โครงสร้างของโปรแกรมต่างๆ หรือ Shell นั้นจะประกอบด้วยตัวแปรระบบ หรือ สภาวะแวดล้อม(Envirenment) ซึ่งพร้อมที่จะส่งให้โปรแกรมอื่นๆ ประมวลผลต่อ ประโยคเงื่อนไขต่างๆ ระบบ Profile สำหรับวัดเวลาในการประมวลผลของโปรแกรมต่างๆ และ User Defined Function

ในการวัดเวลาที่ใช้ในการทำงานคำสั่งต่างๆ ของ Unix นั้นสามารถทำได้โดยใส่ time นำหน้าคำสั่งที่ต้องการประมวลผล ซึ่งโปรแกรมจะบอก OS ให้ทำการจับเวลา และ รายงานผลเวลาที่ใช้ในการประมวลผลหลังจากทำงานเสร็จ เช่น

time ls -al /bin/*

สำหรับกรณี Shell บน Unix นั้น การเขียนคำสั่งแต่ละคำสั่งจะต้องจบคำสั่ง ด้วยบรรทัดใหม่ หรือ เครื่องหมาย semi-colon เสมอ “;” และกรณีที่คำสั่งมีความยาวมาก (ไม่จบภายใน 1 บรรทัด) ให้จบบรรทัดด้วยเครื่องหมาย “” แล้วพิมพ์ต่อในบรรทัดถัดไป เช่น

echo "Test"; ls;

echo "Hello
world"

การเขียน Comment ใน Shell Script จะใช้เครื่องหมาย “#”

ทั้งนี้ในการเขียนโปรแกรมที่เป็นภาษา Script ต่างๆ บน Unix นั้น ในบรรทัดแรกเราควรจะระบุชื่อโปรแกรมที่จะทำการประมวลผล Script นั้นๆ ไว้ด้วยเช่น

#! /bin/ksh

หรือ กรณีเป็น Perl อาจจะเขียนเป็น

#! /usr/bin/perl

เป็นต้น

การใช้งาน Variable / Environment

บนระบบ OS โดยทั่วไปมักจะมี Environment หรือ System Variable เพื่อส่งผ่านค่าระหว่างโปรแกรมต่างๆ ภายในระบบ เสมอ ซึ่งโดยทั่วไปเราจะอ่านค่า Environment (ของ OS เกือบทุกตัว) ได้โดยการใช้คำสั่ง set

ตัวอย่างการตั้งค่า Variable ใน Shell เช่น

bash / ksh tcsh

myname="Krerk Piromsopa"
count=5;

set myname="Krerk Piromsopa"
set count=5;

นอกจากนี้เรายังสามารถนำผลลัพธ์จากการ Run Programm อื่นๆ มาเป็นค่า Variable ได้อีกด้วย โดยใช้ Back Quote แทน หรือ Run คำสั่งภายในเครื่องหมาย $(command) เช่น

bash / ksh tcsh
list = `ls`
# หรือ
list=$(ls)
set list = `ls`
# หรือ
set list=$(ls)

ทั้งนี้หากต้องการให้ค่า Variable หรือ Environment คงอยู่หลังจากที่ Run Shell สามารถทำได้โดยการ export เช่น

export myname,count

นอกจากนี้ยังมีตัวแปรระบบที่จะใช้สำหรับการอ้างอิงในโปรแกรม Shell ได้อีกด้วย เช่น

ตัวแปร ความหมาย
$# แสดงจำนวน Parameter ที่ส่งมาให้กับระบบ
$? เก็บค่าที่ได้จากประมวลผลคำสั่งก่อนหน้านี้
$0 แสดงชื่อของ โปรแกรมที่กำลังประมวลผลอยู่
$* เก็บค่า Parameter ทุกตัวที่ใช้ในการประมวลผล ($1 $2 ....)
"$@" เก็บค่า Parameter ทุกตัวที่ใช้ในการประมวลผล ("$1" "$2" ....)

การใช้งาน Variable ทุกตัวนั้นจะเป็น Implicit Declaration กล่างคือผู้ใช้ไม่จำเป็นต้องประกาศตัวแปร เช่นในภาษา C หรือ ภาษาชั้นสูงอื่นๆ และ เมื่อต้องการยกเลิกตัวแปรนั้นสามารถทำได้โดยใช้คำสั่ง unset varname เช่น

unset myname

test และ expression

บน bash และ ksh จะมีคำสั่ง Test และการเขียนเงื่อนไขในการตรวจสอบ Expression ต่างๆ ในการเขียนประโยคเงื่อนไขต่าง ซึ่งมีโครงสร้างการเขียนดังนี้

test expression

หรือ

[ expression ]

สำหรับ tcsh นั้นจะไม่มีคำสั้ง test แต่จะมีการนำ expression เพื่อไปใช้ในการประมวผลผลคำสั่งต่างๆ เช่นกัน

ทั้งนี้ expression บน bash กับ ksh และ tcsh นั้นจะมีโครงสร้างที่แตกต่างกัน ดังอธิบายได้ดังต่อไปนี้

bash / ksh tcsh ความหมาย
คำสั่งเกี่ยวกับจำนวนเต็ม
int1 -eq int2 int1 == int2 เป็นจริงเมื่อ int1 เท่ากับ int2
int1 -ge int2 int1 >= int2 เป็นจริงเมื่อ int1 มากกว่าหรือเท่ากับ int2
int1 -gt int2 int1 > int2 เป็นจริงเมื่อ int1 มากกว่า int2
int1 -le int2 int1 <= int2 เป็นจริงเมื่อ int1 น้อยกว่าหรือเท่ากับ int2
int1 -lt int2 int1 <> เป็นจริงเมื่อ int1 น้อยกว่า int2
int1 -ne int2 int1 != int2 เป็นจริงเมื่อ int1 ไม่เท่ากับint2
คำสั่งเกี่ยวกับ String
str1 = str2 str1 == str2 เป็นจริงเมื่อ str1 เหมือนกับ str2
str1 != str2 str1 != str2 เป็นจริงเมื่อ str1 ไม่เหมือนกับ str2
str เป็นจริงเมื่อ str ไม่เป็น null
-n str เป็นจริงเมื่อ str มีความยาวมากกว่า 0
-z str เป็นจริงเมื่อ str มีความยาวเป็น 0
คำสั่งเกี่ยวกับ file
-d filename -d filename เป็นจริงเมื่อ filename เป็น directory
-f filename -f filename เป็นจริงเมื่อ filename เป็น file
-r filename -r filename เป็นจริงเมื่อ filename อ่านได้โดยโปรแกรม
-s filename เป็นจริงเมื่อ filename มีขนาดไม่เป็น 0
-w filename -w filename เป็นจริงเมื่อ filename เขียนได้โดยโปรแกรม
-x filename -x filename เป็นจริงเมื่อ filename run ได้โดยโปรแกรม
-e filename เป็นจริงเมื่อ filename มีชื่ออยู่
-o filename เป็นจริงเมื่อ filename เป็นของผู้ใช้ปัจจุบัน
-z filename เป็นจริงเมื่อ filename มีขนาดเป็น 0
คำสั่งเกี่ยวกับ Logical อื่นๆ
! expr ! expr เป็นจริงเมื่อ exp เป็นเท็จ
exp1 -a exp2 exp1 && exp2 เป็นจริงเมื่อ exp1 และ exp2 เป็นจริง
exp1 -o exp2 exp1 || exp2 เป็นจริงเมื่อ exp1 หรือ exp2 เป็นจริง

เงื่อนไข if

โครงสร้าง

bash / ksh tcsh

if [expression ]
then
commands
elif [ exprression ]
then
commands
else
commands
fi

if (expression) then
commands
else if (expression) then
commands
else
commands
endif

ทั้งนี้จะมี elif หรือ ไม่มี else ก็ได้

ตัวอย่างการใช้งาน เช่น

#! /bin/ksh
if [ -f .signature ]
then
echo "There is .signature file in this current directory"
else
echo "File .signature could not be found.
fi

เงื่อนไข case หรือ switch

โครงสร้าง

bash / ksh tcsh

case string in
str1)
commands;;
str2)
commands;;
str3)
commands;;
*)
commands;;
esac

switch(string)
case str1:
commands
breaksw
case str2:
commands
breaksw
case str3:
commands
breaksw
default:
commands
breaksw
endsw

ในกรณีของ bash และ ksh นั้น จะใช้ ;; แทน break

ตัวอย่างการใช้งาน เช่น

#! /bin/ksh
# Sample switch case
if [ $# -lt 2 ]
then
echo "Using : $0 [a | b | c] filename"
else
case $1 in
"a" | "A")
cat $2;;
"b" | "B")
&nsbp; more $2;;
"c" | "C")
less $2;;
*)
echo "I Don't understand $1"
esac fi

เงื่อนไข for

โครงสร้าง

bash / ksh tcsh

for var in list
do
commands
done

foreach var (list)
commands
end

ตัวอย่างการใช้งาน เช่น

#! /bin/ksh
# Sample for
for x in "a " "b" "x*"
do
if [ -f "$x" ]
then
echo "--- found $x ---"
else
echo "--- Not found $x ---"
fi

เงื่อนไข while

โครงสร้าง

bash / ksh tcsh
while [ expression ]
do
commands
done
while (expression)
commands
end

ตัวอย่างการใช้ while เช่น

#! /bin/ksh
count=1
while [ -n "$*" ]
do
echo "This is parameter no $count $1"
shift
count=`expr $count+1`
done

เงื่อนไข until

โครงสร้าง

bash / ksh tcsh
until [ expression ]
do
commands
done
ไม่สนับสนุน

เงื่อนไข repeat

bash / ksh tcsh
ไม่สนับสนุน repeat count command

ตัวอย่างการใช้ repeat เช่น

#! /bin/tcsh
repeat $num echo -n "+"
echo ""

คำสั่งอื่นๆ ที่ช่วยให้เขียน Interactive Shell Script ได้ง่ายขึ้น

คำสั่ง read

ใช้สำหรับอ่านค่าตัวแปรจาก Standard Input เช่น

#! /bin/ksh
echo -n "Enter FirstName Lastname"
read x y
echo ""
echo "Your firstname is $y, $x."

คำสั่ง echo

ใช้เพื่อการแสดงข้อความ

คำสั่ง shift

ใช้สำหรับการ Shift ค่าตัวแปรใน $* ไป 1 ตำแหน่ง

การสร้างคำสั่งใหม่ (Function)

โครงสร้าง

bash / ksh tcsh

[function] fname {
(shell commands)
}

ไม่สนับสนุน

ซึ่งเราสามารถเรียกใช้ Function ได้โดย

fname [ parm1 parm2 parm3 ... ]

ตัวอย่างเช่น

#! /bin/bash

print () {
echo $1 $2;
}

a=10;
b=20;
print $a $b

การประมวลผลทางคณิตศาสตร์ และ expression

เราสามารถประมวลผลบวกลบคูณหาร หรือ expression ต่างๆ ได้โดยใช้โปรแกรม expr ช่วย เช่น

a=`expr $a + 1` หรือ set a=`expr $a + 1`

ในกรณีของ bash เราสามารถใช้ a=$(($a + 1 )) แทนได้เลย

ข้อควรระวังคือ จะต้องมีการเว้นวรรคระหว่าง ตัวแปร ค่าคงที่ และ เครื่องหมาย (มิเช่นนั้น จะได้ผลลัพธ์เป็น string ต่อกัน) เครื่องหมายและการปฏิบัติการที่ใช้ได้ใน expr คือ +, -, *, / , % (modulo หรือ หารเอาเศษ) นอกจากนี้ยังมีการเปรียบเทียบต่างๆ เช่น =, \<, \>, \<=, \>=, และ != (กรณีใช้กับ bash $(( expression )) ให้นำเครื่องหมาย \ ออก การเปรียบเทียบเหล่านี้จะให้ผลลัพธ์เป็น 0 หากเป็นเท็จ และ 1 หากเป็นจริง

เกร็ดเล็กน้อยเกี่ยวกับการใช้ expr

expr1 \| expr2

จะแสดงผลลัพธ์จาก expr2 หาก expr1 เป็น 0 หรือ NULL เช่น
expr 1 - 1 \| "zero"
จะได้ผลลัพธ์เป็น "zero" เนื่องจาก 1 - 1 = 0 เป็นต้น

expr1 \& expr2

จะแสดงผลลัพธ์จาก expr1 หาก ไม่มีอันใดเป็น 0 หรือ NULL เช่น
expr valid \& 3 - 2 \& 4
จะได้ผลลัพธ์เป็น "valid" เนื่องจากไม่มี expression ใด เป็น 0 เป็นต้น

expr1 : expr2

จะเปรียบเทียบ expr1 กับ expr2 ตามมาตรฐาน regular expression เช่น
expr $HOME : ".*home.*"
จะตรวจสอบว่ามีคำว่า home อยู่ใน $HOME หรือไม่ เป็นต้น

เอกสารอ้างอิง

  • T. Parker, Chapter 13 Shell Programming ,"Slackware Linux", SAMS Publishing, 3rdEdition, 1997.
  • ksh , sh , tcsh , bash manpage.