1. 程式人生 > >Shell中的函式呼叫

Shell中的函式呼叫

本章學習內容

        ----------函式介紹

        ----------函式定義

        ----------函式使用

        ----------區分returnexit

        ----------刪除函式

        ----------注意事項

1、介紹函式

    通俗地講,函式就是將一組功能相對獨立的程式碼集中起來,形成一個程式碼塊,這個程式碼可以完成某個具體的功能(這裡理解為命令的堆積也可以)。從本質上講,函式是一個函式名到某個程式碼塊的對映。也就是說,使用者在定義了函式之後,就可以通過函式名來呼叫其所對應的一組程式碼(稱為函式呼叫)。

2、定義函式

    <1>倆種方式     

       ▲function fname {

           command
           command
         }

       ▲fname {

           command
           command
         } 

    <2>函式名不能和命令相同,命令優先順序高於函式

    <3>函式命名規則

       不可與命令相同,否則命令無法正常使用。  

       函式優先順序>命令

       不可定義與函式名同名的別名,否則函式無法使用。

       別名優先順序>函式

       所以優先順序:別名 >  函式   >  命令

    <4>函式中的變數設為區域性變數,防止與shell的變數衝突

       驗證

# 建立函式檔案fun2,定義test函式
[[email protected] ~]#vim fun2
function test()
{
 a=first
 echo "a=$a"
}
# 編寫指令碼testfun2.sh
[[email protected] ~]#vim testfun2.sh 
#!/bin/bash
#
a=second
source fun2
test
echo "a=$a"
# 執行指令碼
[
[email protected]
 ~]#bash testfun2.sh  a=first a=first

結果表明雖然指令碼中定義了a=second,但是執行test函式之後,已經變成了first。為了解決指令碼中變數和函式中變數的混淆,一般將函式中的變數定義為區域性變數。

3、使用函式

    <1>載入函式

       子shell中如果需要使用父shell中的函式,需要將函式載入至本shell

       載入方式

           source FUNCTION

           . FUNCTION      

       注:修改函式之後,必須重新載入shell才能生效

    <2>呼叫函式

       輸入函式名再加引數即可 

4、函式返回值return和exit的不同

    return:退出當前函式

        return :從函式中返回,用最後狀態命令決定返回值

        return 0 :無錯誤返回。

        return 1-255 :有錯誤返回

    exit:退出當前指令碼

5、刪除函式

    unset FUNCTION:刪除函式

    set:檢視所有定義的函式

6、關於函式引數傳遞問題

    不知道大家有沒有過這樣的疑問,在函式中引數是如何傳遞的呢?因為在C語言中是這樣定義一個函式的:int cmp(int a, int b),變數已經在函式中宣告。但是shell中的函式並沒有定義引數,這個過程是怎麼完成的呢?小編剛開始也很疑惑,後來才明白shell的函式中通過函式位置變數和變數的引用就可以實現引數的傳遞。下面是一些示例以及傳參的注意事項,大家可以參考一下。

# 建立函式檔案fun1,定義函式add
function add() {
local sum
 sum=$[$1+$2]
 echo $sum
}
# 編寫指令碼testfun1.sh
#!/bin/bash
source fun1
add $1 $2
####執行指令碼:
[[email protected] ~/bin]#bash funadd.sh 1 2
3

指令碼中的$1和$2是指令碼引用命令列的位置變數,而函式中的$1和$2是引用指令碼中的第一個變數和第二個變數,我們稱其為函式位置變數。(實現傳參的方法之一)

看下面這種情況

# 建立函式檔案fun1,定義函式add
function add() {
local sum
 sum=$[$1+$2]
 echo $sum
}
# 編寫指令碼testfun1.sh
#!/bin/bash
source fun1
read -p "Please input two figures:" a b    # 只新增此行
add $1 $2
# 執行指令碼
[[email protected] ~/bin]#bash funadd.sh 
Please input two figures:1 2
/root/bin/fun1: line 3: +: syntax error: operand expected (error token is "+")

