掌握 Bash 腳本中的迴圈與循環控制技巧 | Shell 結構化 & 循環

掌握 Bash 腳本中的迴圈與循環控制技巧 | Shell 結構化 & 循環

Overview of Content

這篇文章將引導您深入了解 Bash 腳本中的迴圈和循環控制技巧

從簡單的 for-in 命令到 whileuntil 語句,我們將一步步地探索如何利用這些工具來處理列表數據、讀取變數、以及執行命令。此外,我們還會深入研究如何使用 IFS 字串分隔符和通配符來擴展您的腳本功能。無論您是初學者還是有一定經驗的用戶,這篇文章都將為您打開 Bash 腳本的新世界

寫文章分享不易,如有引用參考請詳註出處,如有指導、意見歡迎留言(如果覺得寫得好也請給我一些支持),感謝 😀

個人程式分享時比較注重「縮排」,所以可能不適合手機的排版閱讀,建議切換至「電腦版」、「平板版」視窗看 😀


for-in 命令

bash 提供 for 命令,其格式如下:


for var in list
do
    commands
    
done

● 也可以把全部寫在同一句 command 中(使用 ; 隔開每個指令)

for <var> in <list> ; do ; <commands> ; done


讀取列表數據

● 如果有使用過其他語言(C、Java... 等等),其實 Shell 的 for 回圈其實差不多意思,它會在循環的過程中刷新暫存值


#!/bin/bash

# 暫存值 tmp
for tmp in A B C D E F G ; do
    echo "I get the word of $tmp"
done

● 並且列表是使用 空格 來區分每個數據,如果數據中有空格,可以使用以下兩個方案解決

A. 跳脫字元 \


#!/bin/bash

for word in Hello, I\'m Kyle, we\'re firend. ; do
    echo "word: $word"
done

B. 雙引號


#!/bin/bash

for test in "Hello world" "Check out" "Yo yo 123" ; do
    echo "$test"
done

讀取數據變數

● 從變數中讀取列表需要注意:1. 使用 $ 符號引用變量、2. 仍使用空格來區分每個數據


#!/bin/bash

var_list="My meet at 8.30am."

# 記得使用 `$` 引用
for tmp in $var_list ; do
    echo "var word: $tmp"
done

讀取命令數據

● 可以用 命令替換( 格式:$( command ))來執行任何能輸出列表的命令,再放置到 for 原本 list 的位置

以下使用 cat 命令輸出指令

A. 先產生一個文件,之後會使用 cat 命令讀取該文件


Hello
World
ABC
Shopping mall
"Yo yo man"
I\'m Kyle

B. 使用 命令替換 來執行會輸出列表的命令


#!/bin/bash

# 目前該檔案在相同路徑之下,如果不在同路徑之下,需使用完全路徑
file_name="fakeFileList"

for item in $( cat $file_name ) ; do
    echo "Visit file by for, word: $item"
done

執行 Shell 的結果如下

● 透過結果可以看出輸出列表會以 空格換行 來區分每個字串

● 另外可以看出使用跳脫字元 \“” 並不會讓字串當成一個句子完整輸出

這跟 字串分隔符 有關,可以透過它來解決這個問題

拓展細節:IFS 字串分隔符

● IFS 内部字段分隔符 (internal field separator) 是用來標明,如何去分隔字符為一個字串;bash shell 默認 IFS 如下

空格

制表符\t

換行\n

● 替換 IFS 的方式如下

IFS=<分隔符號*>

範例:IFS=$'\n':;" 代表的含義就是就是使用 \n:;" 作為字段分隔符

換行符號一般來說就是一個符號,如果超過一個符號就必須使用 $""$'' 來包裹多符號

● 替換 IFS 為 \n


#!/bin/bash

# \n 就是超過兩個符號,所以必須使用 $'' 包裹
IFS=$'\n'

file_name="fakeFileList"

for item in $( cat $file_name ) ; do
    echo "Visit file by for, word: $item"
done

分隔符 IFS=$'\n'

替換 IFS 為 : 符號 來讀取 /etc/passwd 檔案


#!/bin/bash

IFS_OLD=$IFS

IFS=:

for item in $( cat /etc/passwd | grep alien ) ; do
    echo "Visit file by for, word: $item"
done

分隔符 IFS=:

通配符:讀取目錄

● list 列表可以使用「通配符」加部份文件名,來讀取文件目錄


#!/bin/bash

# 讀取 /etc/s 開頭的所有文件
for file in /etc/s* ; do
    if [ -d "$file" ] ; then
        echo "$file is directory."

    elif [ -f "$file" ] ; then
        echo "$file is file."

    else 
        echo "Strang $file"

    fi
done


C 語言風格的 for

Bash shell 也可以使用類似 C 語言風格的迴圈,其格式如下


for (( variable assignment ; condition ; iteration process )) 
do
    commands
done

● 注意,C 語言迴圈與 bash shell 差異:

● 變量赋值可以有空格(Shell 變量)

● 條件變量不用 $ 引用

