# การเขียนโปรแกรมเชลล์ (8)

### รายละเอียดของโปรแกรม copydir

ต้นแบบของ copydir

    #!/bin/sh

    if [ $# -ne 2 ] ;then
        echo "Usage : $0 [source dir] [dest dir]"
        exit 1
    fi

    if [ ! -d $1 ] ;then
        echo "\"$1\" must be a directory, please check."
        exit 1
    fi

    if [ ! -w $2 ] ;then
        if [ ! -a $2 ] ;then
            echo -n "\"$2\" does not exist, do you want to create it? [y/n] :"
    	read ans
      	while [ true ] ;do
    	    case "$ans" in
    	    y|Y|yes|YES)
    	        mkdir $2
    		if [ $? -ne 0 ] ;then
    		    echo "Write \"$2\" error"
    		    exit 1
    		fi
    		break ;;
    	    n|N|no|NO)
    		echo "Ok, don't creat directory, so this will done nothing."
    		exit 0 ;;
    	    *)
    		echo -n "Expect \"yes\" or \"no\", please answer [y/n] :"
    		read ans
    	    esac
    	done
        else
            echo "\"$2\" does not have write permission, please check."
            exit 1
        fi
    else
        if [ ! -d $2 ] ;then
            echo "\"$2\" is not a directory, please check."
            exit 1
        fi
    fi

    LISTFILE=`ls $2`
    if [ ! -z "$LISTFILE" ] ;then
        echo -n "There is some file in \"$2\", still process copy directory? [y/n] :"
        ans=""
        read ans
        while [ true ] ;do
            case "$ans" in
            y|Y|yes|YES)
                break ;;
            n|N|no|NO)
    	    echo "Ok, don't process, so this will done nothing."
    	    exit 0 ;;
    	*)
    	    echo -n "Expect \"yes\" or \"no\", please answer [y/n] :"
    	    read ans
    	esac
        done
    fi

    SOURCEPARDIR=`dirname $1`
    SOURCENAME=`basename $1`
    DESTPARDIR=`dirname $2`
    DESTNAME=`basename $2`
    CURRDIR=`pwd`

    echo "Copy in process..."
    cd $1
    if [ $DESTPARDIR = "." ] ;then
        # Destination is relative reference
        find -print | cpio -pd $CURRDIR/$DESTNAME
    else
        # Destination is full reference
        find -print | cpio -pd $DESTPARDIR/$DESTNAME
    fi
    cd $CURRDIR
    echo "Copy directory complete."
    exit 0

อธิบายโปรแกรม

