快速學習Bash

Vamei發表於2017-12-30

作者:Vamei 出處:http://www.cnblogs.com/vamei 嚴禁轉載。


Shell是Linux下經典的文字互動方式,而Bash是現在最常用的一種Shell。我在這裡總結了Bash的要點知識。

 

Shell綜述

Linux圖形化桌面算不上精美。幸好,Linux提供了更好的與樹莓派互動的方式:Shell。開啟終端(Terminal),桌面上就會出現一個黑色背景的視窗,裡面就執行著一個Shell。如果你敲擊鍵盤,會發現字元會顯示在$提示符的後面,形成一串文字形式的命令。所謂的Shell,就是執行在終端中的文字互動程式。Shell分析你的文字輸入,然後把文字轉換成相應的計算機動作。

 

在後面的內容中,我將用$來表示Linux系統Shell的命令提示符。比如說輸入date命令:

$date

date用於日期時間的相關功能。敲擊Enter鍵Enter後,Shell會顯示出系統當前的時間。

 

Shell看起來簡陋,但實際上比圖形化桌面強大得多。它是Unix體系下的文字互動介面。你只需要用鍵盤來輸入文字,就可以和作業系統互動。但這還是不夠具體。說到底,Shell其實是一個執行著的程式。這個程式接收到你按下Enter鍵之間的輸入,就會對輸入的文字進行分析。比如下面這個命令:

$free -h

包括空格在內總共7個字元。Shell程式會通過空格,區分出命令的不同部分。第一個部分是命令名。剩下的部分是選項和引數。在這個例子中,Shell會進一步分析第二個部分,發現這一部分的開頭是"-"字元,從而知道它是一個選項。

 

有了命令名,Shell下一步就要執行該命令名對應的動作。這聽起來就像是在戲劇舞臺上,演員按照指令碼演戲。Shell命令可以分為如下三類:

  • Shell內建函式(built-in function)
  • 可執行檔案(executable file)
  • 別名(alias)

Shell的內建函式是Shell自帶的功能,而可執行檔案是儲存在Shell之外的指令碼,提供了額外的功能。Shell必須在系統中找到對應命令名的可執行檔案,才能正確執行。我們可以用絕對路徑來告訴Shell可執行檔案所在的位置。如果使用者只是給出了命令名,而沒有給出準確的位置,那麼Shell必須自行搜尋一些特殊的位置,也就是所謂的預設路徑。Shell會執行第一個名字和命令名相同的可執行檔案。這就相當於,Shell幫我們自動補齊了可執行檔案的位置資訊。我們可以通過which命令,來確定命令名對應的是哪個可執行檔案:

$which date

 

別名是給某個命令一個簡稱,以後在Shell中就可以通過這個簡稱來呼叫對應的命令。在Shell中,我們可以用alias來定義別名:

$alias freak="free -h"

Shell會記住我們的別名定義。以後我在這個Shell中輸入命令freak時,都將等價於輸入free -h。

 

在Shell中,我們可以通過type命令來了解命令的型別。如果一個命令是可執行檔案,那麼type將列印出檔案的路徑。

$type date
$type pwd

總的來說,Shell就是根據空格和其他特殊符號,來讓電腦理解並執行使用者要求的動作。到了後面,我們還將看到Shell中其他的特殊符號。

 

Shell的選擇

Shell是文字直譯器程式的統稱,所以包括了不止一種Shell。常見的Shell有sh、bash、ksh、rsh、csh等。在樹莓派中,就安裝了sh和bash兩個Shell直譯器。sh的全名是Bourne Shell。名字中的玻恩就是這個Shell的作者。而bash的全名是Bourne Again Shell。最開始在Unix系統中流行的是sh,而bash作為sh的改進版本,提供了更加豐富的功能。一般來說,都推薦使用bash作為預設的Shell。樹莓派,以及其他Linux系統中廣泛安裝sh,都是出於相容歷史程式的目的。

 

我們可以通過下面的命令來檢視當前的Shell型別:

$echo $SHELL

echo用於在終端列印出文字。而$是一個新的Shell特殊符號。它提示Shell,後面跟隨的不是一般的文字,而是用於儲存資料的變數。Shell會根據變數名找到真正的文字,替換到變數所在的位置。SHELL變數儲存了當前使用的Shell的資訊你可以在bash中用sh命令啟動sh,並可以用exit命令從中退出。

 

命令的選項和引數

我們已經看到,一行命令裡還可以包含著選項和引數。總的來說,選項用於控制命令的行為,而引數說明了命令的作用物件。比如說:

$uname -m

 

在上面的命令中,選項-m影響了命令uname的行為,導致uname輸出了樹莓派的CPU型號。如果不是該選項的影響,uname輸出的將是"Linux"。我們不妨把每個命令看做多功能的瑞士軍刀,而選項讓命令在不同的功能間切換。由一個"-"引領一個英文字母,這成為短選項。多個短選項的字母可以合在一起,跟在同一個"-"後面。比如,下面的兩個命令就等價:

$uname -m -r
$uname -mr

 

此外還有一種長選項,是用"--"引領一整個英文單詞,比如:

$date --version

上面的命令將輸出date程式的版本資訊。

 

如果說選項控制了瑞士軍刀的行為,那麼引數就提供了瑞士軍刀發揮用場的原材料。就拿echo這個命令來說,它能把字元列印到終端。它選擇列印的物件,正是它的引數:

$echo hello

 

有的時候,選項也會攜帶變數,以便來說明選項行為的原材料。比如:

$sudo date --set="1999-01-01 08:00:00"

選項"--set"用於設定時間,用等號連線的,就是它的引數。date會把日期設定成這一變數所代表的日期。如果用短選項,那麼就要用空格取代等號了:

$sudo date -s "1999-01-01 08:00:00"

值得注意的是,Shell對空格敏感。當一整個引數資訊中包含了空格時,我們需要用引號把引數包裹起來,以便Shell能識別出這是一個整體。

 

所謂的選項和引數提供給命令的附加資訊。因此,命令最終會拿這些字串做什麼,是由命令自己決定的。因此,有時會發現一些特異的選項或引數用法。這個時候,你就要從文件中尋找答案。

 

變數

我們可以在Bash中輸入一行的命令。Bash會把輸入的命令轉化為特定的動作。從這一節起,我們將看到Bash的可程式設計性。Bash提供了某些類似於C語言那樣的程式設計語法,從而允許你用程式設計的方式,來組合使用Linux系統。我們首先看Bash用變數儲存資料的能力。正如我們在C語言中看到的,變數是記憶體中的一塊兒空間,可以用於儲存資料。我們可以通過變數名來引用變數中保持的資料。藉助變數,程式設計師可以複用出現過的資料。Bash中也有變數,但Bash的變數只能儲存文字。

 

1)變數賦值

Bash和C類似,同樣用“=”來表示賦值。比如:

$var=World

就是把文字World存入名為var的變數,即賦值。根據Bash的語法,賦值符號“=”的前後不留空格。賦值號右邊的文字內容會存入賦值號左邊的變數。

 

如果文字中包含空格,那麼你可以用單引號或雙引號來包裹文字。比如:

$var='abc bcd'

或者:

$var="abc bcd"

 

在Bash中,我們可以把一個命令輸出的文字直接賦予給一個變數:

$now=`date`

藉助``符號,date命令的輸出存入了變數now。

 

我們還可以把一個變數中的資料賦值給另一個變數:

$another=$var

  

2)引用變數

我們可以用$var的方式來引用變數。在Bash中,所謂的引用變數就是把變數翻譯成變數中儲存的文字。比如:

$var=World
$echo $var

 就會列印出World,即變數中儲存的文字。

 

在Bash中,你還可以在一段文字中嵌入變數。Bash也會把變數替換成變數中儲存的文字。比如: 

$echo Hello$var

 文字將列印出HelloWorld。

 

為了避免變數名和尾隨的普通文字混淆,我們也可以換用${}的方式來標識變數。比如說:

$echo $varIsGood

 由於Bash中並沒有varIsGood這個變數,所以Bash將列印空白行。但如果將命令改為: 

$echo ${var}IsGood

 Bash通過${}識別出變數var,並把它替換成資料。最終echo命令列印出WorldIsGood。

 

在Bash中,為了把一段包含空格的文字當做單一引數,就需要用到單引號或雙引號。你可以在雙引號中使用變數。比如:

$echo "Hello $var"

 將列印Hello World。與此相對,Bash會忽視單引號中的變數引用,所以單引號中的變數名只會被當做普通文字,比如:

$echo 'Hello $var'

 將列印Hello $var。

 

數學運算

在Bash中,數字和運算子都被當做普通文字。所以你無法像C語言一樣便捷地進行數學運算。比如執行下面的命令:

$result=1+2
$echo $result

Bash並不會進行任何運算。它只會列印文字“1+2”。

 

在Bash中,你還可以通過$(())語法來進行數值運算。在雙括號中你可以放入整數的加減乘除表示式。Bash會對其中的內容進行數值運算。比如

$echo $((2 + (5*2)))

 將列印運算結果12。此外,在$(())中,你也可以使用變數。比如: 

$var=1
$echo $(($var + (5*2)))

 將列印運算結果11。

 

你可以用Bash實現多種整數運算:

 加法:$(( 1 + 6 ))。結果為7。
 減法:$(( 5 – 3 ))。結果為2。
 乘法:$(( 2*2 ))。結果為4。
 除法:$(( 9/3 ))。結果為3。
 求餘:$(( 5%3 ))。結果為2。
 乘方:$(( 2**3 ))。結果為8。

 

現在,你就可以把數學運算結果存入變數:

$result=$(( 1 + 2 ))

 

 

返回程式碼

在Linux中,每個可執行程式會有一個整數的返回程式碼。按照Linux慣例,當程式正常執行完畢並返回時,將返回整數0。因此,C程式中返回0的語句,都出現在C程式中main函式的最後一句。例如下面的foo.c程式:

int main(void) {
  int a;
  int b;
  int c;

  a = 6;
  b = 2;
  c = 6/2;

  return 0;
}

 這段程式可以正常執行。因此,它將在最後一句執行return語句,程式的返回程式碼是0。在Shell中,我們執行了程式後,可以通過$?變數來獲知返回碼。比如:

$gcc foo.c
$./a.out
$echo $?

 

如果一個程式執行異常,那麼這個程式將返回非0的返回程式碼。比如刪除一個不存在的檔案: 

$rm none_exist.file
$echo $?

 

 在Linux中,可以在一個行命令中執行多個程式。比如:

$touch demo.file; ls;

 

在執行多個程式時,我們可以讓後一個程式的執行參考前一個程式的返回程式碼。比如說,只有前一個程式返回成功程式碼0,才讓後一個程式執行: 

$rm demo.file && echo "rm succeed"

如果rm命令順利執行,那麼第二個echo命令將執行。

 

還有一種情況,是等到前一個程式失敗了,才執行後一個程式,比如:

$rm demo.file || echo "rm fail"

如果rm命令失敗,第二個echo命令才會執行。

 

Bash指令碼

你還可以把多行的Bash命令寫入一個檔案,成為所謂的Bash指令碼。當Bash指令碼執行時,Shell將逐行執行指令碼中的命令。編寫Bash指令碼,是我們開始實現Bash程式碼複用的第一步。

 

1)指令碼的例子

用文字編輯器編寫一個Bash指令碼hello_world.bash: 

#!/bin/bash

echo Hello
echo World

 

指令碼的第一行說明了該指令碼使用的Shell,即/bin/bash路徑的Bash程式。指令碼正文是兩行echo命令。執行指令碼的方式和執行可執行程式的方式類似,都是: 

$./hello_world.bash

需要注意的是,如果使用者不具有執行Bash指令碼檔案的許可權,那麼他將無法執行Bash指令碼。此時,使用者必須更換檔案許可權,或者以其他身份登入,才能執行指令碼。當指令碼執行時,兩行命令將按照由上至下的順序依次執行。Shell將列印兩行文字:

Hello

World

 

Bash指令碼是一種複用程式碼的方式。我們可以用Bash指令碼實現特定的功能。由於該功能記錄在指令碼中,因此我可以反覆地執行同一個檔案來實現相同的功能,而不是每次想用的時候都要重新敲一遍命令。我們看一個簡單的Bash指令碼hw_info.bash,它將計算機的資訊存入到名為log的檔案中:

#!/bin/bash

echo "Information of Vamei's computer:" > log
lscpu >> log
uname –a >> log
free –h >> log

  

2)指令碼引數

和可執行程式類似,Bash指令碼執行時,也可以攜帶引數。這些引數可以在Bash指令碼中以變數的形式使用。比如test_arg.bash:

#!/bin/bash

echo $0
echo $1
echo $2

 

在Bash中,你可以用$0、$1、$2……的方式,來獲得Bash指令碼執行時的引數。我們用下面的方式執行Bash指令碼:

$./test_arg.bash hello world

$0是命令的第一部分,也就是./test_arg.bash。$1代表了引數hello,而$2代表了引數world。因此,上面程式將列印:

./test_arg.bash

hello

world

 

如果變更引數,同一段指令碼將有不同的行為。這大大提高了Bash指令碼的靈活性。上面的hw_info.bash指令碼中,我們把輸出檔名寫死成log。我們也可以修改指令碼,用引數作為輸出檔案的檔名:

#!/bin/bash

echo "Information of Vamei's computer:" > $1
lscpu >> $1
uname –a >> $1
free –h >> $1

 

藉助引數,我們就可以自由地設定輸出檔案的名字:

$./hw_info.bash output.file

 

3)指令碼的返回程式碼

和可執行程式類似,指令碼也可以有返回程式碼。還是按照慣例,指令碼正常退出時返回程式碼0。在指令碼的末尾,我們可以用exit命令來設定指令碼的返回程式碼。我們修改hello_world.bash:

#!/bin/bash

echo Hello
echo World
exit 0

其實在指令碼的末尾加一句exit 0並不必要。一個指令碼如果正常執行完最後一句,會自動的返回程式碼0。在指令碼執行後,我們可以通過$?變數查詢指令碼的返回程式碼: 

$./hello_world.bash
$echo $?

 

如果在指令碼中部出現exit命令,指令碼會直接在這一行停止,並返回該exit命令給出的返回程式碼。比如下面的demo_exit.bash:

#!/bin/bash

echo hello
exit 1
echo world

你可以執行該指令碼,檢查其輸出結果,並檢視其返回程式碼。

 

函式

在Bash中,指令碼和函式有很多相似的地方。指令碼實現了一整個指令碼檔案的程式複用,而函式複用了指令碼內部的部分程式。一個函式可以像指令碼一個包含多個指令,用於說明該函式如果被呼叫會執行哪些活動。在定義函式時,我們需要花括號來標識函式包括的部分: 

#!/bin/bash

function my_info (){
lscpu >> log
uname –a >> log
free –h >> log
}

my_info

  

指令碼一開始定義了函式my_info,my_info是函式名。關鍵字function和花括號都提示了該部分是函式定義。因此,function關鍵字並不是必須的。上面的指令碼等效於:

#!/bin/bash

my_info (){
lscpu >> log
uname –a >> log
free –h >> log
}

my_info

花括號中的三行命令,就說明了函式執行時需要執行的命令。需要強調的是,函式定義只是食譜,並沒有轉化成具體的動作。指令碼的最後一行是在呼叫函式。只有通過函式呼叫,函式內包含的命令才能真正執行。呼叫函式時,只需要一個函式名就可以了。

 

像指令碼一樣,函式呼叫時還可以攜帶引數。在函式內部,我們同樣可以用$1、$2這種形式的變數來使用引數:

#!/bin/bash

function my_info (){
lscpu >> $1
uname –a >> $1
free –h >> $1
}

my_info output.file
my_info another_output.file

在上面的指令碼中,進行了兩次函式呼叫。函式呼叫時,分別攜帶了引數output.file和another_output.file。

 

跨指令碼呼叫

在Bash中使用source命令,可以實現函式的跨指令碼呼叫。命令source的作用是在同一個程式中執行另一個檔案中的Bash指令碼。比如說,有兩個指令碼,my_info.bash和app.bash。指令碼my_info.sh中的內容是: 

#!/bin/bash

function my_info (){
lscpu >> $1
uname –a >> $1
free –h >> $1
}

 

指令碼app.bash中的內容是: 

#!/bin/bash

source my_info.bash
my_info output.file

執行app.bash時,執行到source命令那一行時,就會執行my_info.bash指令碼。在app.bash的後續部分,就可以使用my_info.bash中的my_info函式。

 

 

邏輯判斷

我們已經介紹了函式和指令碼兩種組合命令的方式。這兩種方式都可以把多行命令合併起來,組成一個功能單元。函式和指令碼都實現了一定程度的程式碼複用。從這一節起,我們將看到選擇和迴圈兩種語法結構,這兩種語法結構可以改變指令碼的執行順序,從而編寫出更加靈活的程式。Bash除了可以進行數值運算,還可以進行邏輯判斷。邏輯判斷是決定某個說法的真假。我們在生活中很自然地進行各種各樣的邏輯判斷。比如“3大於2”這個說法,我們會說它是真的。邏輯判斷就是對一個說法判斷真假。在Bash中,我們可以用test命令來進行邏輯判斷:

$test 3 -gt 2; echo $?

命令test後面跟有一個判斷表示式,其中的-gt表示大於,即greater than。由於“3大於2”這一表示式為真,所以命令的返回程式碼將是0。如果表示式為1,那麼命令的返回程式碼是1:

$test 3 -lt 2; echo $?

表示式中的-lt表示小於,即less than。

 

數值大小和相等關係的判斷,是最常見的邏輯判斷。除了上面的大於和小於判斷,我們還可以進行以下的數值判斷:

  • 等於: $test 3 -eq 3; echo $? 
  • 不等於: $test 3 -ne 1; echo $? 
  • 大於等於: $test 5 -ge 2; echo $? 
  • 小於等於: $test 3 -le 1; echo $? 

 

Bash中最常見的資料形式是文字,因此也提供了很多關於文字的判斷:

  • 文字相同: $test abc = abx; echo $? 
  • 文字不同: $test abc != abx; echo $? 
  • 按照詞典順序,一個文字在另一個文字之前: $test apple > tea; echo $? 
  • 按照詞典順序,一個文字在另一個文字之後: $test apple < tea; echo $? 

 

Bash還可以對檔案的狀態進行邏輯判斷:

  • 檢查一個檔案是否存在: $test –e a.out; echo $? 
  • 檢查一個檔案是否存在,而且是普通檔案: $test –f file.txt; echo $? 
  • 檢查一個檔案是否存在,而且是目錄檔案: $test –d myfiles; echo $? 
  • 檢查一個檔案是否存在,而且是軟連線: $test –L a.out; echo $? 
  • 檢查一個檔案是否可讀: $test –r file.txt; echo $? 
  • 檢查一個檔案是否可寫: $test –w file.txt; echo $? 
  • 檢查一個檔案是否可執行: $test –x file.txt; echo $? 

 

在做邏輯判斷時,可以把多個邏輯判斷條件用“與、或、非”的關係組合起來,形成複合的邏輯判斷。 

! expression
expression1 –a expression2
expression1 –o expression2

 

 

選擇結構

邏輯判斷可以獲得計算機和程式的狀態。進一步,Bash可以根據邏輯判斷,讓程式有條件地執行,這也就是所謂的選擇結構。選擇結構是一種語法結構,可以讓程式根據條件決定執行哪一部分的指令。最早的程式都是按照指令順序依次執行。選擇結構打破了這一順序,給程式帶來更高的靈活性。最簡單的,我們可以根據條件來決定是否執行某一部分程式,比如下面的demo_if.bash指令碼:

#!/bin/bash

var = `whoami`
if [ $var = "root" ]
then
  echo "You are root"
  echo "You are my God."
fi

這個指令碼中使用了最簡單的if結構。關鍵字if後面跟著[],裡面是一個邏輯表示式。這個邏輯表示式就是if結構的條件。如果條件成立,那麼if將執行then到fi之間包含的語句,我們稱之為隸屬於then的程式碼塊。如果條件不成立,那麼then的程式碼塊不執行。這個例子的條件是判斷使用者是否為root。因此,如果是非root使用者執行該指令碼,那麼Shell不會列印任何內容。

 

我們還可以通過if...then...else...結構,讓Bash指令碼從兩個程式碼塊中選擇一個執行。該選擇結構同樣有一個條件。如果條件成立,那麼將執行then附屬的程式碼塊,否則執行else附屬的程式碼塊。下面的demo_if_else.bash指令碼是一個小例子:

#!/bin/bash

filename=$1
if [ -e $filename ]
then
  echo "$filename exists"
else
  echo "$filename NOT exists"
fi

echo "The End"

 

if後面的“-e $filename”作為判斷條件。如果條件成立,即檔案存在,那麼執行then部分的程式碼塊。如果檔案不存在,那麼指令碼將執行else語句中的echo命令。末尾的fi結束整個語法結構。指令碼繼續以順序的方式執行剩餘內容。執行指令碼: 

$./demo_if_else.bash a.out

指令碼會根據a.out是否存在,列印出不同的內容。

 

我們看到,在使用if...then...else...結構時,我們可以實現兩部分程式碼塊的選擇執行。而在then程式碼塊和else程式碼塊內部,我們可以繼續巢狀選擇結構,從而實現更多個程式碼塊的選擇執行。比如指令碼demo_nest.bash:

#!/bin/bash

var=`whoami`
echo "You are $var"

if [ $var = "root" ]
then
  echo "You are my God."
else
  if [ $var = "vamei" ]
  then
    echo "You are a happy user."
  else
    echo "You are the Others."
  fi
fi

 

在Bash下,我們還可以用case語法來實現多程式塊的選擇執行。比如下面的指令碼demo_case.bash: 

#!/bin/bash

var=`whoami`
echo "You are $var"

case $var in
root)
echo "You are God."
;;
vamei)
echo "You are a happy user."
;;
*)
echo "You are the Others."
;;
esac

這個指令碼和上面的demo_nest.bash功能完全相同。可以看到case結構與if結構的區別。關鍵字case後面不再是邏輯表示式,而是一個作為條件的文字。後面的程式碼塊分為三個部分,都以文字標籤)的形式開始,以;;結束。在case結構執行時,會逐個檢查文字標籤。當條件文字和文字標籤可以對應上時,Bash就會執行隸屬於該文字標籤的程式碼塊。如果是使用者vamei執行該Bash指令碼,那麼條件文字和vamei標籤對應上,指令碼就會列印:

You are a happy user.

 

文字標籤除了是一串具體的文字,還可以包含文字萬用字元。結構case中常用的萬用字元包括:

 

萬用字元 含義 文字標籤例子 通過的條件文字
* 任意文字 *) Xyz, 123, …
? 任意一個字元 a?c) abc, axc, …
[] 範圍內一個字元 [1-5][b-d]) 2b, 3d, …

上面的程式中最後一個文字標籤是萬用字元*,即表示任意條件文字都可以觸發此段程式碼塊的執行。當然,前提是前面的幾個文字標籤都沒有“截胡”。

 

迴圈結構

迴圈結構是程式語言中另一種常見的語法結構。迴圈結構的功能是重複執行某一段程式碼,直到計算機的狀態符合某一條件。在while語法中,Bash會迴圈執行隸屬於while的程式碼塊,直到邏輯表示式不成立。比如下面的demo_while.bash:

#!/bin/bash

now=`date +'%Y%m%d%H%M'`
deadline=`date --date='1 hour' +'%Y%m%d%H%M'`

while [ $now -lt $deadline ]
do
  date
  echo "not yet"
  sleep 10
  now=`date +'%Y%m%d%H%M'`
done

echo "now, deadline reached"

關鍵字do和done之間的程式碼是隸屬於該迴圈結構的程式碼塊。在while後面跟著條件,該條件決定了程式碼塊是否重複執行下去。這個條件是用當前的時間與目標時間對比。如果當前時間小於目標時間,那麼程式碼塊就會重複執行下去。否則,Bash將跳出迴圈,繼續執行後面的語句。

 

如果while的條件始終是真,那麼迴圈會一直進行下去。下面的程式就是以無限迴圈的形式,不斷播報時間: 

#!/bin/bash

while true 
do
  date
  sleep 1
done

 

語法while的終止條件是一個邏輯判斷。如果在迴圈過程中改變邏輯判斷的內容,那麼我們很難在程式執行之前預判迴圈進行的次數。正如我們之前在demo_while.bash中看到的,我們在迴圈進行過程中改變著作為條件的邏輯表示式,不斷地更新參與邏輯判斷的當前時間。與while語法對應的是for迴圈。這種語法會在程式進行前確定好迴圈進行的次數,比如demo_for.bash: 

#!/bin/bash

for var in `ls log*`
do
  rm $var
done

 

在這個例子中,命令ls log*將返回所有以log開頭的檔名。這些檔名之間由空格分隔。迴圈進行時,Bash會依次取出一個檔名,賦值給變數var,並執行do和done之間隸屬於for結構的程式塊。由於ls命令返回的內容在是確定的,因此for迴圈進行的次數也會在一開始確定下來。

 

在for語法中,我們也可以使用自己構建一個由空格分隔的文字。由空格區分出來的每個子文字會在迴圈中賦值給變數。比如:

#!/bin/bash

for user in vamei anna yutian
do
  echo $user
done

 

此外,for迴圈還可以和seq命令配合使用。命令seq用於生成一個等差的整數序列。命令後面可以跟3個引數,第一個參數列示整數序列的開始數字,第二個參數列示每次增加多少,最後一個參數列示序列的終點。因此,下面命令: 

$seq 1 2 10

將返回:

1 3 5 7 9

可以看到,seq返回的也是由空格分隔開的文字。因此,seq的返回結果也可用於for迴圈。

 

結合for迴圈和seq命令,我們可以解一些有趣的數學問題。比如高斯求和,是要計算從1到100的所有整數的和。我們可以用Bash解決: 

#!/bin/bash

total=0

for number in `seq 1 1 100`
do
  total=$(( $total + $number ))
done

echo $total

 

 這個問題還可以用do while迴圈來求解:

#!/bin/bash

total=0
number=1
while :
do
  if [ $number -gt 100 ]
  then
    break
  fi

  total=$(( $total + $number ))
  number=$(($number + 1))
done

echo $total

這裡break語句的作用是在滿足條件時跳出迴圈。

 

如果想計算1到100所有不被3整數的和,則可以使用continue語句,跳過所有被3整數的數:

#!/bin/bash
total=0
for number in `seq 1 1 100`
do
  if (( $number % 3 == 0 )) 
  then
    continue
  fi
  total=$(( $total + $number ))
done

echo $total

 

Bash與C語言

到了這裡,我們已經介紹完Bash語言的基本語法。Bash語言和C語言都是Linux下的常用語言。它們都能通過特定的語法來編寫程式,而程式執行後都能實現某些功能。儘管在語法細節上存在差異,但兩種語言都有以下語法:

  • 變數:在記憶體中儲存資料
  • 迴圈結構:重複執行程式碼塊
  • 選擇結構:根據條件執行程式碼塊
  • 函式:複用程式碼塊

程式語言的作者在設計語言時,往往會借鑑已有程式語言的優點。這是程式語言之間相似性的一大原因。程式設計師往往要掌握不止一套程式語言。相似的語法特徵,會讓程式設計師在學習新語言時感到親切,從而促進語言的推廣。

Bash和C的相似性,也來自於它們共同遵守的程式設計正規化——程式導向程式設計。支援程式導向程式設計的語言,一般都會提供類似於函式的程式碼封裝方式。函式把多行指令包裝成一個功能。只要知道了函式名,程式可以通過呼叫函式來使用函式功能,最終實現程式碼複用。除了程式導向程式設計,還有物件導向和函式式的程式設計正規化。每種程式設計正規化都提供了特定的程式碼封裝方式,並達到程式碼複用的目的。值得注意的是,近年來出現的新語言往往會支援不止一種程式設計正規化。

除了相似性,我們還應該注意到Bash和C程式的區別。Bash的變數只能是文字型別,C的變數卻可以有整數、浮點數、字元等型別。Bash的很多功能,如加減乘除運算,都是呼叫其他程式實現的。而C直接就可以進行加減乘除運算。可以說,C語言是一門真正的程式語言。C程式最終會編譯成二進位制的可執行檔案。CPU可以直接理解這些檔案中的指令。

另一方面,Bash是一個Shell。它本質上是一個命令直譯器程式,而不是程式語言。使用者可以通過命令列的方式,來呼叫該程式的某些功能。所謂的Bash程式設計,只是命令直譯器程式提供的一種互動方法。Bash指令碼只能和Bash程式互動。它不能像C語言一樣,直接呼叫CPU的功能。因此,Bash能實現的功能會受限,執行速度上也比不上可執行檔案。

但另一反面,Bash指令碼也有它的好處。 C語言能接觸到很底層的東西,但使用起來也很複雜。有時候,即使你已經知道如何用C實現一個功能,寫程式碼依然是一個很繁瑣的過程。Bash正相反。由於Bash可以便捷地呼叫已有的程式,因此很多工作可以用數行的指令碼解決。此外,Bash指令碼不需要編輯,就可以由Bash程式理解並執行。因此,開發Bash指令碼比寫C程式要快很多。Linux的系統運維工作,如定期備份、檔案系統管理等,就經常使用到Bash指令碼。總之,Bash程式設計知識是晉級為資深Linux使用者的必要條件。

 

歡迎閱讀“騎著企鵝採樹莓”系列文章

 

相關文章