● 迭代過程的數學運算不用特別處理(不用使用 expr、或特殊符號)

如同 if 迴圈的高級特性 (( expression ))


C 語言的迴圈腳本

● Bash shell 檔案中使用 C 語言風格的迴圈


#!/bin/bash

for (( i = 1; i <= 10; i++)) ; do

    echo "Number is $i"

done

● 既然是 C 語言的迴圈,就可以使用多個條件變量,變量之間使用逗號 , 區分


#!/bin/bash

# 多條件變量
for (( i = 1, j = 10; i <= 10; i++, j--)) ; do

    echo "The number of i is $i, and j is $j"

done


while 語句

while 語句的命令會判斷 test command 的退出碼,如果是 0(代表的就是「判斷成立」)就會無限一直執行迴圈內容,其格式如下:


while test command
do 
    other commands
done

# 或是使用 [] 替代 test -------------------------------------

while [ command ]
do 
    other commands
done

● 想想,其實如果要達成「判斷成立」的特性,其實並非只有使用 test 語句可以達成,也可以使用其他的判斷、特性去達成!


while ls | grep -q pdf; do
    echo -n "There is afile with pdf, $(pwd)"
done

while:基礎使用

● while 每次都會檢查 test 命令的退出碼,以下是基本用法


#!/bin/bash

var=10

# 使用 test 命令判斷 gather than 0 (大於 0)
while [ $var -gt 0 ] ; do
    # 修改變量條件
    echo "variable: $(( var-- ))" 

done

記得在 while 迴圈內修改變量條件,否則容易造成死迴圈

reference link

while:多條件判斷

● while 中可以有多條件,不同條件只需要換行即可


#/bin/bash

var=10

# 多條件 while 判斷
while echo $var
    [ $var -ge 0 ] ; do

    echo "variable: $(( --var ))"

done

while 迴圈的結束條件是,所有判斷的退出碼都為 0 才會退出

while 配合管道 pipe

● while 的測試命令(test)可以配合 pipe(管道) | 使用是滿常見的操作,這裡只要注意 test 測試命令的判斷是取管道的最後返回值做判斷

範例如下


#/bin/bash

FILE=/tmp/whileTtest.$$

echo firstline > $FILE

# 將 `tail -10 $FILE` 的結果丟至 `grep -q firstline` 中
# 也就是仍是一個 test 判斷
# 判斷重點在 `grep -q firstline`
while tail -10 $FILE | grep -q firstline; do    #

    # 對螢幕輸出資訊
    echo -n Number of lines in $FILE:' '
    wc -l $FILE | awk '{print $1}'
    
    # 追加輸出
    echo newline >> $FILE
    cat -n $FILE

done

rm -f $FILE

until 語句

until 語句的判斷與 while 相反,until 判斷 test 語句退出碼不為 0 就一直執行迴圈;其格式如下


until test commands
do
    other commands
done

# 使用 [] 替代 test -------------------------------------
until [ commands ]
do
    other commands
done

until 使用

● until 的使用同 while 差異不大,只是要記得 until 是判斷退出值為 0 才結束迴圈


#!/bin/bash

var=10

until test $var = 0 ; do

    echo "variable: $(( var-- ))"

done

until 也可以執行多條件判斷,這邊就不示範了


循環控制

上面所有的循環語句(for、while、until)都可以透過以下兩個命令來控制循環內部的情況

● break 命令

● continue 命令

循環中斷:break

● break 命令可以用在循環中,其格式如下(可以執定跳出的層數,沒有指定預設則為 1)


break [跳出層數=1]

A. 跳出單個循環


#!/bin/bash

for (( a = 10; a > 0; a-- )) ; do

    if [ $a -ne 4 ] ; then
        echo "current number: $a"
    else 
        echo "break for at :$a"
        break
    fi
done	

B. 跳出指定循環


#!/bin/bash

for (( a = 10; a > 0; a-- )) ; do

    echo "Inner for : $a"

    var=30
    while [ $var -gt 5 ] ; do

        echo "Inner while: $(( var -= 5 ))"

        if [ $var -eq 5 ] ; then
            echo "break from inner cricle. $var"
            break 2
        fi
    done

done

循環跳過:continue

● continue 命令可以中斷當前執行的迴圈,但 並不會跳出,而時執行下一次迴圈,其格式如下(可以執定跳過的層數,沒有指定預設則為 1)


continue [跳過層數=1]

A. 跳過單個循環


#!/bin/bash

var=5

until [ $var -eq 0 ] ; do

    var=$[ $var - 1 ]

    if [ $var -eq 3 ] ; then
        # 跳過 var == 3 的數值,再次進入 until 迴圈
        continue
    fi

    echo "variable: $var"
done

B. 跳過指定循環


#!/bin/bash

var=5

until [ $var -eq 0 ] ; do

    var=$[ $var - 1 ]

    echo "variable until: $var"

    for (( a = 0; a < 5; a++ )) ; do

        echo "vairable for: $a"

        if [ $a -eq 2 ] ; then
            ## 跳到外部的 until 回圈
            continue 2
        fi

    done

