Bash腳本編程學習筆記08:函數

来源:https://www.cnblogs.com/alongdidi/archive/2020/01/17/bash_function.html
-Advertisement-
Play Games

官方資料:Shell Functions (Bash Reference Manual) 簡介 正如我們在《Bash腳本編程學習筆記06:條件結構體》中最後所說的,我們應該把一些可能反覆執行的代碼塊整合起來,避免反覆編寫使得代碼過於臃腫。 函數正是為瞭解決這個問題而存在的。函數在定義時,可以將常用的 ...


 

官方資料:Shell Functions (Bash Reference Manual)

簡介

正如我們在《Bash腳本編程學習筆記06:條件結構體》中最後所說的,我們應該把一些可能反覆執行的代碼塊整合起來,避免反覆編寫使得代碼過於臃腫。

函數正是為瞭解決這個問題而存在的。函數在定義時,可以將常用的代碼整合為一個整體,當我們需要執行的時候,只需要調用這個函數即可。

Bash是過程式編程語言,從上至下順序執行代碼,因此函數定義必須在函數調用之前完成。

函數屬於shell的基礎特性,即不僅僅是針對於bash,包括csh、sh、ksh和zsh等都具有這裡說明的函數特性。

函數會在當前的shell環境下執行,不會創建新的進程(子shell)來解釋函數。

函數可以通過“unset -f”命令刪除。

 

函數的定義和調用

函數的定義有兩種方式。

方式一。

FUNC_NAME () {
    BODY
}

方式二。

function FUNC_NAME [()] {
    BODY
}

[()]:表示小括弧可以省略。

函數的調用只需要鍵入函數名即可,就像鍵入命令名一樣。

[root@c7-server ~]# cat function.sh 
#!/bin/bash

# 函數定義 test_func() {
echo "This is just for test." }
# 函數調用 test_func [root@c7
-server ~]# bash function.sh This is just for test.

示例:編寫一個腳本,接收一個用戶名參數,輸出用戶名、UID和預設shell。(要求以函數的形式)

#!/bin/bash

userinfo() {
    if ! id $username &> /dev/null; then
        echo "The user $username is not exists."
    else
        grep "^$username\>" /etc/passwd | cut -d : -f 1,3,7
    fi
}

username=$1
userinfo

示例:將《Bash腳本編程學習筆記06:條件結構體》中最後的服務腳本中冗餘的代碼改寫為函數,即函數式編程。

#!/bin/bash
#
# chkconfig: - 50 50
# Description: test service script
#

prog=$(basename $0)
lockfile="/var/lock/subsys/$prog"

start() {
    if [ -e $lockfile ]; then
        echo "The service $prog has already started."
    else
        touch $lockfile
        echo "The service $prog starts finished."
    fi
}
stop() {
    if [ ! -e $lockfile ]; then
        echo "The service $prog has already stopped."
    else
        rm -f $lockfile
        echo "The service $prog stops finished."
    fi
}
restart() {
    if [ -e $lockfile ]; then
        rm -f $lockfile
        touch $lockfile
        echo "The service $prog restart finished."
    else
        touch $lockfile
        echo "The service $prog starts finished."
    fi
}
status() {
    if [ -e $lockfile ]; then
        echo "The service $prog is running."
    else
        echo "The service $prog is not running."
    fi
}

case $1 in
    start)
        start
    ;;
    stop)
        stop
    ;;
    restart)
        restart
    ;;
    status)
        status
    ;;
    *)
        echo "Usage: $prog {start|stop|restart|status}"
        exit 1
    ;;
esac

 

函數的輸出和退出狀態碼

函數的輸出指的是函數體中執行的命令的輸出,STDOUT和STDERR都會輸出。其中也包括了我們使用echo或者printf的顯式的STDOUT。

如果函數中沒有return語句的話,那麼函數的退出狀態碼是函數體中最後一條命令的退出狀態碼。

如果函數中有return語句的話,那麼當函數體自上而下執行到return時,函數會立即停止並返回,函數的退出狀態碼就是return指定的退出狀態碼。

return [n]

如果return沒有指定退出狀態碼的話,那麼就是return的上一條命令的退出狀態碼。

註意不要和exit語句混淆。return用戶終止函數的執行並返回,而exit是終止了整個腳本的執行並退出。後者的作用域更廣。

 

函數的位置參數