為什麼這裡會報錯呢?因為在指令碼中引用的位置變數$1和$2並不是1和2,也就是說$1和$2並沒有值,函式中並沒有引用到數值,計算結果當然是錯的。從側面也說明了read是無法引用位置變數的。

那麼,該怎麼解決呢?答案如下

#!/bin/bash
source fun1
read -p "Please input two figures:" a b
add $a $b                          # 只改變此行
[[email protected] ~/bin]#bash funadd.sh 
Please input two figures:1 2
3

因為此時的$a和$b引用了命令列輸入的1和2,而函式中又會呼叫這倆個值,結果計算正確。

再看一種情況

# 建立函式檔案fun1,定義函式add
function add() {
  local sum
  sum=$[$a+$b]
  echo $sum
}
# 編寫指令碼testfun1.sh
#!/bin/bash
source fun1
read -p "Please input two figures:" a b
add $1 $2   或者   add  $a $b   或者   add a b   # 三種情況
# 執行指令碼
[[email protected] ~/bin]#bash funadd.sh 
Please input two figures:1 2
3

小編已經實驗過,這三種情況都能執行出結果,為了書寫簡便,都一一列舉再在此處。但是大家知道這是為什麼嗎?因為函式直接引用了命令列的引數(實現傳參的方法之二,弱型別程式語言特有的變數引用),也就不需要通過指令碼來傳參了。所以互動式輸入的指令碼中呼叫的函式如果沒有使用$1和$2之類的字元來引用變數,而是直接使用了$a和$b之類的字元,那是此時傳參的意義也就不存在了。

不過在非互動式的指令碼中呼叫的函式也可以直接呼叫指令碼中的變數(實現傳參的方法之三)

再舉一個例子加深一下印象

# 建立函式檔案fun3,定義函式string
function string() {
if [ $1 == ha ]; then
  echo "nihao"
fi
}
# 編寫指令碼testfun3.sh
#!/bin/bash
#
source fun3
a=ha
string $a
unset a
####執行指令碼
[[email protected] ~]#bash testfun3.sh 
nihao
# 建立函式檔案fun3,定義函式string
function string() {
if [ $a == ha ]; then
  echo "nihao"
fi
}
# 編寫指令碼testfun3.sh
#!/bin/bash
#
source fun3
a=ha
string $a  或者  string $1  或者  string
unset a
####執行指令碼
[[email protected] ~]#bash testfun3.sh 
nihao

課後強化練習

1、編寫服務指令碼/root/bin/testsrv.sh,完成如下要求

(1) 指令碼可接受引數:start, stop, restart, status

(2) 如果引數非此四者之一,提示使用格式後報錯退出

(3) 如是start:則建立/var/lock/subsys/SCRIPT_NAME, 並顯示“啟動成功”

考慮:如果事先已經啟動過一次,該如何處理?

(4) 如是stop:則刪除/var/lock/subsys/SCRIPT_NAME, 並顯示“停止完成”

考慮:如果事先已然停止過了,該如何處理?

(5) 如是restart,則先stop, 再start

考慮:如果本來沒有start,如何處理?

(6) 如是status, 則如果/var/lock/subsys/SCRIPT_NAME檔案存在,則顯示“SCRIPT_NAMEis running...”

如果/var/lock/subsys/SCRIPT_NAME檔案不存在,則顯示“SCRIPT_NAME is stopped...”

其中:SCRIPT_NAME為當前指令碼名

# 建立函式檔案testsrv,定義多個函式
[[email protected] ~/bin/dir]#vim testsrv
function start() {
    if [ -e /var/lock/subsys/testsrv.sh ]; then
      echo "Already start..."
    else
      touch /var/lock/subsys/testsrv.sh
      echo "start ok"
    fi
}

