Bash程式設計基礎之語法和工具
Shell 是作業系統的 命令直譯器,其中 Bash 是我最喜歡的。每當使用者或者系統管理員將 命令輸入系統的時候, Linux 的 shell 直譯器就會把這些命令轉換成作業系統可以理解的形式。而執行結果返回 shell 程式後,它會將結果輸出到 STDOUT(標準輸出),預設情況下,這些結果會顯示在你的終端。所有我熟悉的 shell 同時也是一門程式語言。 |
Bash 是個功能強大的 shell,包含眾多便捷特性,比如:tab 補全、命令回溯和再編輯、別名等。它的命令列預設編輯模式是 Emacs,但是我最喜歡的 Bash 特性之一是我可以將其更改為 Vi 模式,以使用那些儲存在我肌肉記憶中的的編輯命令。
然而,如果你把 Bash 當作單純的 shell 來用,則無法體驗它的真實能力。我在設計一套包含三卷的 Linux 自學課程時(這個系列的文章正是基於此課程),瞭解到許多 Bash 的知識,這些是我在過去 20 年的 Linux 工作經驗中所沒有掌握的,其中的一些知識就是關於 Bash 的程式設計用法。不得不說,Bash 是一門強大的程式語言,是一個能夠同時用於命令列和 shell 指令碼的完美設計。
Bash 是 Bourne Again Shell 的縮寫,因為 Bash shell 是 基於 更早的 Bourne shell,後者是 Steven Bourne 在 1977 年開發的。另外還有很多其他的 shell 可以使用,但下面四個是我經常見到的:
- csh:C shell 適合那些習慣了 C 語言語法的開發者。
- ksh:Korn shell,由 David Korn 開發,在 Unix 使用者中更流行。
- tcsh:一個 csh 的變種,增加了一些易用性。
- zsh:Z shell,整合了許多其他流行 shell 的特性。
所有 shell 都有內建命令,用以補充或替代核心工具集。開啟 shell 的 man 說明頁,找到“BUILT-INS”那一段,可以檢視都有哪些內建命令。
每種 shell 都有它自己的特性和語法風格。我用過 csh、ksh 和 zsh,但我還是更喜歡 Bash。你可以多試幾個,尋找更適合你的 shell,儘管這可能需要花些功夫。但幸運的是,切換不同 shell 很簡單。
所有這些 shell 既是程式語言又是命令直譯器。下面我們來快速瀏覽一下 Bash 中整合的程式設計結構和工具。
大多數場景下,系統管理員都會使用 Bash 來傳送簡單明瞭的命令。但 Bash 不僅可以輸入單條命令,很多系統管理員可以編寫簡單的命令列程式來執行一系列任務,這些程式可以作為通用工具,能節省時間和精力。
編寫 CLI 程式的目的是要提高效率(做一個“懶惰的”系統管理員)。在 CLI 程式中,你可以用特定順序列出若干命令,逐條執行。這樣你就不用盯著螢幕,等待一條命令執行完,再輸入另一條,省下來的時間就可以去做其他事情了。
自由線上計算機詞典(FOLDOC)對於程式的定義是:“由計算機執行的指令,而不是執行它們的物理硬體。”普林斯頓大學的 WordNet 將程式定義為:“……計算機可以理解並執行的一系列指令……”維基百科上也有一條不錯的關於計算機程式的條目。
總結下,程式由一條或多條指令組成,目的是完成一個具體的相關任務。對於系統管理員而言,一段程式通常由一系列的 shell 命令構成。Linux 下所有的 shell (至少我所熟知的)都有基本的程式設計功能,Bash 作為大多數 linux 發行版的預設 shell,也不例外。
本系列用 Bash 舉例(因為它無處不在),假如你使用一個不同的 shell 也沒關係,儘管結構和語法有所不同,但程式設計思想是相通的。有些 shell 支援某種特性而其他 shell 則不支援,但它們都提供程式設計功能。Shell 程式可以被存在一個檔案中被反覆使用,或者在需要的時候才建立它們。
最簡單的命令列程式只有一或兩條語句,它們可能相關,也可能無關,在按Enter鍵之前被輸入到命令列。程式中的第二條語句(如果有的話)可能取決於第一條語句的操作,但也不是必須的。
這裡需要特別講解一個標點符號。當你在命令列輸入一條命令,按下Enter鍵的時候,其實在命令的末尾有一個隱含的分號(;)。當一段 CLI shell 程式在命令列中被串起來作為單行指令使用時,必須使用分號來終結每個語句並將其與下一條語句分開。但 CLI shell 程式中的最後一條語句可以使用顯式或隱式的分號。
下面的例子會闡明這一語法規則。這段程式由單條命令組成,還有一個顯式的終止符:
[student@studentvm1 ~]$ echo "Hello world." ; Hello world.
看起來不像一個程式,但它確是我學習每個新程式語言時寫下的第一個程式。不同語言可能語法不同,但輸出結果是一樣的。
讓我們擴充套件一下這段微不足道卻又無所不在的程式碼。你的結果可能與我的有所不同,因為我的家目錄有點亂,而你可能是在 GUI 桌面中第一次登入賬號。
[student@studentvm1 ~]$ echo "My home directory." ; ls ; My home directory. chapter25 TestFile1.Linux dmesg2.txt Downloads newfile.txt softlink1 testdir6 chapter26 TestFile1.mac dmesg3.txt file005 Pictures Templates testdir TestFile1 Desktop dmesg.txt link3 Public testdir Videos TestFile1.dos dmesg1.txt Documents Music random.txt testdir1
現在是不是更明顯了。結果是相關的,但是兩條語句彼此獨立。你可能注意到我喜歡在分號前後多輸入一個空格,這樣會讓程式碼的可讀性更好。讓我們再執行一遍這段程式,這次不要帶結尾的分號:
[student@studentvm1 ~]$ echo "My home directory." ; ls
輸出結果沒有區別。
像所有其他程式語言一樣,Bash 支援變數。變數是個象徵性的名字,它指向記憶體中的某個位置,那裡存著對應的值。變數的值是可以改變的,所以它叫“變~量”。
Bash 不像 C 之類的語言,需要強制指定變數型別,比如:整型、浮點型或字元型。在 Bash 中,所有變數都是字串。整數型的變數可以被用於整數運算,這是 Bash 唯一能夠處理的數學型別。更復雜的運算則需要藉助 bc 這樣的命令,可以被用在命令列程式設計或者 指令碼中。
變數的值是被預先分配好的,這些值可以用在命令列程式設計或者指令碼中。可以通過變數名字給其賦值,但是不能使用 $ 符開頭。比如,VAR=10 這樣會把 VAR 的值設為 10。要列印變數的值,你可以使用語句 echo $VAR。變數名必須以文字(即非數字)開始。
Bash 會儲存已經定義好的變數,直到它們被取消掉。
下面這個例子,在變數被賦值前,它的值是空(null)。然後給它賦值並列印出來,檢驗一下。你可以在同一行 CLI 程式裡完成它:
[student@studentvm1 ~]$ echo $MyVar ; MyVar="Hello World" ; echo $MyVar ; Hello World [student@studentvm1 ~]$
注意:變數賦值的語法非常嚴格,等號(=)兩邊不能有空格。
那個空行表明了 MyVar 的初始值為空。變數的賦值和改值方法都一樣,這個例子展示了原始值和新的值。
正如之前說的,Bash 支援整數運算,當你想計算一個陣列中的某個元素的位置,或者做些簡單的算術運算,這還是挺有幫助的。然而,這種方法並不適合科學計算,或是某些需要小數運算的場景,比如財務統計。這些場景有其它更好的工具可以應對。
下面是個簡單的算術題:
[student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var1*Var2))" Result = 63
好像沒啥問題,但如果運算結果是浮點數會發生什麼呢?
[student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var1/Var2))" Result = 0 [student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var2/Var1))" Result = 1 [student@studentvm1 ~]$
結果會被取整。請注意運算被包含在 echo 語句之中,其實計算在 echo 命令結束前就已經完成了,原因是 Bash 的內部優先順序。想要了解詳情的話,可以在 Bash 的 man 頁面中搜尋 “precedence”。
Shell 的控制運算子是一種語法運算子,可以輕鬆地建立一些有趣的命令列程式。在命令列上按順序將幾個命令串在一起,就變成了最簡單的 CLI 程式:
command1 ; command2 ; command3 ; command4 ; . . . ; etc. ;
只要不出錯,這些命令都能順利執行。但假如出錯了怎麼辦?你可以預設好應對出錯的辦法,這就要用到 Bash 內建的控制運算子, && 和 ||。這兩種運算子提供了流程控制功能,使你能改變程式碼執行的順序。分號也可以被看做是一種 Bash 運算子,預示著新一行的開始。
&& 運算子提供瞭如下簡單邏輯,“如果 command1 執行成功,那麼接著執行 command2。如果 command1 失敗,就跳過 command2。”語法如下:
command1 && command2
現在,讓我們用命令來建立一個新的目錄,如果成功的話,就把它切換為當前目錄。確保你的家目錄(~)是當前目錄,先嚐試在 /root 目錄下建立,你應該沒有許可權:
[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir/ && cd $Dir mkdir: cannot create directory '/root/testdir/': Permission denied [student@studentvm1 ~]$
上面的報錯資訊是由 mkdir 命令丟擲的,因為建立目錄失敗了。&& 運算子收到了非零的返回碼,所以 cd 命令就被跳過,前者阻止後者繼續執行,因為建立目錄失敗了。這種控制流程可以阻止後面的錯誤累積,避免引發更嚴重的問題。是時候講點更復雜的邏輯了。
當一段程式的返回碼大於零時,使用 || 運算子可以讓你在後面接著執行另一段程式。簡單語法如下:
command1 || command2
解讀一下,“假如 command1 失敗,執行 command2”。隱藏的邏輯是,如果 command1 成功,跳過 command2。下面實踐一下,仍然是建立新目錄:
[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir || echo "$Dir was not created." mkdir: cannot create directory '/root/testdir': Permission denied /root/testdir was not created. [student@studentvm1 ~]$
正如預期,因為目錄無法建立,第一條命令失敗了,於是第二條命令被執行。
把 && 和 || 兩種運算子結合起來才能發揮它們的最大功效。請看下面例子中的流程控制方法:
前置 commands ; command1 && command2 || command3 ; 跟隨 commands
語法解釋:“假如 command1 退出時返回碼為零,就執行 command2,否則執行 command3。”用具體程式碼試試:
[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir && cd $Dir || echo "$Dir was not created." mkdir: cannot create directory '/root/testdir': Permission denied /root/testdir was not created. [student@studentvm1 ~]$
現在我們再試一次,用你的家目錄替換 /root 目錄,你將會有許可權建立這個目錄了:
[student@studentvm1 ~]$ Dir=~/testdir ; mkdir $Dir && cd $Dir || echo "$Dir was not created." [student@studentvm1 testdir]$
像 command1 && command2 這樣的控制語句能夠執行的原因是,每條命令執行完畢時都會給 shell 傳送一個返回碼,用來表示它執行成功與否。預設情況下,返回碼為 0 表示成功,其他任何正值表示失敗。一些系統管理員使用的工具用值為 1 的返回碼來表示失敗,但其他很多程式使用別的數字來表示失敗。
Bash 的內建變數 $? 可以顯示上一條命令的返回碼,可以在指令碼或者命令列中非常方便地檢查它。要檢視返回碼,讓我們從執行一條簡單的命令開始,返回碼的結果總是上一條命令給出的。
[student@studentvm1 testdir]$ ll ; echo "RC = $?" total 1264 drwxrwxr-x 2 student student 4096 Mar 2 08:21 chapter25 drwxrwxr-x 2 student student 4096 Mar 21 15:27 chapter26 -rwxr-xr-x 1 student student 92 Mar 20 15:53 TestFile1 drwxrwxr-x. 2 student student 663552 Feb 21 14:12 testdir drwxr-xr-x. 2 student student 4096 Dec 22 13:15 Videos RC = 0 [student@studentvm1 testdir]$
在這個例子中,返回碼為零,意味著命令執行成功了。現在對 root 的家目錄測試一下,你應該沒有許可權:
[student@studentvm1 testdir]$ ll /root ; echo "RC = $?" ls: cannot open directory '/root': Permission denied RC = 2 [student@studentvm1 testdir]$
本例中返回碼是 2,表明非 root 使用者沒有許可權進入這個目錄。你可以利用這些返回碼,用控制運算子來改變程式執行的順序。
本文將 Bash 看作一門程式語言,並從這個視角介紹了它的簡單語法和基礎工具。我們學習瞭如何將資料輸出到 STDOUT,怎樣使用變數和控制運算子。在本系列的下一篇文章中,將會重點介紹能夠控制指令執行流程的邏輯運算子。
原文地址: https://www.linuxprobe.com/bash-programming-foundation.html
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559985/viewspace-2667012/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 怎樣用 Bash 程式設計:語法和工具程式設計
- C#基礎程式設計——簡介及基礎語法C#程式設計
- Java-基礎語法19:網路程式設計Java程式設計
- Python程式設計入門基礎語法詳解Python程式設計
- Drools之基礎語法
- Dart語法篇之基礎語法(一)Dart
- Python程式設計入門——基礎語法詳解(經典)Python程式設計
- Golang 基礎之基礎語法梳理 (三)Golang
- JavaSE之java基礎語法Java
- 好程式設計師web前端培訓分享JavaScript基礎語法程式設計師Web前端JavaScript
- Linux_day06_01_Shell指令碼程式設計_Bash基礎Linux指令碼程式設計
- 淺談Kotlin語法篇之基礎語法(一)Kotlin
- 01 Python3程式設計之程式設計語法簡介Python程式設計
- Java學習之基礎語法Java
- 好程式設計師大資料學習路線分享大資料之基礎語法程式設計師大資料
- dart基礎之非同步程式設計Dart非同步程式設計
- Java入門之基礎程式設計Java程式設計
- 『Java 語法基礎』final、finalize 和 finally 的不同之處Java
- 軟體設計師:程式設計語言基礎知識程式設計
- Java基礎-語法基礎Java
- python 基礎語法之物件導向Python物件
- Flutter開發之Dart語法基礎FlutterDart
- ‘程式語言‘ ’程式設計工具’程式設計
- 基礎語法
- Linux Bash程式設計Linux程式設計
- bash基礎
- C語言入門基礎之陣列——數學和程式設計的完美結合(圖)C語言陣列程式設計
- 輸入和輸出基礎語法
- scala基礎語法-----Spark基礎Spark
- Python基礎:語法基礎(3)Python
- Linux之19——Shell程式設計基礎詳解Linux程式設計
- Java基礎語法之資料型別Java資料型別
- Java學習之基礎語法練習Java
- python程式設計基礎:深度學習基礎:繪相簿之matplotlib(1)Python程式設計深度學習
- Socket程式設計基礎程式設計
- Go程式設計基礎Go程式設計
- Shell程式設計-基礎程式設計
- python程式設計基礎Python程式設計