done


循環結果的輸出:done >

透過重新定向,可以將回圈的輸出的內容導向到某個文件(這時螢幕就不會輸出任何數據);其格式如下


while [ command ]
do
    other command
done > file

迴圈重新導向範例:


#!/bin/bash

for tmp in A B C D E F G ; do
	echo "I get the word of $tmp"

# 輸出重新導向文件
done > "The command for command finish."


更多 Linux Shell 知識

探索 Debian 系統上的套件管理系統(PKMS 概念)| 套件倉庫

Linux 環境、服務:用戶、權限管理

Linux 環境、服務、管理

在這個主題中,你可以探索各種 Linux 環境、服務和管理相關的重要概念…

了解 Linux 組成:內核責任、GNU 工具、桌面環境 | 基礎版到發行版

在這裡,你將了解 Linux 系統的基本組成部分,包括內核、GNU 工具和桌面環境,並了解不同 Linux 發行版之間的差異

探索 X Window 服務與桌面環境:Linux 視窗 XWindow 服務、D-Bus 機制

這裡探討了 Linux 系統中 X Window 服務和 D-Bus 機制的工作原理,讓你深入了解桌面環境的背後運作方式

Linux 系統管理入門:安全性、用戶管理與權限設定指南

在這個指南中,你將學習如何管理 Linux 系統,包括安全性、用戶管理和權限設定,以確保系統的安全和有效運作

認識身份驗證與權限管理:UNIX UID、使用者驗證與 PAM | PAM 設定

這裡介紹了 UNIX 系統中的身份驗證機制和權限管理方式,包括 UNIX UID、使用者驗證和 PAM(Pluggable Authentication Modules)設定

Linux Shell 相關知識

Linux Shell 相關知識

這個主題涵蓋了各種與 Linux Shell 相關的知識,從基礎到進階都有…

Shell 基礎知識:包括 Shell 的基礎使用以及差異,還有變數以及計算

認識命令行:Shell 類型、命令差異 | Sub Shell 關係 | Builtin 命令

這裡介紹了命令行中不同類型的 Shell,以及各種 Shell 命令之間的區別和內建命令的用法

Shell 全局及區域變數、特殊變數及環境變數 | Shell 啟動順序 | Array 變數

學習如何在 Shell 中使用全局變數、特殊變數和環境變數,以及如何設置和管理這些變數

探索腳本與命令:Shell 腳本的必備相關知識 | 腳本的數學運算

這裡介紹了 Shell 腳本的基本知識和常用技巧,包括如何執行腳本和進行數學運算

Shell 結構化

Shell 腳本程式中的條件語句和高級特性 | Shell 結構化 & 判斷

該篇文章是介紹結構化腳本的基礎以及特殊的高級技巧,轉著於腳本中的邏輯判斷

掌握 Bash 腳本中的迴圈與循環控制技巧 | Shell 結構化 & 循環

接著是腳本中的循環與控制的技巧,它可以讓我們在 Shell 腳本中有更多的判斷與技巧

探索 Shell 函數與腳本庫 | Shell 結構化 & 函數

這裡你將會學在到結構化腳本的重要技巧「函數」,它可以讓你建立可重複利用的腳本,以節省我們之後開發的時間

Shell 掌握參數處理與用戶輸入技巧 | Shell 結構化 &輸入互動

不可互動的腳本有時候相對無趣,這篇文章有分享如何讓腳本與使用者產生交互互動,並依照使用者輸入的參數做讀取

Shell script 進階

Shell 命令輸入與輸出指南:掌握標準文件描述符與重定向 | 臨時文件

掌控 Shell、指令的輸出輸入是成為進階使用 CLI 之人必經的一課,透過這篇文章可以了解到輸出輸入、臨時文件等等資訊

探索 Linux 訊號與後台進程管理:安排定期啟動腳本 | 運行時啟動腳本

信號可以算是 Shell 腳本與 Linux 系統之間的通訊方式,透過這個文章,我們可以了解到信號與 Shell 腳本的安排時間

Linux 硬體規劃:檔案系統、分區

Linux 硬體規劃:檔案系統、分區

最後,這個主題涵蓋了 Linux 系統中硬體結構相關的重要概念…

Shell 文件和目錄操作的常用命令和技巧 | 尋找檔案

這裡介紹了 Shell 中常用的文件和目錄操作命令,包括如何尋找文件和管理文件系統

深入探索 Linux 文件系統與硬體管理 | 分區、檔案系統、邏輯卷 | inode

學習如何管理 Linux 文件系統,包括分區、文件系統和邏輯卷等相關概念,以及文件系統中的 inode 機制

理解 Unix/Linux 設備偵測、建立 | 認識與使用 udevd | SCSI 與 Linux 核心

這裡介紹了 Unix/Linux 系統中的設備偵測和管理,包括如何使用 udevd 和 SCSI 相關的 Linux 核心知識


Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響