函數和腳本一樣,都可以接收參數作為位置參數,然後在函數中引用這些參數。也支持引用與位置參數有關的特殊變數($#, $*, $@)。

向函數傳遞位置參數和向腳本傳遞位置參數的方式是一樣的。

my_func() {
    echo "$1 $2"
}

my_func arg1 arg2

記住,傳參時,不要寫成類似C語言的風格,會報錯的。

my_func(arg1, arg2)

 

練習

1、編寫一個腳本,批量添加用戶,用戶傳參給腳本,參數是欲添加的用戶名首碼,用戶名其餘部分使用數字補全。

#!/bin/bash

userAdd() {
    if id $1 &> /dev/null; then
        return 1
    else
        useradd $1
        return 0
    fi
}

for i in {01..03}; do
    username="$1$i"
    userAdd $username
    retval=$?
    if [ $retval -eq 0 ]; then
        echo "The user $username has been added."
    elif [ $retval -eq 1 ]; then
        echo "The user $username was existed."
    fi
done

這裡有一點需要註意,在for迴圈體中,一定要在函數調用後立即將函數的退出狀態碼(return)獲取並存入變數中(如該示例中的retval),而後在對該狀態碼做判斷。

如果直接多次判斷$?的值的話,那麼第一次的$?的值是函數的退出狀態碼,到了第二次,就會變成了上一句echo語句了。

2、編寫一個腳本,通過函數檢測(ping)某一主機的線上狀態。需檢測192.168.152.1~192.168.152.254這個範圍。

#!/bin/bash

ping_test() {
    ip=$1
    if ping -c 1 -q $ip &> /dev/null; then
        echo "The host $ip is online."
        return 0
    else
        return 1
    fi
}

for i in 192.168.152.{1..254}; do
    ping_test $i
done

Linux中的ping命令,預設是發送無限的請求數據包持續ping的,需要使用-c指定包的數量。這個腳本不太好,因為ping遇到不通的情況會等待一段時間,導致腳本比較耗時。

3、編寫一個腳本,通過函數實現乘法口訣表,註意,不是九九乘法口訣表,而是NN乘法口訣表,N作為用戶參數傳入腳本。

#!/bin/bash

multi() {
    N=$1
    for ((i=1;i<=N;i++)); do
        for ((j=1;j<=i;j++)); do
            echo -ne "$j*$i=$((i*j))\t"
        done
        echo
    done
}

multi $1

註意:這個腳本有瑕疵,在輸出時,當N數值過大的時候,使用“\t”製表符會使顯示較不人性化。

 

 

函數中的變數作用域

《Bash腳本編程學習筆記01:變數與多命令執行》中我們說變數有三種類型並說明瞭其作用域。

  • 本地變數:僅當前shell有效(即當前bash進程)。
  • 環境變數:當前shell及其子shell(即當前bash進程即其子bash進程)。
  • 局部變數:在某部分代碼片段中有效(例如函數)。

結合作用域的概念,我們來看下麵這個示例。

#!/bin/bash

name=tom

set_name() {
    name=jerry
    echo $name
}

set_name
echo $name

我們應該會認為這樣吧?

set_name --輸出--> jerry
echo $name --輸出--> tom

其實,真實的結果是:

[root@c7-server ~]# bash func_scope.sh
jerry
jerry

造成這種結果的原因是,局部變數是需要手動定義的,而不是其出現在函數體中就是局部變數了。

可以通過內置命令local來定義局部變數,local僅可以在函數內部使用!

local [option] name[=value] …

因此我們對腳本進行改寫,函數體中的變數明確使用local命令定義,就可以驗證我們此前說的變數作用域的理論了。

[root@c7-server ~]# cat func_scope.sh
#!/bin/bash

name=tom

set_name() {
    local name=jerry
    echo $name
}

set_name
echo $name
[root@c7-server ~]# bash func_scope.sh
jerry
tom

函數中的變數作用域,是一個難點,我其實沒搞太明白。上面的示例其實是一個非常簡單的示例,在複雜的情況下,例如函數A調用函數B時,local會變得很有幫助。具體遇到的時候,建議大家再翻閱一下官方的手冊。

 

函數的遞歸

一個函數可以調用另一個函數,而當一個函數調用自身時,就叫做函數的遞歸。

遞歸不會無限遞歸,一般會有一個邊界,在邊界處會返回,而後根據遞歸的順序逐一按照相反的順序返回。

函數的遞歸很好地解決了計算階乘(factorial)和斐波那契(fibonacci)數列。

階乘

階乘是由基斯頓·卡曼發明的數學運算。一個正整數的階乘是所有小於等於該數的正整數的積,記作“n!”。0和1的階乘都是1。

n!=1*2*3*4*...*n

階乘存在一個規律。

5!=1*2*3*4*5
4!=1*2*3*4
3!=1*2*3
2!=1*2
1!=1

因此。

5!=4!*5
4!=3!*5
3!=2!*3
...
n!=(n-1)!*n=n*(n-1)!

我們就可以把階乘定義為一個函數並通過遞歸的方式來實現階乘的計算。

#!/bin/bash

fact() {
    if [ $1 -eq 1 -o $1 -eq 0 ]; then
        echo 1
    else
        echo $(($1*$(fact $(($1-1)))))
    fi
}

fact $1

斐波那契數列

斐波那契數列是數學家萊昂納多·斐波那契以兔子繁殖為例引入的,又作兔子數列。

該數列F,第一項和第二項都為1,從第二項開始,每一項的值都是前兩項之和。

數列F
F(1)=1
F(2)=1
F(3)=F(1)+F(2)
...
F(n)=F(n-2)+F(n-1)
1 1 2 3 5 8 13 21 34 ...

因此我們可以把求斐波那契數列的第N項的值寫為一個函數。

[root@c7-server ~]# cat func_factorial.sh 
#!/bin/bash

fact() {
    if [ $1 -eq 1 -o $1 -eq 0 ]; then
        echo 1
    else
        echo $(($1*$(fact $(($1-1)))))
    fi
}

fact $1
[root@c7-server ~]# vim func_fibo.sh
[root@c7-server ~]# bash func_fibo.sh 1
1
[root@c7-server ~]# bash func_fibo.sh 2
1
[root@c7-server ~]# bash func_fibo.sh 3
2
[root@c7-server ~]# bash func_fibo.sh 4
3
[root@c7-server ~]# bash func_fibo.sh 5
5
[root@c7-server ~]# bash func_fibo.sh 6
8
[root@c7-server ~]# bash func_fibo.sh 7
13
[root@c7-server ~]# bash func_fibo.sh 8
21

不過這種方式僅僅是求得第N項的值,我們可以改為列印這個斐波那契數列。

[root@c7-server ~]# cat func_fibo.sh 
#!/bin/bash

fibo() {
    if [ $1 -eq 1 -o $1 -eq 2 ]; then
        echo -n "1 "
    else
        echo -n "$(($(fibo $(($1-2)))+$(fibo $(($1-1))))) "
    fi
}

for i in $(seq $1); do
    fibo $i
done
echo
[root@c7-server ~]# bash func_fibo.sh 10
1 1 2 3 5 8 13 21 34 55

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.ImportData主方法 把傳入為object數組類型,按照下標取出對應的參數,此處為Table和Username public object[] ImportData(object[] Param) { DataTable dt = (DataTable)Param[0]; string m ...
  • 實現把String字元串轉化為In後可用參數代碼: public string StringToList(string aa) { string bb1 = "("; if (!string.IsNullOrEmpty(aa.Trim())) { string[] bb = aa.Split(new ...
  • 本筆記摘抄自:https://www.cnblogs.com/PatrickLiu/p/7640873.html,記錄一下學習過程以備後續查用。 一、引言 很多人說原型設計模式會節省機器記憶體,他們說是拷貝出來的對象是原型的複製,不會使用記憶體。我認為這是不對的,因為拷貝出來的每一個對象都是實際 存在的 ...
  • ``` var xmlstr = @" some_appid 1413192605 component_verify_ticket some_verify_ticket "; Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i ...
  • 已有站點:HTTP80 %systemroot%\system32\inetsrv\APPCMD ADD APP /SITE.NAME:"HTTP80" /path:/Redirect /physicalPath:"C:\Windows\System32\drivers\etc" /applicat ...
  • 設計一個簡單的登錄視窗,要求輸入用戶名:小金,密碼:123456時候點登錄能正確轉到另一個視窗。 1、建立窗體應用。 2、這裡創建一個login和一個NewForm的窗體。 3、在login的窗體拖拉2個label和2個textbox和1個linklabel的控制項。一個標簽名字為用戶名,一個標簽為密 ...
  • Microsoft Visual Studio 2010 的項目為件改為Microsoft Visual Studio 2015預設打開 2010 的Solution (.Sln) file 更改為 2015 的Solution (.Sln) file ...
  • 本文將介紹如何在.NET Core3環境下使用MVVM框架Prism的使用事件聚合器實現模塊間的通信 一.事件聚合器 在上一篇 ".NET Core 3 WPF MVVM框架 Prism系列之模塊化" 我們留下了一些問題,就是如何處理同模塊不同窗體之間的通信和不同模塊之間不同窗體的通信,Prism提 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...