Shell 腳本語法


 

條件測試:test  和  [

命令 test 或 [ 可以測試一個條件是否成立,如果測試結果為真,則該命令的Exit Status為0,如果測試結果為假,則命令的Exit Status為1注意與C語言的邏輯表示正好相反)。

例如測試兩個數的大小關系:

itcast@ubuntu:~$ var=2
itcast@ubuntu:~$ test $var -gt 1
itcast@ubuntu:~$ echo $? // 查看返回值
0 // 真 返回0
itcast@ubuntu:~$ test $var -gt 3
itcast@ubuntu:~$ echo $?
1 // 假返回 1
itcast@ubuntu:~$ [ $var -gt 3 ]
itcast@ubuntu:~$ echo $?
1
itcast@ubuntu:~$

 

雖然看起來很奇怪,但 左方括號 [ 確實是一個命令的名字,傳給命令的各參數之間應該用空格隔開,

比如,$VAR、-gt、3、] 是 [ 命令的四個參數,它們之間必須用空格隔開。命令test或 [ 的參數形式是相同的,只不過test命令不需要]參數。

以 [ 命令為例,常見的測試命令如下表所示:
[ -d DIR ] 如果DIR存在並且是一個目錄則為真    為真返回 0
[ -f FILE ] 如果FILE存在且是一個普通文件則為真

[ -e FILE ] 如果FILE是否存在

[
-z STRING ] 如果STRING的長度為零則為真 [ -n STRING ] 如果STRING的長度非零則為真 [ STRING1 = STRING2 ] 如果兩個字符串相同則為真 [ STRING1 != STRING2 ] 如果字符串不相同則為真 [ ARG1 OP ARG2 ] ARG1和ARG2應該是整數或者取值為整數的變量,OP是-eq(等於)-ne(不等於)-lt(小於)-le(小於等於)-gt(大於)-ge(大於等於)之中的一個 和C語言類似,測試條件之間還可以做與、或、非邏輯運算: 帶與、或、非的測試命令 [ ! EXPR ] EXPR可以是上表中的任意一種測試條件,!表示邏輯反 [ EXPR1 -a EXPR2 ] EXPR1和EXPR2可以是上表中的任意一種測試條件,-a表示邏輯與 [ EXPR1 -o EXPR2 ] EXPR1和EXPR2可以是上表中的任意一種測試條件,-o表示邏輯或

 

例如:

$ VAR=abc
$ [ -d Desktop -a $VAR = 'abc' ]
$ echo $?
0

 

注意,如果上例中的$VAR變量事先沒有定義,則被Shell展開為空字符串,會造成測試條件的語法錯誤(展開為[ -d Desktop -a = ‘abc’ ]),

作為一種好的Shell編程習慣,應該總是把變量取值放在雙引號之中(展開為[ -d Desktop -a “” = ‘abc’ ]):

$ unset VAR
$ [ -d Desktop -a $VAR = 'abc' ]
bash: [: too many arguments
$ [ -d Desktop -a "$VAR" = 'abc' ]
$ echo $?
1

 

if/then/elif/else/fi

和C語言類似,在Shell中用if、then、elif、else、fi這幾條命令實現分支控制。這種流程控制語句本質上也是由若干條Shell命令組成的,

例如先前講過的

if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi

 

其實是三條命令,if [ -f ∼/.bashrc ]是第一條then . ∼/.bashrc是第二條fi是第三條。如果兩條命令寫在同一行則需要用;號隔開,

一行只寫一條命令就不需要寫;號了,另外,then后面有換行,但這條命令沒寫完,Shell會自動續行,把下一行接在then后面當作一條命令處理。

和 [ 命令一樣,要注意命令和各參數之間必須用空格隔開。if命令的參數組成一條子命令,如果該子命令的Exit Status為0(表示真),則執行then后面的子命令,

如果Exit Status非0(表示假),則執行elif、else或者fi后面的子命令。if后面的子命令

通常是測試命令,但也可以是其它命令。Shell腳本沒有{}括號,所以用fi表示if語句塊的結束。見下例:

 

read

gec@ubuntu:~/myshare/Shell$ read ABC
hello world
gec@ubuntu:~/myshare/Shell$ echo $ABC
hello world
gec@ubuntu:~/myshare/Shell$ 

 

#! /bin/sh
if [ -f /bin/bash ]
then echo "/bin/bash is a file"
else echo "/bin/bash is NOT a file"
fi
if :; then echo "always true"; 

:是一個特殊的命令,稱為空命令,該命令不做任何事,但Exit Status總是真。此外, 也可以執行/bin/true或/bin/false得到真或假的Exit Status。再看一個例子: #! /bin/sh echo "Is it morning? Please answer yes or no." read YES_OR_NO if [ "$YES_OR_NO" = "yes" ]; then echo "Good morning!" elif [ "$YES_OR_NO" = "no" ]; then echo "Good afternoon!" else echo "Sorry, $YES_OR_NO not recognized. Enter yes or no." exit 1 fi exit 0

 

上例中的read命令的作用是等待用戶輸入一行字符串,將該字符串存到一個Shell變量中。

此外,Shell還提供了&&和||語法,和C語言類似,具有Short-circuit特性,很多Shell腳本喜歡寫成這樣:

test "$(whoami)" != 'root' && (echo you are using a non-privileged account; exit 1)
&&相當於“if…then…”,而||相當於“if not…then…”。&&和||用於連接兩個命 令,而上面講的-a和-o僅用於在測試表達式中連接兩個測試條件,要注意它們的區別,例
如,
test "$VAR" -gt 1 -a "$VAR" -lt 3
和以下寫法是等價的
test "$VAR" -gt 1 && test "$VAR" -lt 3

 

 case/esac

case命令可類比C語言的switch/case語句,esac表示case語句塊的結束。C語言的case只能匹配整型或字符型常量表達式,

Shell腳本的case可以匹配字符串和Wildcard,每個匹配分支可以有若干條命令,末尾必須以 ;; 結束,執行時找到第一個匹配的分支並執行相應的命令,

然后直接跳到esac之后,不需要像C語言一樣用break跳出。

#! /bin/sh
echo "Is it morning? Please answer yes or no."
read YES_OR_NO
case "$YES_OR_NO" in
  yes|y|Yes|YES)
    echo "Good Morning!";;
  [nN]*)
    echo "Good Afternoon!";;
  *)
    echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
    exit 1;;
esac
exit 0

 

/etc/init.d/vsftpd

#!/bin/sh

### BEGIN INIT INFO
# Provides:        vsftpd
# Required-Start:    $network $remote_fs $syslog
# Required-Stop:    $network $remote_fs $syslog
# Default-Start:    2 3 4 5
# Default-Stop:        0 1 6
# Short-Description:    Very secure FTP server
# Description:        Provides a lightweight, efficient FTP server written
#            for security.
### END INIT INFO

set -e

DAEMON="/usr/sbin/vsftpd"
NAME="vsftpd"
PATH="/sbin:/bin:/usr/sbin:/usr/bin"
LOGFILE="/var/log/vsftpd.log"
CHROOT="/var/run/vsftpd/empty"

test -x "${DAEMON}" || exit 0

. /lib/lsb/init-functions

if [ ! -e "${LOGFILE}" ]
then
    touch "${LOGFILE}"
    chmod 640 "${LOGFILE}"
    chown root:adm "${LOGFILE}"
fi

if [ ! -d "${CHROOT}" ]
then
    mkdir -p "${CHROOT}"
fi

case "${1}" in
    start)
        log_daemon_msg "Starting FTP server" "${NAME}"

        if [ -e /etc/vsftpd.conf ] && ! egrep -iq "^ *listen(_ipv6)? *= *yes" /etc/vsftpd.conf
        then
            log_warning_msg "vsftpd disabled - listen disabled in config."
            exit 0
        fi

        start-stop-daemon --start --background -m --oknodo --pidfile /var/run/vsftpd/vsftpd.pid --exec ${DAEMON}
        
        n=0
        while [ ${n} -le 5 ]
        do 
            _PID="$(if [ -e /var/run/vsftpd/vsftpd.pid ]; then cat /var/run/vsftpd/vsftpd.pid; fi)"
            if ! ps -C vsftpd | grep -qs "${_PID}"
            then
                break
            fi
            sleep 1
            n=$(( $n + 1 ))
        done

        if ! ps -C vsftpd | grep -qs "${_PID}"
        then
            log_warning_msg "vsftpd failed - probably invalid config."
            exit 1
        fi

        log_end_msg 0
        ;;

    stop)
        log_daemon_msg "Stopping FTP server" "${NAME}"

        start-stop-daemon --stop --pidfile /var/run/vsftpd/vsftpd.pid --oknodo --exec ${DAEMON}
        rm -f /var/run/vsftpd/vsftpd.pid

        log_end_msg 0
        ;;

    restart)
        ${0} stop
        ${0} start
        ;;

    reload|force-reload)
        log_daemon_msg "Reloading FTP server configuration"

        start-stop-daemon --stop --pidfile /var/run/vsftpd/vsftpd.pid --signal 1 --exec $DAEMON

        log_end_msg "${?}"
        ;;

    status)
        status_of_proc "${DAEMON}" "FTP server"
        ;;

    *)
        echo "Usage: ${0} {start|stop|restart|reload|status}"
        exit 1
        ;;