function stop() {
    if [ ! -e /var/lock/subsys/testsrv.sh ]; then
      echo "Already stop..."
    else
      rm -f /var/lock/subsys/testsrv.sh
      echo "stop ok"
    fi
}

function restart() {
    stop 
    start 
}

function status() {
  if [ -e /var/lock/subsys/testsrv.sh ]; then
    echo "testsrv.sh is running..."
  else
    echo "testsrv.sh is stopped..."
  fi
}

function quit() {
  exit 2
}

function again() {
  while [ $1 != start -a $1 != stop -a $1 != restart -a $1 != status ]; do
  read -p "Error,please enter again:" CHOICE
  done
}       # 最後一個函式未使用,問題。

# 編寫指令碼testsrv3.sh   
source /root/bin/dir/testsrv
cat << EOF
four choices for you:
start)
stop)
restart)
status)
EOF

read -p "please input your choice(start|stop|restart|status|quit):" CHOICE
  while [ $CHOICE != "start" -a $CHOICE != "stop" -a $CHOICE != "restart" -a $CHOICE != "status" ]; do
    read -p "Error,please enter again:" CHOICE
   done
case $CHOICE in 
start)
  start 
  ;;
stop) 
  stop
  ;;
restart) 
  restart 
  ;;
status) 
  status
  ;;
quit)
  quit 
  ;;
esac

或者使用select

# 函式不變,改寫指令碼
#!/bin/bash
source testsrv
PS3="please input your choice:"
select CHOICE in start stop restart status quit; do
  case $CHOICE in
  start)
    start
    ;;
  stop)
    stop 
    ;;
  restart)
    restart 
    ;;
  status)
    status
    ;;
  quit)
    quit 
    ;;
  *)
    echo "Error,please enter again..."
  esac
done

2、編寫指令碼/root/bin/copycmd.sh

(1) 提示使用者輸入一個可執行命令名稱;

(2) 獲取此命令所依賴到的所有庫檔案列表

(3) 複製命令至某目標目錄(例如/mnt/sysroot)下的對應路徑下;

如:/bin/bash ==> /mnt/sysroot/bin/bash

/usr/bin/passwd==> /mnt/sysroot/usr/bin/passwd

(4) 複製此命令依賴到的所有庫檔案至目標目錄下的對應路徑下:

如:/lib64/ld-linux-x86-64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2

(5)每次複製完成一個命令後,不要退出,而是提示使用者鍵入新的要複製的命令,並重復完成上述功能;直到使用者輸入quit退出

# 建立函式檔案copycmd,定義多個函式
function query() {
  ldd /usr/bin/$1
}

function copy() {
  mkdir /mnt/sysroot &> /dev/null
  local dir1
  dir1=/mnt/sysroot
  cp /usr/bin/$1 $dir1
  mkdir /mnt/sysroot/lib64 &> /dev/null
  local dir2
  dir2=/mnt/sysroot/lib64
  cp `ldd /usr/bin/ls | sed -r '[email protected][[:space:]]+.*=>?[[:space:]]?(.*)[[:space:]].*@\[email protected]'` $dir2 &> /dev/null
}

function quit() {
  if [ $1 == quit ]; then
    exit
  fi
}

# 編寫指令碼copycmd4
#!/bin/bash
source copycmd
PS3='Please input your option:'
select option in run quit; do   # 列出倆個選項,是否執行或者退出,select自帶迴圈功能
  case $option in                      退出即可,option是run和quit
  run)
    read -p "Input your cmd:" CMD   # 賦初值,否則無法與quit比較,直接錯誤                                                       
    until [[ $CMD == quit ]]; do
      if  which $CMD &> /dev/null; then
        query $CMD           # 呼叫函式
        copy $CMD            # 呼叫函式
      else
        read -p "Error,again:" CMD  
      fi
    read -p "Input your cmd:" CMD   # 糾正初值,與quit比較
    done
  ;;
  quit)
    quit $option
  ;;
  esac
done