怎樣用 Bash 程式設計:迴圈
本文是 Bash 程式設計系列三篇中的最後一篇,來學習使用迴圈執行迭代的操作。
Bash 是一種強大的用於命令列和 shell 指令碼的程式語言。本系列的三部分都是基於我的三集 Linux 自學課程 寫的,探索怎麼用 CLI 進行 bash 程式設計。
本系列的 第一篇文章 討論了 bash 程式設計的一些簡單命令列操作,如使用變數和控制操作符。第二篇文章 探討了檔案、字串、數字等型別和各種各樣在執行流中提供控制邏輯的的邏輯運算子,還有 bash 中不同種類的擴充套件。本文是第三篇(也是最後一篇),意在考察在各種迭代的操作中使用迴圈以及怎麼合理控制迴圈。
迴圈
我使用過的所有程式語言都至少有兩種迴圈結構來用來執行重複的操作。我經常使用 for
迴圈,然而我發現 while
和 until
迴圈也很有用處。
for 迴圈
我的理解是,在 bash 中實現的 for
命令比大部分語言靈活,因為它可以處理非數字的值;與之形成對比的是,諸如標準 C 語言的 for
迴圈只能處理數字型別的值。
Bash 版的 for
命令基本的結構很簡單:
for Var in list1 ; do list2 ; done
解釋一下:“對於 list1
中的每一個值,把 $Var
設定為那個值,使用該值執行 list2
中的程式語句;list1
中的值都執行完後,整個迴圈結束,退出迴圈。” list1
中的值可以是一個簡單的顯式字串值,也可以是一個命令執行後的結果(`` 包含其內的命令執行的結果,本系列第二篇文章中有描述)。我經常使用這種結構。
要測試它,確認 ~/testdir
仍然是當前的工作目錄(PWD)。刪除目錄下所有東西,來看下這個顯式寫出值列表的 for
迴圈的簡單的示例。這個列表混合了字母和數字 — 但是不要忘了,在 bash 中所有的變數都是字串或者可以被當成字串來處理。
[student@studentvm1 testdir]$ rm *
[student@studentvm1 testdir]$ for I in a b c d 1 2 3 4 ; do echo $I ; done
a
b
c
d
1
2
3
4
給變數賦予更有意義的名字,變成前面版本的進階版:
[student@studentvm1 testdir]$ for Dept in "Human Resources" Sales Finance "Information Technology" Engineering Administration Research ; do echo "Department $Dept" ; done
Department Human Resources
Department Sales
Department Finance
Department Information Technology
Department Engineering
Department Administration
Department Research
建立幾個目錄(建立時顯示一些處理資訊):
[student@studentvm1 testdir]$ for Dept in "Human Resources" Sales Finance "Information Technology" Engineering Administration Research ; do echo "Working on Department $Dept" ; mkdir "$Dept" ; done
Working on Department Human Resources
Working on Department Sales
Working on Department Finance
Working on Department Information Technology
Working on Department Engineering
Working on Department Administration
Working on Department Research
[student@studentvm1 testdir]$ ll
total 28
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Administration
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Engineering
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Finance
drwxrwxr-x 2 student student 4096 Apr 8 15:45 'Human Resources'
drwxrwxr-x 2 student student 4096 Apr 8 15:45 'Information Technology'
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Research
drwxrwxr-x 2 student student 4096 Apr 8 15:45 Sales
在 mkdir
語句中 $Dept
變數必須用引號包裹起來;否則名字中間有空格(如 Information Technology
)會被當做兩個獨立的目錄處理。我一直信奉的一條實踐規則:所有的檔案和目錄都應該為一個單詞(中間沒有空格)。雖然大部分現代的作業系統可以處理名字中間有空格的情況,但是系統管理員需要花費額外的精力去確保指令碼和 CLI 程式能正確處理這些特例。(即使它們很煩人,也務必考慮它們,因為你永遠不知道將擁有哪些檔案。)
再次刪除 ~/testdir
下的所有東西 — 再執行一次下面的命令:
[student@studentvm1 testdir]$ rm -rf * ; ll
total 0
[student@studentvm1 testdir]$ for Dept in Human-Resources Sales Finance Information-Technology Engineering Administration Research ; do echo "Working on Department $Dept" ; mkdir "$Dept" ; done
Working on Department Human-Resources
Working on Department Sales
Working on Department Finance
Working on Department Information-Technology
Working on Department Engineering
Working on Department Administration
Working on Department Research
[student@studentvm1 testdir]$ ll
total 28
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Administration
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Engineering
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Finance
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Human-Resources
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Information-Technology
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Research
drwxrwxr-x 2 student student 4096 Apr 8 15:52 Sales
假設現在有個需求,需要列出一臺 Linux 機器上所有的 RPM 包並對每個包附上簡短的描述。我為北卡羅來納州工作的時候,曾經遇到過這種需求。由於當時開源尚未得到州政府的“批准”,而且我只在桌上型電腦上使用 Linux,對技術一竅不通的老闆(PHB)需要我列出我計算機上安裝的所有軟體,以便他們可以“批准”一個特例。
你怎麼實現它?有一種方法是,已知 rpm –qa
命令提供了 RPM 包的完整描述,包括了白痴老闆想要的東西:軟體名稱和概要描述。
讓我們一步步執行出最後的結果。首先,列出所有的 RPM 包:
[student@studentvm1 testdir]$ rpm -qa
perl-HTTP-Message-6.18-3.fc29.noarch
perl-IO-1.39-427.fc29.x86_64
perl-Math-Complex-1.59-429.fc29.noarch
lua-5.3.5-2.fc29.x86_64
java-11-openjdk-headless-11.0.ea.28-2.fc29.x86_64
util-linux-2.32.1-1.fc29.x86_64
libreport-fedora-2.9.7-1.fc29.x86_64
rpcbind-1.2.5-0.fc29.x86_64
libsss_sudo-2.0.0-5.fc29.x86_64
libfontenc-1.1.3-9.fc29.x86_64
<snip>
用 sort
和 uniq
命令對列表進行排序和列印去重後的結果(有些已安裝的 RPM 包具有相同的名字):
[student@studentvm1 testdir]$ rpm -qa | sort | uniq
a2ps-4.14-39.fc29.x86_64
aajohan-comfortaa-fonts-3.001-3.fc29.noarch
abattis-cantarell-fonts-0.111-1.fc29.noarch
abiword-3.0.2-13.fc29.x86_64
abrt-2.11.0-1.fc29.x86_64
abrt-addon-ccpp-2.11.0-1.fc29.x86_64
abrt-addon-coredump-helper-2.11.0-1.fc29.x86_64
abrt-addon-kerneloops-2.11.0-1.fc29.x86_64
abrt-addon-pstoreoops-2.11.0-1.fc29.x86_64
abrt-addon-vmcore-2.11.0-1.fc29.x86_64
<snip>
以上命令得到了想要的 RPM 列表,因此你可以把這個列表作為一個迴圈的輸入資訊,迴圈最終會列印每個 RPM 包的詳細資訊:
[student@studentvm1 testdir]$ for RPM in `rpm -qa | sort | uniq` ; do rpm -qi $RPM ; done
這段程式碼產出了多餘的資訊。當迴圈結束後,下一步就是提取出白痴老闆需要的資訊。因此,新增一個 egrep
命令用來搜尋匹配 ^Name
或 ^Summary
的行。脫字元(^
)表示行首,整個命令表示顯示所有以 Name 或 Summary 開頭的行。
[student@studentvm1 testdir]$ for RPM in `rpm -qa | sort | uniq` ; do rpm -qi $RPM ; done | egrep -i "^Name|^Summary"
Name : a2ps
Summary : Converts text and other types of files to PostScript
Name : aajohan-comfortaa-fonts
Summary : Modern style true type font
Name : abattis-cantarell-fonts
Summary : Humanist sans serif font
Name : abiword
Summary : Word processing program
Name : abrt
Summary : Automatic bug detection and reporting tool
<snip>
在上面的命令中你可以試試用 grep
代替 egrep
,你會發現用 grep
不能得到正確的結果。你也可以通過管道把命令結果用 less
過濾器來檢視。最終命令像這樣:
[student@studentvm1 testdir]$ for RPM in `rpm -qa | sort | uniq` ; do rpm -qi $RPM ; done | egrep -i "^Name|^Summary" > RPM-summary.txt
這個命令列程式用到了管道、重定向和 for
迴圈,這些全都在一行中。它把你的 CLI 程式的結果重定向到了一個檔案,這個檔案可以在郵件中使用或在其他地方作為輸入使用。
這個一次一步構建程式的過程讓你能看到每步的結果,以此來確保整個程式以你期望的流程進行且輸出你想要的結果。
白痴老闆最終收到了超過 1900 個不同的 RPM 包的清單,我嚴重懷疑根本就沒人讀過這個列表。我給了他們想要的東西,沒有從他們嘴裡聽到過任何關於 RPM 包的資訊。
其他迴圈
Bash 中還有兩種其他型別的迴圈結構:while
和 until
結構,兩者在語法和功能上都類似。這些迴圈結構的基礎語法很簡單:
while [ expression ] ; do list ; done
邏輯解釋:表示式(expression
)結果為 true 時,執行程式語句 list
。表示式結果為 false 時,退出迴圈。
until [ expression ] ; do list ; done
邏輯解釋:執行程式語句 list
,直到表示式的結果為 true。當表示式結果為 true 時,退出迴圈。
While 迴圈
while
迴圈用於當邏輯表示式結果為 true 時執行一系列程式語句。假設你的 PWD 仍是 ~/testdir
。
最簡單的 while
迴圈形式是這個會一直執行下去的迴圈。下面格式的條件語句永遠以 true
作為返回。你也可以用簡單的 1
代替 true
,結果一樣,但是這解釋了 true 表示式的用法。
[student@studentvm1 testdir]$ X=0 ; while [ true ] ; do echo $X ; X=$((X+1)) ; done | head
0
1
2
3
4
5
6
7
8
9
[student@studentvm1 testdir]$
既然你已經學了 CLI 的各部分知識,那就讓它變得更有用處。首先,為了防止變數 $X
在前面的程式或 CLI 命令執行後有遺留的值,設定 $X
的值為 0。然後,因為邏輯表示式 [ true ]
的結果永遠是 1,即 true,在 do
和 done
中間的程式指令列表會一直執行 — 或者直到你按下 Ctrl+C
抑或傳送一個 2 號訊號給程式。那些程式指令是算數擴充套件,用來列印變數 $X
當前的值並加 1.
《系統管理員的 Linux 哲學》的信條之一是追求優雅,實現優雅的一種方式就是簡化。你可以用操作符 ++
來簡化這個程式。在第一個例子中,變數當前的值被列印出來,然後變數的值增加了。可以在變數後加一個 ++
來表示這個邏輯:
[student@studentvm1 ~]$ X=0 ; while [ true ] ; do echo $((X++)) ; done | head
0
1
2
3
4
5
6
7
8
9
現在刪掉程式最後的 | head
再執行一次。
在下面這個版本中,變數在值被列印之前就自增了。這是通過在變數之前新增 ++
操作符實現的。你能看出區別嗎?
[student@studentvm1 ~]$ X=0 ; while [ true ] ; do echo $((++X)) ; done | head
1
2
3
4
5
6
7
8
9
你已經把列印變數的值和自增簡化到了一條語句。類似 ++
操作符,也有 --
操作符。
你需要一個在迴圈到某個特定數字時終止迴圈的方法。把 true 表示式換成一個數字比較表示式來實現它。這裡有一個迴圈到 5 終止的程式。在下面的示例程式碼中,你可以看到 -le
是 “小於或等於” 的數字邏輯操作符。整個語句的意思:只要 $X
的值小於或等於 5,迴圈就一直執行。當 $X
增加到 6 時,迴圈終止。
[student@studentvm1 ~]$ X=0 ; while [ $X -le 5 ] ; do echo $((X++)) ; done
0
1
2
3
4
5
[student@studentvm1 ~]$
Until 迴圈
until
命令非常像 while
命令。不同之處是,它直到邏輯表示式的值是 true
之前,會一直迴圈。看一下這種結構最簡單的格式:
[student@studentvm1 ~]$ X=0 ; until false ; do echo $((X++)) ; done | head
0
1
2
3
4
5
6
7
8
9
[student@studentvm1 ~]$
它用一個邏輯比較表示式來計數到一個特定的值:
[student@studentvm1 ~]$ X=0 ; until [ $X -eq 5 ] ; do echo $((X++)) ; done
0
1
2
3
4
[student@studentvm1 ~]$ X=0 ; until [ $X -eq 5 ] ; do echo $((++X)) ; done
1
2
3
4
5
[student@studentvm1 ~]$
總結
本系列探討了構建 Bash 命令列程式和 shell 指令碼的很多強大的工具。但是這僅僅是你能用 Bash 做的很多有意思的事中的冰山一角,接下來就看你的了。
我發現學習 Bash 程式設計最好的方法就是實踐。找一個需要多個 Bash 命令的簡單專案然後寫一個 CLI 程式。系統管理員們要做很多適合 CLI 程式設計的工作,因此我確信你很容易能找到自動化的任務。
很多年前,儘管我對其他的 Shell 語言和 Perl 很熟悉,但還是決定用 Bash 做所有系統管理員的自動化任務。我發現,有時稍微搜尋一下,我可以用 Bash 實現我需要的所有事情。
via: https://opensource.com/article/19/10/programming-bash-loops
作者:David Both 選題:lujun9972 譯者:lxbwolf 校對:wxy
訂閱“Linux 中國”官方小程式來檢視
相關文章
- 怎樣用 Bash 程式設計:語法和工具程式設計
- 怎樣用 Bash 程式設計:邏輯操作符和 shell 擴充套件程式設計套件
- 迴圈結構程式設計程式設計
- 3.迴圈結構程式設計程式設計
- Java語言程式設計—迴圈語句Java程式設計
- 04 shell程式設計之迴圈語句程式設計
- 迴圈結構程式設計之習題程式設計
- Linux Bash程式設計Linux程式設計
- C語言程式設計學習中while迴圈和do……while迴圈C語言程式設計While
- 實驗6迴圈結構程式設計(for語句的應用)程式設計
- 迴圈結構程式設計 實驗題目程式設計
- CodeMonkey少兒程式設計第6章 for迴圈程式設計
- 非同步程式設計之事件迴圈機制非同步程式設計事件
- C#程式設計基礎第七課:C#中的基本迴圈語句:while迴圈、do-while迴圈、for迴圈、foreach迴圈的使用C#程式設計While
- 彙編實驗小記(五)-迴圈程式設計程式設計
- 非同步程式設計 101:寫一個事件迴圈非同步程式設計事件
- CodeMonkey少兒程式設計第3章 times迴圈程式設計
- 好程式設計師雲端計算教程分享Shell程式設計之for迴圈結構程式設計師
- shell程式設計–bash變數程式設計變數
- 實驗2C語言分支與迴圈基礎應用程式設計C語言程式設計
- 實驗2_C語言分支與迴圈基礎應用程式設計C語言程式設計
- 實驗2 c語言分支與迴圈基礎應用程式設計1C語言程式設計
- 實驗2 C語言分支與迴圈基礎應用程式設計-1C語言程式設計
- 好程式設計師Java培訓分享For迴圈詳解程式設計師Java
- 好程式設計師Java教程分享Java 迴圈結構程式設計師Java
- [譯] Flutter 非同步程式設計:Future、Isolate 和事件迴圈Flutter非同步程式設計事件
- Kotlin 迴圈與函式詳解:高效程式設計指南Kotlin函式程式設計
- 如何理解Python的迴圈設計Python
- Bash程式設計007——函式(一)程式設計函式
- 實驗5 迴圈結構程式設計(while、do-while語句的應用)程式設計While
- 實驗5迴圈結構程式設計(while、do-while語句的應用)程式設計While
- Linux系統程式設計 - 07. 迴圈建立N個子程式分析Linux程式設計
- 人人都能學會的python程式設計教程5:迴圈 2Python程式設計
- Rust 程式設計影片教程(進階)——015_1 引用迴圈Rust程式設計
- 好程式設計師Python培訓分享For迴圈用法詳解程式設計師Python
- 好程式設計師分享迴圈內的回撥函式程式設計師函式
- 關於迴圈主鍵的設計
- shell程式設計–bash變數介紹程式設計變數