esac

exit 0

 

 

 

 使用case語句的例子可以在系統服務的腳本目錄/etc/init.d中找到。這個目錄下的腳本大多具有這種形式(以/etc/init.d/nfs-kernel-server為例):

case "$1" in
  start)
    ...
  ;;
  stop)
    ...
  ;;
  reload | force-reload)
    ...
  ;;
  restart)
    ...
  *)
    log_success_msg "Usage: nfs-kernel-server {start|stop|status|reload|force-reload|restart}"
    exit 1
  ;;
esac

 

啟動nfs-kernel-server服務的命令是

$ sudo /etc/init.d/nfs-kernel-server start

$1是一個特殊變量在執行腳本時自動取值為第一個命令行參數,也就是start,所以

進入start)分支執行相關的命令。同理,命令行參數指定為stop、reload或restart可以進入其它分支執行停止服務、重新加載配置文件或重新啟動服務的相關命令。

for/do/done

Shell腳本的for循環結構和C語言很不一樣,它類似於某些編程語言的foreach循環。例

如:

#! /bin/sh
for FRUIT in apple banana pear; do
echo "I like $FRUIT"
done
運行:
I like apple
I like banana
I like pear
FRUIT是一個循環變量,第一次循環$FRUIT的取值是apple,第二次取值是banana,第三次取值是pear。再比如,要 將當前目錄下的chap0、chap1、chap2等文件名改為chap0~、chap1~、chap2~等(按慣例,末尾有~字符的文件名表 示臨時文件),這個命令可以這樣寫: $ for FILENAME in chap?; do mv $FILENAME $FILENAME~; done 也可以這樣寫: $ for FILENAME in `ls chap?`; do mv $FILENAME $FILENAME~; done

 