- มีการตรวจสอบจำนวนอาร์กิวเมนต์ที่จะใส่เข้ามา ต้องมีสองอาร์กิวเมนต์คือเป็นอาร์กิวเมนต์ที่เป็นชื่อไดเรกทอรีต้นทาง และอาร์กิวเมนต์ที่เป็นชื่อไดเรกทอรีปลายทาง ถ้ามีจำนวนอาร์กิวเมนต์ไม่ครบก็จะแจ้งข้อผิดพลาดและจบโปรแกรม

         if [ $# -ne 2 ] ;then

- ตรวจสอบอาร์กิวเมนต์ต้นทางว่าต้องเป็นไดเรกทอรี

         if [ ! -d $1 ] ;then

- ตรวจสอบว่ามีไดเรกทอรีปลายทาง และอนุญาตให้สิทธิในการเขียนได้ แต่ถ้าไม่มีสิทธิในการเขียนก็จะแจ้งข้อผิดพลาดและจบโปรแกรม

         if [ ! -w $2 ] ;then

- ถ้าไม่มีไดเรกทอรี โปรแกรมจะถามว่าจะสร้างไดเรกทอรีใหม่หรือไม่

         if [ ! -a $2 ] ;then

  ส่วนของโปรแกรมที่เป็นการถามว่าจะสร้างไดเรกทอรีใหม่นั้นจะเป็นการวนลูปเพื่อรอป้อนข้อมูลเป็น "yes","YES","y","Y" หรือ "no","NO","n","N" เท่านั้นถ้าผู้ใช้ป้อนข้อมูลอย่างอื่นเข้ามาก็จะวนลูปไปเรื่อยๆ
- ถ้าผ่านช่วงการตรวจสอบไดเรกทอรีมาแล้วก็จะทำการตัดส่วนชื่อพาธ (คำสั่ง dirname) และชื่อตัว (คำสั่ง basename) โดยจะแยกเป็นสองส่วนคือส่วนที่เป็น relative pathname (อ้างโดยการใช้จุด ".") และ full pathname (อ้างชื่อเต็มตั้งแต่ไดเรกทอรีรากขึ้นมา) เนื่องจากคำสั่ง cpio ต้องการชื่อเต็ม (full pathname) ดังนั้นถ้าผู้ใช้ทำการอ้างชื่อ ไดเรกทอรีปลายทางแบบ relative เช่น

          $ copydir /home/user1 ./user2

  จะต้องทำการเปลี่ยนชื่อพาธ (ส่วนที่เป็น "./" ที่นำหน้า "user2") ให้เป็นชื่อไดเรกทอรีปัจจุบันเสียก่อนโดยการใช้คำสั่ง "pwd" แล้วจึงนำเอาชื่อพาธที่เปลี่ยนใหม่นี้มาต่อเข้ากับชื่อตัวเดิมก็จะสามารถทำการส่งค่าไดเรกทอรีปลายทางให้กับคำสั่ง cpio ได้

          CURRDIR=`pwd`
          find -print | cpio -pd $CURRDIR/$DESTNAME

  แต่ถ้าเป็นการอ้างแบบ full pathname อยู่แล้วก็นำเอาชื่อไดเรกทอรีนั้นมาใช้ได้เลยทันที
- รหัสที่โปรแกรมส่งคืนเมื่อโปรแกรมทำงานเสร็จ
  - จบโปรแกรมด้วยรหัส 1 ในกรณีมีข้อผิดพลาด
  - จบโปรแกรมด้วยรหัส 0 ในกรณีที่เป็นการจบงานแบบปกติ ไม่มีข้อผิดพลาด
  - เตรียมไว้เผื่อถูกนำไปใช้กับเชลล์อื่น เช่นตรวจสอบเงื่อนไข

### copydir ฉบับปรับปรุง

จะเห็นว่าโปรแกรม copydir ของเรานั้นมีการทำงานอยู่ขั้นตอนหนึ่งที่ซ้ำซ้อนกันอยู่ซึ่งก็คือขั้นตอนของการสอบถามและรับคำตอบยืนยัน ซึ่งขั้นตอนนี้จะถูกนำมาใช้สองครั้งคือการถามว่าต้องการจะสร้างไดเรกทอรีปลายทางหรือไม่ และถามว่าจะยังต้องการทำขั้นตอนของการสำเนาไดเรกทอรีอยู่หรือไม่ในกรณีที่มีไฟล์อยู่ในไดเรกทอรีปลายทางจะเห็นว่าถ้าเราสามารถแยกขั้นตอนการถามตอบนี้ออกมาเป็นฟังก์ชั่นต่างหาก จะทำให้เราไม่ต้องเขียนโปรแกรมซับซ้อน และจะทำให้โปรแกรมสามารถอ่านและแก้ไขได้ง่ายขึ้นนอกจากนี้หากเราเขียนโปรแกรมเชลล์อื่นๆและต้องการใช้ขั้นตอนการทำงานในลักษณะนี้เราก็จะสามารถนำเอาฟังก์ชั่นนี้ไปใช้งานด้วยได้

สำหรับฟังก์ชั่นการถามตอบนี้ ควรมีการรับพารามิเตอร์ที่เป็นข้อความที่ต้องการจะให้โปรแกรมพิมพ์ออกมาเป็นคำถาม เพื่อที่เราสามารถจะใช้โปรแกรมนี้กับการถามคำถามแบบต่างๆได้ และเมื่อผู้ใช้ใส่ข้อมูลมาในลักษณะตอบรับ (YES, yes, Y, y) ก็จะออกจากฟังก์ชั่นพร้อมทั้งคืนค่าด้วยรหัส "0" แต่ถ้าถ้ารับข้อมูลในลักษณะการตอบปฏิเสธ (NO, no, N, n) ก็จะออกจากฟังก์ชั่นพร้อมทั้งคืนค่าด้วยรหัส "1"

ในส่วนของตัวโปรแกรมหลักเมื่อมีการเรียกใช้ฟังก์ชั่นถามตอบนี้แล้ว จะสามารถตรวจสอบว่าผู้ใช้มีการตอบยืนยันหรือตอบปฏิเสธได้จากรหัสที่ถูกคืนค่ามาจากฟังก์ชั่นถามตอบนี้ โดยการตรวจสอบจากตัวแปรเชลล์ "$?" ในโปรแกรมหลักจะใช้ฟังก์ชั่นถามตอบสองครั้งดังต่อไปนี้

    fyesno "\"$2\" does not exist, do you want to create it? [y/n] :"
    และ
    fyesno "There is some file in \"$2\", still process copy directory? [y/n] :"

สำหรับการประกาศฟังก์ชั่นจะต้องทำก่อนที่จะมีการเรียกใช้ฟังก์ชั่นนั้นๆ โดยที่จะต้องประกาศไว้แยกต่างหากจากส่วนของโปรแกรมหลักที่จะเรียกใช้ด้วย

    #!/bin/sh

    #--------------------------------------------------------

    fyesno () {
        echo -n $1
        read ans
        while [ true ] ;do
            case "$ans" in
            y|Y|yes|YES)
    	    # 0 - on success (true in shell)
                return 0 ;;
            n|N|no|NO)
    	    # 1 - on fail (false in shell)
    	    return 1 ;;
    	*)
    	    echo -n "Expect \"yes\" or \"no\", please answer [y/n] :"
    	    read ans
    	esac
        done
    }

    #-----------------------------------------------------------

    if [ $# -ne 2 ] ;then
        echo "Usage : $0 [source dir] [dest dir]"
        exit 1
    fi

    if [ ! -d $1 ] ;then
        echo "\"$1\" must be a directory, please check."
        exit 1
    fi

    if [ ! -w $2 ] ;then
        if [ ! -a $2 ] ;then
            fyesno "\"$2\" does not exist, do you want to create it? [y/n] :"
    	if [ $? -eq 0 ] ;then
    	    mkdir $2
    	    if [ $? -ne 0 ] ;then
    	        echo "Write \"$2\" error"
    		exit 1
    	    fi
            else
    	    echo "Ok, don't creat directory, so this will done nothing."
    	    exit 0
    	fi
        else
            echo "\"$2\" does not have write permission, please check."
            exit 1
        fi
    else
        if [ ! -d $2 ] ;then
            echo "\"$2\" is not a directory, please check."
            exit 1
        fi
    fi

    LISTFILE=`ls $2`
    if [ ! -z "$LISTFILE" ] ;then
        fyesno "There is some file in \"$2\", still process copy directory? [y/n] :"
        if [ $? -eq 1 ] ;then
            echo "Ok, don't process, so this will done nothing."
            exit 0
        fi
    fi

    SOURCEPARDIR=`dirname $1`
    SOURCENAME=`basename $1`
    DESTPARDIR=`dirname $2`
    DESTNAME=`basename $2`
    CURRDIR=`pwd`

    echo "Copy in process..."
    cd $1
    if [ $DESTPARDIR = "." ] ;then
        # Destination is relative reference
        find -print | cpio -pd $CURRDIR/$DESTNAME
    else
        # Destination is full reference
        find -print | cpio -pd $DESTPARDIR/$DESTNAME
    fi
    cd $CURRDIR
    echo "Copy directory complete."
    exit 0

ในส่วนของตัวโปรแกรมอาจมีบางส่วนที่ยังไม่สมบูรณ์ นอกจากเรื่องการปรับปรุงส่วนของฟังก์ชั่นถามตอบแล้วคุณอาจสามารถจะดัดแปลงแก้ไขเพื่อเพิ่มประสิทธิภาพของตัวโปรแกรมได้

HTML developed by Kaiwal Development Team (kaiwal@geocities.com)