天天看點

Shell 快速指南Shell 快速指南

Shell 快速指南

███████╗██╗  ██╗███████╗██╗     ██╗                           
██╔════╝██║  ██║██╔════╝██║     ██║                           
███████╗███████║█████╗  ██║     ██║                           
╚════██║██╔══██║██╔══╝  ██║     ██║                           
███████║██║  ██║███████╗███████╗███████╗           

概述

什麼是 shell

Shell 是一個用 C 語言編寫的程式,它是使用者使用 Linux 的橋梁。

Shell 既是一種指令語言,又是一種程式設計語言。

Shell 是指一種應用程式,這個應用程式提供了一個界面,使用者通過這個界面通路 Linux 核心的服務。

Ken Thompson 的 sh 是第一種 Unix Shell,Windows Explorer 是一個典型的圖形界面 Shell。

什麼是 shell 腳本

Shell 腳本(shell script),是一種為 shell 編寫的腳本程式,一般檔案字尾為

.sh

業界所說的 shell 通常都是指 shell 腳本,但 shell 和 shell script 是兩個不同的概念。

Shell 環境

Shell 程式設計跟 java、php 程式設計一樣,隻要有一個能編寫代碼的文本編輯器和一個能解釋執行的腳本解釋器就可以了。

Shell 的解釋器種類衆多,常見的有:

  • sh - 即 Bourne Shell。sh 是 Unix 标準預設的 shell。
  • bash - 即 Bourne Again Shell。bash 是 Linux 标準預設的 shell。
  • fish - 智能和使用者友好的指令行 shell。
  • xiki - 使 shell 控制台更友好,更強大。
  • zsh - 功能強大的 shell 與腳本語言。

指定腳本解釋器

在 shell 腳本,

#!

告訴系統其後路徑所指定的程式即是解釋此腳本檔案的 Shell 解釋器。

#!

被稱作

shebang(也稱為 Hashbang )

是以,你應該會在 shell 中,見到諸如以下的注釋:

  • 指定 sh 解釋器
#!/bin/sh           
  • 指定 bash 解釋器
#!/bin/bash           

注意

上面的指定解釋器的方式是比較常見的,但有時候,你可能也會看到下面的方式:

#!/usr/bin/env bash           
這樣做的好處是,系統會自動在

PATH

環境變量中查找你指定的程式(本例中的

bash

)。相比第一種寫法,你應該盡量用這種寫法,因為程式的路徑是不确定的。這樣寫還有一個好處,作業系統的

PATH

變量有可能被配置為指向程式的另一個版本。比如,安裝完新版本的

bash

,我們可能将其路徑添加到

PATH

中,來“隐藏”老版本。如果直接用

#!/bin/bash

,那麼系統會選擇老版本的

bash

來執行腳本,如果用

#!/usr/bin/env bash

,則會使用新版本。

模式

shell 有互動和非互動兩種模式。

互動模式

簡單來說,你可以将 shell 的互動模式了解為執行指令行。

看到形如下面的東西,說明shell處于互動模式下:

user@host:~$           

接着,便可以輸入一系列 Linux 指令,比如

ls

grep

cd

mkdir

rm

等等。

非互動模式

簡單來說,你可以将 shell 的非互動模式了解為執行 shell 腳本。

在非互動模式下,shell 從檔案或者管道中讀取指令并執行。

當 shell 解釋器執行完檔案中的最後一個指令,shell 程序終止,并回到父程序。

可以使用下面的指令讓shell以非互動模式運作:

sh /path/to/script.sh
bash /path/to/script.sh           

上面的例子中,

script.sh

是一個包含shell解釋器可以識别并執行的指令的普通文本檔案,

sh

bash

是shell解釋器程式。你可以使用任何喜歡的編輯器建立

script.sh

(vim,nano,Sublime Text, Atom等等)。

除此之外,你還可以通過

chmod

指令給檔案添加可執行的權限,來直接執行腳本檔案:

chmod +x /path/to/script.sh #使腳本具有執行權限
/path/to/test.sh           

這種方式要求腳本檔案的第一行必須指明運作該腳本的程式,比如:

#!/bin/bash
echo "Hello, world!"           

上面的例子中,我們使用了一個很有用的指令

echo

來輸出字元串到螢幕上。

Shell 程式設計

由于 bash 是 Linux 标準預設的 shell,可以說 bash 是 shell 程式設計的基礎。

是以,下面将全部基于 bash 來講解 shell 程式設計。

此外,本篇章主要介紹的是 shell 程式設計的文法,對于 linux 指令不做任何介紹。

解釋器

前面雖然兩次提到了

#!

,但是本着重要的事情說三遍的精神,這裡再強調一遍:

#!

#!

#!

決定了腳本可以像一個獨立的可執行檔案一樣執行,而不用在終端之前輸入

sh

,

bash

python

php

等。

示例:

# 以下兩種方式都可以指定 shell 解釋器為 bash,第二種方式更好
#!/bin/bash
#!/usr/bin/env bash           

注釋

shell 文法支援注釋。注釋是特殊的語句,會被 shell 解釋器忽略。它們以

#

開頭,到行尾結束。

#!/bin/bash
### This script will print your username.
whoami           
Tip: 用注釋來說明你的腳本是幹什麼的,以及為什麼這樣寫。

變量

跟許多程式設計語言一樣,你可以在 bash 中建立變量。

Bash 中沒有資料類型,bash 中的變量可以儲存一個數字、一個字元、一個字元串等等。同時無需提前聲明變量,給變量指派會直接建立變量。

你可以建立三種變量:局部變量,環境變量以及作為位置參數的變量。

局部變量

局部變量是僅在某個腳本内部有效的變量。它們不能被其他的程式和腳本通路。

局部變量可以用

=

聲明(作為一種約定,變量名、

=

、變量的值之間不應該有空格),其值可以用

$

通路到。
username="zhangpeng"  ### 聲明變量
echo $username          ### 輸出變量的值
unset username          ### 删除變量           
可以用

local

關鍵字聲明屬于某個函數的局部變量。這樣聲明的變量會在函數結束時消失。
local local_var="I'm a local value"           

環境變量

環境變量是對目前 shell 會話内所有的程式或腳本都可見的變量。

建立它們跟建立局部變量類似,但使用的是

export

關鍵字。
export global_var="I'm a global value"           

常見的環境變量:

描述

$HOME

目前使用者的使用者目錄

$PATH

用分号分隔的目錄清單,shell會到這些目錄中查找指令

$PWD

目前工作目錄

$RANDOM

0到32767之間的整數

$UID

數值類型,目前使用者的使用者ID

$PS1

主要系統輸入提示符

$PS2

次要系統輸入提示符
這裡

有一張更全面的 Bash 環境變量清單。

位置參數

位置參數是在調用一個函數并傳給它參數時建立的變量。

位置參數變量表:

$0

腳本名稱

$1 … $9

第1個到第9個參數清單

${10} … ${N}

第10個到N個參數清單

$*

or

$@

除了

$0

外的所有位置參數

$#

不包括

$0

在内的位置參數的個數

$FUNCNAME

函數名稱(僅在函數内部有值)

在下面的例子中,位置參數為:

$0='./script.sh'

$1='foo'

$2='bar'

$ ./script.sh foo bar           

變量可以有預設值。我們可以用如下文法來指定預設值:

### 如果變量為空,賦給他們預設值
: ${VAR:='default'}
: ${1:='first'}
echo "\$1 : " $1
: ${2:='second'}
echo "\$2 : " $2

### 或者
FOO=${FOO:-'default'}           

Shell擴充

擴充 發生在一行指令被分成一個個的 記号(tokens) 之後。換言之,擴充是一種執行數學運算的機制,還可以用來儲存指令的執行結果,等等。

感興趣的話可以閱讀

關于shell擴充的更多細節

大括号擴充

大括号擴充讓生成任意的字元串成為可能。它跟 檔案名擴充 很類似,舉個例子:

echo beg{i,a,u}n ### begin began begun           

大括号擴充還可以用來建立一個可被循環疊代的區間。

echo {0..5} ### 0 1 2 3 4 5
echo {00..8..2} ### 00 02 04 06 08           

指令置換

指令置換允許我們對一個指令求值,并将其值置換到另一個指令或者變量指派表達式中。當一個指令被```

$()`包圍時,指令置換将會執行。舉個例子:

now=`date +%T`
### or
now=$(date +%T)

echo $now ### 19:08:26           

算數擴充

在bash中,執行算數運算是非常友善的。算數表達式必須包在

$(( ))

中。算數擴充的格式為:

result=$(( ((10 + 5*3) - 7) / 2 ))
echo $result ### 9           

在算數表達式中,使用變量無需帶上

$

字首:

x=4
y=7
echo $(( x + y ))     ### 11
echo $(( ++x + y++ )) ### 12
echo $(( x + y ))     ### 13           

單引号和雙引号

單引号和雙引号之間有很重要的差別。在雙引号中,變量引用或者指令置換是會被展開的。在單引号中是不會的。舉個例子:

echo "Your home: $HOME" ### Your home: /Users/<username>
echo 'Your home: $HOME' ### Your home: $HOME           

當局部變量和環境變量包含空格時,它們在引号中的擴充要格外注意。随便舉個例子,假如我們用

echo

來輸出使用者的輸入:

INPUT="A string  with   strange    whitespace."
echo $INPUT   ### A string with strange whitespace.
echo "$INPUT" ### A string  with   strange    whitespace.           

調用第一個

echo

時給了它5個單獨的參數 ——

$INPUT

被分成了單獨的詞,

echo

在每個詞之間列印了一個空格。第二種情況,調用

echo

時隻給了它一個參數(整個$INPUT的值,包括其中的空格)。

來看一個更嚴肅的例子:

FILE="Favorite Things.txt"
cat $FILE   ### 嘗試輸出兩個檔案: `Favorite` 和 `Things.txt`
cat "$FILE" ### 輸出一個檔案: `Favorite Things.txt`           

盡管這個問題可以通過把FILE重命名成

Favorite-Things.txt

來解決,但是,假如這個值來自某個環境變量,來自一個位置參數,或者來自其它指令(

find

cat

, 等等)呢。是以,如果輸入 可能 包含空格,務必要用引号把表達式包起來。

數組

跟其它程式設計語言一樣,bash中的數組變量給了你引用多個值的能力。在bash中,數組下标也是從0開始,也就是說,第一個元素的下标是0。

跟數組打交道時,要注意一個特殊的環境變量

IFS

。IFS,全稱 Input Field Separator,儲存了數組中元素的分隔符。它的預設值是一個空格

IFS=' '

建立數組

在 bash 中有好幾種方法建立一個數組

array[0] = val
array[1] = val
array[2] = val
array=([2]=val [0]=val [1]=val)
array=(val val val)           

擷取數組元素

  • 擷取數組的單個元素:
echo ${array[1]}           
  • 擷取數組的所有元素:
echo ${array[*]}
echo ${array[@]}           

上面兩行有很重要(也很微妙)的差別,假設某數組元素中包含空格:

colors[0]=Red
colors[1]="Dark Green"
colors[2]=Blue           

為了将數組中每個元素單獨一行輸出,我們用内建的

printf

指令:

printf "+ %s\n" ${colors[*]}

# 輸出:
# + Red
# + Dark
# + Green
# + Blue           

為什麼

Desert

fig

各占了一行?嘗試用引号包起來:

printf "+ %s\n" "${colors[*]}"

# 輸出:
# + Red Dark Green Blue           

現在所有的元素都跑去了一行 —— 這不是我們想要的!為了解決這個痛點,

${colors[@]}

閃亮登場:

printf "+ %s\n" "${colors[@]}"

# 輸出:
+ Red
+ Dark Green
+ Blue           

在引号内,

${colors[@]}

将數組中的每個元素擴充為一個單獨的參數;數組元素中的空格得以保留。

  • 通路數組的部分元素:
echo ${array[@]:0:2}           

在上面的例子中,

${array[@]}

擴充為整個數組,

:0:2

取出了數組中從0開始,長度為2的元素。

擷取數組長度

echo ${#array[*]}           

向數組中添加元素

向數組中添加元素也非常簡單:

colors=(Yellow "${colors[@]}" Pink Black)
echo ${colors[@]}

# 輸出:
# Yellow Red Dark Green Blue Pink Black           

${colors[@]}

擴充為整個數組,并被置換到複合指派語句中,接着,對數組

colors

的指派覆寫了它原來的值。

從數組中删除元素

unset

指令來從數組中删除一個元素:

unset colors[0]
echo ${colors[@]}

# 輸出:
# Red Dark Green Blue Pink Black           

運算符

算術運算符

下表列出了常用的算術運算符,假定變量 a 為 10,變量 b 為 20:

說明 舉例
+ 加法

expr $a + $b

結果為 30。
- 減法

expr $a - $b

結果為 -10。
* 乘法

expr $a \* $b

結果為 200。
/ 除法

expr $b / $a

結果為 2。
% 取餘

expr $b % $a

結果為 0。
= 指派

a=$b

将把變量 b 的值賦給 a。
== 相等。用于比較兩個數字,相同則傳回 true。

[ $a == $b ]

傳回 false。
!= 不相等。用于比較兩個數字,不相同則傳回 true。

[ $a != $b ]

傳回 true。

注意:條件表達式要放在方括号之間,并且要有空格,例如: [\(a==\)b] 是錯誤的,必須寫成 [ $a == $b ]。

a=10
b=20

echo "a=$a, b=$b"

val=`expr $a + $b`
echo "a + b : $val"

val=`expr $a - $b`
echo "a - b : $val"

val=`expr $a \* $b`
echo "a * b : $val"

val=`expr $b / $a`
echo "b / a : $val"

val=`expr $b % $a`
echo "b % a : $val"

if [ $a == $b ]
then
  echo "a 等于 b"
fi
if [ $a != $b ]
then
  echo "a 不等于 b"
fi           

關系運算符

關系運算符隻支援數字,不支援字元串,除非字元串的值是數字。

下表列出了常用的關系運算符,假定變量 a 為 10,變量 b 為 20:

-eq 檢測兩個數是否相等,相等傳回 true。

[ $a -eq $b ]

-ne 檢測兩個數是否相等,不相等傳回 true。

[ $a -ne $b ]

-gt 檢測左邊的數是否大于右邊的,如果是,則傳回 true。

[ $a -gt $b ]

-lt 檢測左邊的數是否小于右邊的,如果是,則傳回 true。

[ $a -lt $b ]

-ge 檢測左邊的數是否大于等于右邊的,如果是,則傳回 true。

[ $a -ge $b ]

-le 檢測左邊的數是否小于等于右邊的,如果是,則傳回 true。

[ $a -le $b ]

a=10
b=20

if [ $a -eq $b ]
then
   echo "$a -eq $b : a 等于 b"
else
   echo "$a -eq $b: a 不等于 b"
fi
if [ $a -ne $b ]
then
   echo "$a -ne $b: a 不等于 b"
else
   echo "$a -ne $b : a 等于 b"
fi
if [ $a -gt $b ]
then
   echo "$a -gt $b: a 大于 b"
else
   echo "$a -gt $b: a 不大于 b"
fi
if [ $a -lt $b ]
then
   echo "$a -lt $b: a 小于 b"
else
   echo "$a -lt $b: a 不小于 b"
fi
if [ $a -ge $b ]
then
   echo "$a -ge $b: a 大于或等于 b"
else
   echo "$a -ge $b: a 小于 b"
fi
if [ $a -le $b ]
then
   echo "$a -le $b: a 小于或等于 b"
else
   echo "$a -le $b: a 大于 b"
fi           

布爾運算符

下表列出了常用的布爾運算符,假定變量 a 為 10,變量 b 為 20:

! 非運算,表達式為 true 則傳回 false,否則傳回 true。

[ ! false ]

-o 或運算,有一個表達式為 true 則傳回 true。

[ $a -lt 20 -o $b -gt 100 ]

-a 與運算,兩個表達式都為 true 才傳回 true。

[ $a -lt 20 -a $b -gt 100 ]

a=10
b=20

echo "a=$a, b=$b"

if [ $a != $b ]
then
   echo "$a != $b : a 不等于 b"
else
   echo "$a != $b: a 等于 b"
fi
if [ $a -lt 100 -a $b -gt 15 ]
then
   echo "$a 小于 100 且 $b 大于 15 : 傳回 true"
else
   echo "$a 小于 100 且 $b 大于 15 : 傳回 false"
fi
if [ $a -lt 100 -o $b -gt 100 ]
then
   echo "$a 小于 100 或 $b 大于 100 : 傳回 true"
else
   echo "$a 小于 100 或 $b 大于 100 : 傳回 false"
fi
if [ $a -lt 5 -o $b -gt 100 ]
then
   echo "$a 小于 5 或 $b 大于 100 : 傳回 true"
else
   echo "$a 小于 5 或 $b 大于 100 : 傳回 false"
fi           

邏輯運算符

以下介紹 Shell 的邏輯運算符,假定變量 a 為 10,變量 b 為 20:

&& 邏輯的 AND

[[ $a -lt 100 && $b -gt 100 ]]

傳回 false
|| 邏輯的 OR

[[ $a -lt 100 || $b -gt 100 ]]

傳回 true
a=10
b=20

echo "a=$a, b=$b"

if [[ $a -lt 100 && $b -gt 100 ]]
then
   echo "傳回 true"
else
   echo "傳回 false"
fi

if [[ $a -lt 100 || $b -gt 100 ]]
then
   echo "傳回 true"
else
   echo "傳回 false"
fi           

字元串運算符

下表列出了常用的字元串運算符,假定變量 a 為 "abc",變量 b 為 "efg":

檢測兩個字元串是否相等,相等傳回 true。

[ $a = $b ]

檢測兩個字元串是否相等,不相等傳回 true。

[ $a != $b ]

-z 檢測字元串長度是否為0,為0傳回 true。

[ -z $a ]

-n 檢測字元串長度是否為0,不為0傳回 true。

[ -n $a ]

str 檢測字元串是否為空,不為空傳回 true。

[ $a ]

a="abc"
b="efg"

echo "a=$a, b=$b"

if [ $a = $b ]
then
   echo "$a = $b : a 等于 b"
else
   echo "$a = $b: a 不等于 b"
fi
if [ $a != $b ]
then
   echo "$a != $b : a 不等于 b"
else
   echo "$a != $b: a 等于 b"
fi
if [ -z $a ]
then
   echo "-z $a : 字元串長度為 0"
else
   echo "-z $a : 字元串長度不為 0"
fi
if [ -n $a ]
then
   echo "-n $a : 字元串長度不為 0"
else
   echo "-n $a : 字元串長度為 0"
fi
if [ $a ]
then
   echo "$a : 字元串不為空"
else
   echo "$a : 字元串為空"
fi           

檔案測試運算符

檔案測試運算符用于檢測 Unix 檔案的各種屬性。

屬性檢測描述如下:

操作符
-b file 檢測檔案是否是塊裝置檔案,如果是,則傳回 true。

[ -b $file ]

-c file 檢測檔案是否是字元裝置檔案,如果是,則傳回 true。

[ -c $file ]

-d file 檢測檔案是否是目錄,如果是,則傳回 true。

[ -d $file ]

-f file 檢測檔案是否是普通檔案(既不是目錄,也不是裝置檔案),如果是,則傳回 true。

[ -f $file ]

-g file 檢測檔案是否設定了 SGID 位,如果是,則傳回 true。

[ -g $file ]

-k file 檢測檔案是否設定了粘着位(Sticky Bit),如果是,則傳回 true。

[ -k $file ]

-p file 檢測檔案是否是有名管道,如果是,則傳回 true。

[ -p $file ]

-u file 檢測檔案是否設定了 SUID 位,如果是,則傳回 true。

[ -u $file ]

-r file 檢測檔案是否可讀,如果是,則傳回 true。

[ -r $file ]

-w file 檢測檔案是否可寫,如果是,則傳回 true。

[ -w $file ]

-x file 檢測檔案是否可執行,如果是,則傳回 true。

[ -x $file ]

-s file 檢測檔案是否為空(檔案大小是否大于0),不為空傳回 true。

[ -s $file ]

-e file 檢測檔案(包括目錄)是否存在,如果是,則傳回 true。

[ -e $file ]

變量 file 表示檔案"/var/www/runoob/test.sh",它的大小為100位元組,具有 rwx 權限。下面的代碼,将檢測該檔案的各種屬性:

file="./operatorDemo.sh"
if [ -r $file ]
then
   echo "檔案可讀"
else
   echo "檔案不可讀"
fi
if [ -w $file ]
then
   echo "檔案可寫"
else
   echo "檔案不可寫"
fi
if [ -x $file ]
then
   echo "檔案可執行"
else
   echo "檔案不可執行"
fi
if [ -f $file ]
then
   echo "檔案為普通檔案"
else
   echo "檔案為特殊檔案"
fi
if [ -d $file ]
then
   echo "檔案是個目錄"
else
   echo "檔案不是個目錄"
fi
if [ -s $file ]
then
   echo "檔案不為空"
else
   echo "檔案為空"
fi
if [ -e $file ]
then
   echo "檔案存在"
else
   echo "檔案不存在"
fi           

語句

條件語句

跟其它程式設計語言一樣,Bash中的條件語句讓我們可以決定一個操作是否被執行。結果取決于一個包在

[[ ]]

裡的表達式。

條件表達式可以包含

&&

||

運算符,分别對應 與 和 或 。除此之外還有很多有用的

表達式

共有兩個不同的條件表達式:

if

case

基元群組合表達式

[[ ]]

sh

中是

[ ]

)包起來的表達式被稱作 檢測指令 或 基元。這些表達式幫助我們檢測一個條件的結果。在下面的表裡,為了相容

sh

,我們用的是

[ ]

。這裡可以找到有關

bash中單雙中括号差別

的答案。

使用

if

if

在使用上跟其它語言相同。如果中括号裡的表達式為真,那麼

then

fi

之間的代碼會被執行。

fi

标志着條件代碼塊的結束。

### 寫成一行
if [[ 1 -eq 1 ]]; then echo "true"; fi

### 寫成多行
if [[ 1 -eq 1 ]]; then
  echo "true"
fi           

同樣,我們可以使用

if..else

語句,例如:

### 寫成一行
if [[ 2 -ne 1 ]]; then echo "true"; else echo "false"; fi

### 寫成多行
if [[ 2 -ne 1 ]]; then
  echo "true"
else
  echo "false"
fi           

有些時候,

if..else

不能滿足我們的要求。别忘了

if..elif..else

,使用起來也很友善。

if [[ `uname` == "Adam" ]]; then
  echo "Do not eat an apple!"
elif [[ `uname` == "Eva" ]]; then
  echo "Do not take an apple!"
else
  echo "Apples are delicious!"
fi           

case

如果你需要面對很多情況,分别要采取不同的措施,那麼使用

case

會比嵌套的

if

更有用。使用

case

來解決複雜的條件判斷,看起來像下面這樣:

echo "input param: " $1

case $1 in
  "jpg" | "jpeg")
    echo "It's image with jpeg extension."
  ;;
  "png")
    echo "It's image with png extension."
  ;;
  "gif")
    echo "Oh, it's a giphy!"
  ;;
  *)
    echo "Woops! It's not image!"
  ;;
esac           

每種情況都是比對了某個模式的表達式。

|

用來分割多個模式,

)

用來結束一個模式序列。第一個比對上的模式對應的指令将會被執行。

*

代表任何不比對以上給定模式的模式。指令塊兒之間要用

;;

分隔。

循環語句

循環其實不足為奇。跟其它程式設計語言一樣,bash中的循環也是隻要控制條件為真就一直疊代執行的代碼塊。

Bash中有四種循環:

for

while

until

select

for

循環

for

與它在C語言中的姊妹非常像。看起來是這樣:

for arg in elem1 elem2 ... elemN
do
  ### 語句
done           

在每次循環的過程中,

arg

依次被指派為從

elem1

elemN

。這些值還可以是通配符或者

當然,我們還可以把

for

循環寫在一行,但這要求

do

之前要有一個分号,就像下面這樣:

for i in {1..5}; do echo $i; done           

還有,如果你覺得

for..in..do

對你來說有點奇怪,那麼你也可以像C語言那樣使用

for

,比如:

for (( i = 0; i < 10; i++ )); do
  echo $i
done           

當我們想對一個目錄下的所有檔案做同樣的操作時,

for

就很友善了。舉個例子,如果我們想把所有的

.bash

檔案移動到

script

檔案夾中,并給它們可執行權限,我們的腳本可以這樣寫:

#!/bin/bash

for FILE in $HOME/*.bash; do
  mv "$FILE" "${HOME}/scripts"
  chmod +x "${HOME}/scripts/${FILE}"
done           

while

while

循環檢測一個條件,隻要這個條件為 真,就執行一段指令。被檢測的條件跟

if..then

中使用的

基元

并無二異。是以一個

while

循環看起來會是這樣:

while [[ condition ]]
do
  ### 語句
done           

for

循環一樣,如果我們把

do

和被檢測的條件寫到一行,那麼必須要在

do

之前加一個分号。

比如下面這個例子:

#!/bin/bash

### 0到9之間每個數的平方
x=0
while [[ $x -lt 10 ]]; do ### x小于10
  echo $(( x * x ))
  x=$(( x + 1 )) ### x加1
done           

until

until

循環跟

while

循環正好相反。它跟

while

一樣也需要檢測一個測試條件,但不同的是,隻要該條件為 假 就一直執行循環:

until [[ condition ]]; do
  ### 語句
done           

select

select

循環幫助我們組織一個使用者菜單。它的文法幾乎跟

for

循環一緻:

select answer in elem1 elem2 ... elemN
do
  ### 語句
done           

select

會列印

elem1..elemN

以及它們的序列号到螢幕上,之後會提示使用者輸入。通常看到的是

$?

PS3

變量)。使用者的選擇結果會被儲存到

answer

中。如果

answer

是一個在

1..N

之間的數字,那麼

語句

會被執行,緊接着會進行下一次疊代 —— 如果不想這樣的話我們可以使用

break

語句。

一個可能的執行個體可能會是這樣:

#!/bin/bash

PS3="Choose the package manager: "
select ITEM in bower npm gem pip
do
  echo -n "Enter the package name: " && read PACKAGE
  case $ITEM in
    bower) bower install $PACKAGE ;;
    npm)   npm   install $PACKAGE ;;
    gem)   gem   install $PACKAGE ;;
    pip)   pip   install $PACKAGE ;;
  esac
  break ### 避免無限循環
done           

這個例子,先詢問使用者他想使用什麼包管理器。接着,又詢問了想安裝什麼包,最後執行安裝操作。

運作這個腳本,會得到如下輸出:

$ ./my_script
1) bower
2) npm
3) gem
4) pip
Choose the package manager: 2
Enter the package name: bash-handbook
<installing bash-handbook>           
break 和 continue

如果想提前結束一個循環或跳過某次循環執行,可以使用 shell 的

break

continue

語句來實作。它們可以在任何循環中使用。

break

語句用來提前結束目前循環。

continue

語句用來跳過某次疊代。
for (( i = 0; i < 10; i++ )); do
  if [[ $(( i % 2 )) -eq 0 ]]; then continue; fi
  echo $i
done           

運作上面的例子,會列印出所有0到9之間的奇數。

函數

在腳本中,我們可以定義并調用函數。跟其它程式設計語言類似,函數是一個代碼塊,但有所不同。

bash 中,函數是一個指令序列,這個指令序列組織在某個名字下面,即 函數名 。調用函數跟其它語言一樣,寫下函數名字,函數就會被 調用 。

我們可以這樣聲明函數:

my_func () {
  ### 語句
}

my_func ### 調用 my_func           

我們必須在調用前聲明函數。

函數可以接收參數并傳回結果 —— 傳回值。參數,在函數内部,跟

非互動式

下的腳本參數處理方式相同 —— 使用

。傳回值可以使用

return

指令 傳回 。

下面這個函數接收一個名字參數,傳回

,表示成功執行。

### 帶參數的函數
greeting () {
  if [[ -n $1 ]]; then
    echo "Hello, $1!"
  else
    echo "Hello, unknown!"
  fi
  return 0
}

greeting Denys  ### Hello, Denys!
greeting        ### Hello, stranger!           

我們之前已經介紹過

傳回值

。不帶任何參數的

return

會傳回最後一個執行的指令的傳回值。上面的例子,

return 0

會傳回一個成功表示執行的值,

另外,還有幾個特殊字元用來處理參數:

參數處理
$# 傳遞到腳本的參數個數
$* 以一個單字元串顯示所有向腳本傳遞的參數
$$ 腳本運作的目前程序ID号
$! 背景運作的最後一個程序的ID号
$@ 與$*相同,但是使用時加引号,并在引号中傳回每個參數。
$- 顯示Shell使用的目前選項,與set指令功能相同。
$? 顯示最後指令的退出狀态。0表示沒有錯誤,其他任何值表明有錯誤。

流和重定向

Bash有很強大的工具來處理程式之間的協同工作。使用流,我們能将一個程式的輸出發送到另一個程式或檔案,是以,我們能友善地記錄日志或做一些其它我們想做的事。

管道給了我們建立傳送帶的機會,控制程式的執行成為可能。

學習如何使用這些強大的、進階的工具是非常非常重要的。

輸入、輸出流

Bash接收輸入,并以字元序列或 字元流 的形式産生輸出。這些流能被重定向到檔案或另一個流中。

有三個檔案描述符:

代碼 描述符

stdin

标準輸入

1

stdout

标準輸出

2

stderr

标準錯誤輸出

重定向

重定向讓我們可以控制一個指令的輸入來自哪裡,輸出結果到什麼地方。這些運算符在控制流的重定向時會被用到:

Operator Description

>

重定向輸出

&>

重定向輸出和錯誤輸出

&>>

以附加的形式重定向輸出和錯誤輸出

<

重定向輸入

<<

Here文檔 文法

<<<

Here字元串

以下是一些使用重定向的例子:

### ls的結果将會被寫到list.txt中
ls -l > list.txt

### 将輸出附加到list.txt中
ls -a >> list.txt

### 所有的錯誤資訊會被寫到errors.txt中
grep da * 2> errors.txt

### 從errors.txt中讀取輸入
less < errors.txt           

/dev/null

檔案

如果希望執行某個指令,但又不希望在螢幕上顯示輸出結果,那麼可以将輸出重定向到 /dev/null:

$ command > /dev/null           

/dev/null 是一個特殊的檔案,寫入到它的内容都會被丢棄;如果嘗試從該檔案讀取内容,那麼什麼也讀不到。但是 /dev/null 檔案非常有用,将指令的輸出重定向到它,會起到"禁止輸出"的效果。

如果希望屏蔽 stdout 和 stderr,可以這樣寫:

$ command > /dev/null 2>&1           

Debugging

shell提供了用于debugging腳本的工具。如果我們想以debug模式運作某腳本,可以在其shebang中使用一個特殊的選項:

#!/bin/bash options           

options是一些可以改變shell行為的選項。下表是一些可能對你有用的選項:

Short Name

-f

noglob 禁止檔案名展開(globbing)

-i

interactive 讓腳本以 互動 模式運作

-n

noexec 讀取指令,但不執行(文法檢查)

-t

執行完第一條指令後退出

-v

verbose 在執行每條指令前,向

stderr

輸出該指令

-x

xtrace

stderr

輸出該指令以及該指令的擴充參數

舉個例子,如果我們在腳本中指定了

-x

例如:

#!/bin/bash -x

for (( i = 0; i < 3; i++ )); do
  echo $i
done           

這會向

stdout

列印出變量的值和一些其它有用的資訊:

$ ./my_script
+ (( i = 0 ))
+ (( i < 3 ))
+ echo 0
0
+ (( i++  ))
+ (( i < 3 ))
+ echo 1
1
+ (( i++  ))
+ (( i < 3 ))
+ echo 2
2
+ (( i++  ))
+ (( i < 3 ))           

有時我們需要debug腳本的一部分。這種情況下,使用

set

指令會很友善。這個指令可以啟用或禁用選項。使用

-

啟用選項,

+

禁用選項:

#!/bin/bash

echo "xtrace is turned off"
set -x
echo "xtrace is enabled"
set +x
echo "xtrace is turned off again"           

資料

最後,Stack Overflow上

bash 标簽下

有很多你可以學習的問題,當你遇到問題時,也是一個提問的好地方。

繼續閱讀