while/do/done

while的用法和C語言類似。比如一個驗證密碼的腳本:

#! /bin/sh
echo "Enter password:"
read TRY
while [ "$TRY" != "secret" ]; do
echo "Sorry, try again"
read TRY
done

 

下面的例子通過算術運算控制循環的次數:

#! /bin/sh
COUNTER=1
while [ "$COUNTER" -lt 10 ]; do
echo "Here we go again"
COUNTER=$(($COUNTER+1))
done

 

Shell還有until循環,類似C語言的do…while循環。
 

break和continue

break[n]可以指定跳出幾層循環,continue跳過本次循環步,沒跳出整個循環。break跳出,continue跳過。


1、把上面驗證密碼的程序修改一下,如果用戶輸錯五次密碼就報錯退出。

#! /bin/sh
echo "Enter Password:"
COUNTER=0
read TRY
while [ "$TRY" != "secret" -a $COUNTER != 5 ]
do
    COUNTER=$(($COUNTER + 1))
    echo "Sorry, try again; counter: $COUNTER"
    read TRY
done

 

位置參數和特殊變量

有很多特殊變量是被Shell自動賦值的,我們已經遇到了$?和$1,現在總結一下:

常用的位置參數和特殊變量

$0 相當於C語言main函數的argv[0]

$1、$2... 這些稱為位置參數(Positional Parameter),相當於C語言main函數的argv[1]、argv[2]...

$# 相當於C語言main函數的argc - 1,注意這里的#后面不表示注釋

$@ 表示參數列表"$1" "$2" ...,例如可以用在for循環中的in后面。

$* 表示參數列表"$1" "$2" ...,同上

$? 上一條命令的Exit Status

$$ 當前進程號

位置參數可以用shift命令左移。比如shift 3表示原來的$4現在變成$1,原來的$5現在變成$2等等,原來的$1、$2、$3丟棄,$0不移動。不帶參數的shift命令相當於shift 1。

如:

#! /bin/sh
echo "The program $0 is now running"
echo "The first parameter is $1"
echo "The second parameter is $2"
echo "The parameter list is $@"
shift
echo "The first parameter is $1"
echo "The second parameter is $2"
echo "The parameter list is $@"

 

gec@ubuntu:~/myshare/Shell$ ./t8.sh a b c d e f g
The program ./t8.sh is now running
The first parameter is a
The second parameter is b
The parameter list is a b c d e f g
The first parameter is b
The second parameter is c
The parameter list is b c d e f g

 

shell輸入輸出

echo echo顯示文本行或變量,或者把字符串輸入到文件。

echo [option] string

-e 解析轉義字符

-n 不回車換行。默認情況echo回顯的內容后面跟一個回車換行。

echo "hello\n\n"
echo -e "hello\n\n"
echo "hello"
echo -n "hello"

 

gec@ubuntu:~/myshare/Shell$ echo "hello\n\n"
hello\n\n
gec@ubuntu:~/myshare/Shell$ echo -e "hello\n\n"
hello


gec@ubuntu:~/myshare/Shell$ echo "hello"
hello
gec@ubuntu:~/myshare/Shell$ echo -n "hello"
hellogec@ubuntu:~/myshare/Shell$ 

 

 

管道| 可以通過管道把一個命令的輸出傳遞給另一個命令做輸入。管道用豎線表示

cat myfile | more
ls -l | grep "myfile"
df -k | awk '{print $1}' | grep -v "文件系統"
df -k 查看磁盤空間,找到第一列,去除“文件系統”,並輸出
tee tee命令把結果輸出到標准輸出,另一個副本輸出到相應文件。 df -k | awk '{print $1}' | grep -v "文件系統" | tee a.txt
tee -a a.txt表示追加操作。
df -k | awk '{print $1}' | grep -v "文件系統" | tee -a a.txt


cmd
> file 把標准輸出重定向到新文件中 cmd >> file 追加 cmd > file 2>&1 標准出錯也重定向到1所指向的file里 cmd >> file 2>&1 cmd < file1 > file2 輸入輸出都定向到文件里 cmd < &fd 把文件描述符fd作為標准輸入 cmd > &fd 把文件描述符fd作為標准輸出 cmd < &- 關閉標准輸入

 

 

函數

和C語言類似,Shell中也有函數的概念,但是函數定義中沒有返回值也沒有參數列表。

例如:

#! /bin/sh
foo(){ echo "Function foo is called";}
echo "-=start=-"
foo
echo "-=end=-"

 

注意函數體的左花括號’{‘和后面的命令之間必須有空格或換行如果將最后一條命令和右花括號 ’}’ 寫在同一行,命令末尾必須有 ;

在定義foo()函數時並不執行函數體中的命令,就像定義變量一樣,只是給foo這個名字一個定義,到后面調用foo函數的時候(注意Shell中的函數調用不寫括號)

才執行函數體中的命令。

Shell腳本中的函數必須先定義后調用,一般把函數定義都寫在腳本的前面,把函數調用和其它命令寫在腳本的最后

(類似C語言中的main函數,這才是整個腳本實際開始執行命令的地方)。Shell函數沒有參數列表並不表示不能傳參數,事實上,函數就像是迷你腳本,

調用函數時可以傳任意個參數,在函數內同樣是用$0、$1、$2等變量來提取參數,函數中的位置參數相當於函數的局部變量

改變這些變量並不會影響函數外面的$0、$1、$2等變量。函數中可以用return命令返回,如果return后面跟一個數字則表示函數的Exit Status

#! /bin/sh

foo()
{
    echo $0
    echo $1
    echo $2
    echo $@    
    echo "Function foo is called"; 
  return 0 }
echo $0 echo $1 echo $2 echo $@ echo "-=start=-" foo $1 bb cc // 傳參
echo $?
echo "-=end=-"

 

gec@ubuntu:~/myshare/Shell$ ./func.sh 1 2
./func.sh
1
2
1 2
-=start=-
./func.sh
1
bb
1 bb cc
Function foo is called
0
-=end=-

 

下面這個腳本可以一次創建多個目錄,各目錄名通過命令行參數傳入,腳本逐個測試各目錄是否存在,如果目錄不存在,首先打印信息然后試着創建該目錄。

#! /bin/sh
is_directory()
{
  DIR_NAME=$1
  if [ ! -d $DIR_NAME ]; then
    return 1
  else
    return 0
  fi
}
for DIR in "$@"; do
  if is_directory "$DIR"
  then :
  else
    echo "$DIR doesn't exist. Creating it now..."
    mkdir $DIR > /dev/null 2>&1
    if [ $? -ne 0 ]; then
      echo "Cannot create directory $DIR"
      exit 1
    fi
  fi
done
注意is_directory()返回0表示真返回1表示假。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM