Shell 快速指南
███████╗██╗ ██╗███████╗██╗ ██╗
██╔════╝██║ ██║██╔════╝██║ ██║
███████╗███████║█████╗ ██║ ██║
╚════██║██╔══██║██╔══╝ ██║ ██║
███████║██║ ██║███████╗███████╗███████╗
概述
什么是 shell
Shell 是一個用 C 語言編寫的程序,它是用戶使用 Linux 的橋梁。
Shell 既是一種命令語言,又是一種程序設計語言。
Shell 是指一種應用程序,這個應用程序提供了一個界面,用戶通過這個界面訪問 Linux 內(nèi)核的服務。
Ken Thompson 的 sh 是第一種 Unix Shell,Windows Explorer 是一個典型的圖形界面 Shell。
什么是 shell 腳本
Shell 腳本(shell script),是一種為 shell 編寫的腳本程序,一般文件后綴為 .sh
。
業(yè)界所說的 shell 通常都是指 shell 腳本,但 shell 和 shell script 是兩個不同的概念。
Shell 環(huán)境
Shell 編程跟 java、php 編程一樣,只要有一個能編寫代碼的文本編輯器和一個能解釋執(zhí)行的腳本解釋器就可以了。
Shell 的解釋器種類眾多,常見的有:
- sh - 即 Bourne Shell。sh 是 Unix 標準默認的 shell。
- bash - 即 Bourne Again Shell。bash 是 Linux 標準默認的 shell。
- fish - 智能和用戶友好的命令行 shell。
- xiki - 使 shell 控制臺更友好,更強大。
- zsh - 功能強大的 shell 與腳本語言。
指定腳本解釋器
在 shell 腳本,#!
告訴系統(tǒng)其后路徑所指定的程序即是解釋此腳本文件的 Shell 解釋器。#!
被稱作shebang(也稱為 Hashbang )。
所以,你應該會在 shell 中,見到諸如以下的注釋:
- 指定 sh 解釋器
#!/bin/sh
- 指定 bash 解釋器
#!/bin/bash
注意
上面的指定解釋器的方式是比較常見的,但有時候,你可能也會看到下面的方式:
#!/usr/bin/env bash
這樣做的好處是,系統(tǒng)會自動在
PATH
環(huán)境變量中查找你指定的程序(本例中的bash
)。相比第一種寫法,你應該盡量用這種寫法,因為程序的路徑是不確定的。這樣寫還有一個好處,操作系統(tǒng)的PATH
變量有可能被配置為指向程序的另一個版本。比如,安裝完新版本的bash
,我們可能將其路徑添加到PATH
中,來“隱藏”老版本。如果直接用#!/bin/bash
,那么系統(tǒng)會選擇老版本的bash
來執(zhí)行腳本,如果用#!/usr/bin/env bash
,則會使用新版本。
模式
shell 有交互和非交互兩種模式。
交互模式
簡單來說,你可以將 shell 的交互模式理解為執(zhí)行命令行。
看到形如下面的東西,說明shell處于交互模式下:
user@host:~$
接著,便可以輸入一系列 Linux 命令,比如 ls
,grep
,cd
,mkdir
,rm
等等。
非交互模式
簡單來說,你可以將 shell 的非交互模式理解為執(zhí)行 shell 腳本。
在非交互模式下,shell 從文件或者管道中讀取命令并執(zhí)行。
當 shell 解釋器執(zhí)行完文件中的最后一個命令,shell 進程終止,并回到父進程。
可以使用下面的命令讓shell以非交互模式運行:
sh /path/to/script.sh
bash /path/to/script.sh
上面的例子中,script.sh
是一個包含shell解釋器可以識別并執(zhí)行的命令的普通文本文件,sh
和bash
是shell解釋器程序。你可以使用任何喜歡的編輯器創(chuàng)建script.sh
(vim,nano,Sublime Text, Atom等等)。
除此之外,你還可以通過chmod
命令給文件添加可執(zhí)行的權(quán)限,來直接執(zhí)行腳本文件:
chmod +x /path/to/script.sh #使腳本具有執(zhí)行權(quán)限
/path/to/test.sh
這種方式要求腳本文件的第一行必須指明運行該腳本的程序,比如:
#!/bin/bash
echo "Hello, world!"
上面的例子中,我們使用了一個很有用的命令echo
來輸出字符串到屏幕上。
Shell 編程
由于 bash 是 Linux 標準默認的 shell,可以說 bash 是 shell 編程的基礎。
所以,下面將全部基于 bash 來講解 shell 編程。
此外,本篇章主要介紹的是 shell 編程的語法,對于 linux 指令不做任何介紹。
解釋器
前面雖然兩次提到了#!
,但是本著重要的事情說三遍的精神,這里再強調(diào)一遍:
在 shell 腳本,#!
告訴系統(tǒng)其后路徑所指定的程序即是解釋此腳本文件的 Shell 解釋器。#!
被稱作shebang(也稱為 Hashbang )。
#!
決定了腳本可以像一個獨立的可執(zhí)行文件一樣執(zhí)行,而不用在終端之前輸入sh
, bash
, python
, php
等。
示例:
# 以下兩種方式都可以指定 shell 解釋器為 bash,第二種方式更好
#!/bin/bash
#!/usr/bin/env bash
注釋
shell 語法支持注釋。注釋是特殊的語句,會被 shell 解釋器忽略。它們以 #
開頭,到行尾結(jié)束。
示例:
#!/bin/bash
### This script will print your username.
whoami
Tip: 用注釋來說明你的腳本是干什么的,以及為什么這樣寫。
變量
跟許多程序設計語言一樣,你可以在 bash 中創(chuàng)建變量。
Bash 中沒有數(shù)據(jù)類型,bash 中的變量可以保存一個數(shù)字、一個字符、一個字符串等等。同時無需提前聲明變量,給變量賦值會直接創(chuàng)建變量。
你可以創(chuàng)建三種變量:局部變量,環(huán)境變量以及作為位置參數(shù)的變量。
局部變量
局部變量是僅在某個腳本內(nèi)部有效的變量。它們不能被其他的程序和腳本訪問。
局部變量可以用
=
聲明(作為一種約定,變量名、=
、變量的值之間不應該有空格),其值可以用$
訪問到。
示例:
username="zhangpeng" ### 聲明變量
echo $username ### 輸出變量的值
unset username ### 刪除變量
可以用
local
關鍵字聲明屬于某個函數(shù)的局部變量。這樣聲明的變量會在函數(shù)結(jié)束時消失。
local local_var="I'm a local value"
環(huán)境變量
環(huán)境變量是對當前 shell 會話內(nèi)所有的程序或腳本都可見的變量。
創(chuàng)建它們跟創(chuàng)建局部變量類似,但使用的是
export
關鍵字。
export global_var="I'm a global value"
常見的環(huán)境變量:
變量 | 描述 |
---|---|
$HOME |
當前用戶的用戶目錄 |
$PATH |
用分號分隔的目錄列表,shell會到這些目錄中查找命令 |
$PWD |
當前工作目錄 |
$RANDOM |
0到32767之間的整數(shù) |
$UID |
數(shù)值類型,當前用戶的用戶ID |
$PS1 |
主要系統(tǒng)輸入提示符 |
$PS2 |
次要系統(tǒng)輸入提示符 |
這里 有一張更全面的 Bash 環(huán)境變量列表。
位置參數(shù)
位置參數(shù)是在調(diào)用一個函數(shù)并傳給它參數(shù)時創(chuàng)建的變量。
位置參數(shù)變量表:
變量 | 描述 |
---|---|
$0 |
腳本名稱 |
$1 … $9 |
第1個到第9個參數(shù)列表 |
${10} … ${N} |
第10個到N個參數(shù)列表 |
$* or $@
|
除了$0 外的所有位置參數(shù) |
$# |
不包括$0 在內(nèi)的位置參數(shù)的個數(shù) |
$FUNCNAME |
函數(shù)名稱(僅在函數(shù)內(nèi)部有值) |
示例:
在下面的例子中,位置參數(shù)為:$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擴展
擴展 發(fā)生在一行命令被分成一個個的 記號(tokens) 之后。換言之,擴展是一種執(zhí)行數(shù)學運算的機制,還可以用來保存命令的執(zhí)行結(jié)果,等等。
感興趣的話可以閱讀關于shell擴展的更多細節(jié)。
大括號擴展
大括號擴展讓生成任意的字符串成為可能。它跟 文件名擴展 很類似,舉個例子:
echo beg{i,a,u}n ### begin began begun
大括號擴展還可以用來創(chuàng)建一個可被循環(huán)迭代的區(qū)間。
echo {0..5} ### 0 1 2 3 4 5
echo {00..8..2} ### 00 02 04 06 08
命令置換
命令置換允許我們對一個命令求值,并將其值置換到另一個命令或者變量賦值表達式中。當一個命令被````或$()
包圍時,命令置換將會執(zhí)行。舉個例子:
now=`date +%T`
### or
now=$(date +%T)
echo $now ### 19:08:26
算數(shù)擴展
在bash中,執(zhí)行算數(shù)運算是非常方便的。算數(shù)表達式必須包在$(( ))
中。算數(shù)擴展的格式為:
result=$(( ((10 + 5*3) - 7) / 2 ))
echo $result ### 9
在算數(shù)表達式中,使用變量無需帶上$
前綴:
x=4
y=7
echo $(( x + y )) ### 11
echo $(( ++x + y++ )) ### 12
echo $(( x + y )) ### 13
單引號和雙引號
單引號和雙引號之間有很重要的區(qū)別。在雙引號中,變量引用或者命令置換是會被展開的。在單引號中是不會的。舉個例子:
echo "Your home: $HOME" ### Your home: /Users/<username>
echo 'Your home: $HOME' ### Your home: $HOME
當局部變量和環(huán)境變量包含空格時,它們在引號中的擴展要格外注意。隨便舉個例子,假如我們用echo
來輸出用戶的輸入:
INPUT="A string with strange whitespace."
echo $INPUT ### A string with strange whitespace.
echo "$INPUT" ### A string with strange whitespace.
調(diào)用第一個echo
時給了它5個單獨的參數(shù) —— $INPUT
被分成了單獨的詞,echo
在每個詞之間打印了一個空格。第二種情況,調(diào)用echo
時只給了它一個參數(shù)(整個$INPUT的值,包括其中的空格)。
來看一個更嚴肅的例子:
FILE="Favorite Things.txt"
cat $FILE ### 嘗試輸出兩個文件: `Favorite` 和 `Things.txt`
cat "$FILE" ### 輸出一個文件: `Favorite Things.txt`
盡管這個問題可以通過把FILE重命名成Favorite-Things.txt
來解決,但是,假如這個值來自某個環(huán)境變量,來自一個位置參數(shù),或者來自其它命令(find
, cat
, 等等)呢。因此,如果輸入 可能 包含空格,務必要用引號把表達式包起來。
數(shù)組
跟其它程序設計語言一樣,bash中的數(shù)組變量給了你引用多個值的能力。在bash中,數(shù)組下標也是從0開始,也就是說,第一個元素的下標是0。
跟數(shù)組打交道時,要注意一個特殊的環(huán)境變量IFS
。IFS,全稱 Input Field Separator,保存了數(shù)組中元素的分隔符。它的默認值是一個空格IFS=' '
。
創(chuàng)建數(shù)組
在 bash 中有好幾種方法創(chuàng)建一個數(shù)組
array[0] = val
array[1] = val
array[2] = val
array=([2]=val [0]=val [1]=val)
array=(val val val)
獲取數(shù)組元素
- 獲取數(shù)組的單個元素:
echo ${array[1]}
- 獲取數(shù)組的所有元素:
echo ${array[*]}
echo ${array[@]}
上面兩行有很重要(也很微妙)的區(qū)別,假設某數(shù)組元素中包含空格:
colors[0]=Red
colors[1]="Dark Green"
colors[2]=Blue
為了將數(shù)組中每個元素單獨一行輸出,我們用內(nèi)建的printf
命令:
printf "+ %s\n" ${colors[*]}
# 輸出:
# + Red
# + Dark
# + Green
# + Blue
為什么Desert
和fig
各占了一行?嘗試用引號包起來:
printf "+ %s\n" "${colors[*]}"
# 輸出:
# + Red Dark Green Blue
現(xiàn)在所有的元素都跑去了一行 —— 這不是我們想要的!為了解決這個痛點,${colors[@]}
閃亮登場:
printf "+ %s\n" "${colors[@]}"
# 輸出:
+ Red
+ Dark Green
+ Blue
在引號內(nèi),${colors[@]}
將數(shù)組中的每個元素擴展為一個單獨的參數(shù);數(shù)組元素中的空格得以保留。
- 訪問數(shù)組的部分元素:
echo ${array[@]:0:2}
在上面的例子中,${array[@]}
擴展為整個數(shù)組,:0:2
取出了數(shù)組中從0開始,長度為2的元素。
獲取數(shù)組長度
echo ${#array[*]}
向數(shù)組中添加元素
向數(shù)組中添加元素也非常簡單:
colors=(Yellow "${colors[@]}" Pink Black)
echo ${colors[@]}
# 輸出:
# Yellow Red Dark Green Blue Pink Black
上面的例子中,${colors[@]}
擴展為整個數(shù)組,并被置換到復合賦值語句中,接著,對數(shù)組colors
的賦值覆蓋了它原來的值。
從數(shù)組中刪除元素
用unset
命令來從數(shù)組中刪除一個元素:
unset colors[0]
echo ${colors[@]}
# 輸出:
# Red Dark Green Blue Pink Black
運算符
算術運算符
下表列出了常用的算術運算符,假定變量 a 為 10,變量 b 為 20:
運算符 | 說明 | 舉例 |
---|---|---|
+ | 加法 |
expr $a + $b 結(jié)果為 30。 |
- | 減法 |
expr $a - $b 結(jié)果為 -10。 |
* | 乘法 |
expr $a \* $b 結(jié)果為 200。 |
/ | 除法 |
expr $b / $a 結(jié)果為 2。 |
% | 取余 |
expr $b % $a 結(jié)果為 0。 |
= | 賦值 |
a=$b 將把變量 b 的值賦給 a。 |
== | 相等。用于比較兩個數(shù)字,相同則返回 true。 |
[ $a == $b ] 返回 false。 |
!= | 不相等。用于比較兩個數(shù)字,不相同則返回 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
關系運算符
關系運算符只支持數(shù)字,不支持字符串,除非字符串的值是數(shù)字。
下表列出了常用的關系運算符,假定變量 a 為 10,變量 b 為 20:
運算符 | 說明 | 舉例 |
---|---|---|
-eq | 檢測兩個數(shù)是否相等,相等返回 true。 |
[ $a -eq $b ] 返回 false。 |
-ne | 檢測兩個數(shù)是否相等,不相等返回 true。 |
[ $a -ne $b ] 返回 true。 |
-gt | 檢測左邊的數(shù)是否大于右邊的,如果是,則返回 true。 |
[ $a -gt $b ] 返回 false。 |
-lt | 檢測左邊的數(shù)是否小于右邊的,如果是,則返回 true。 |
[ $a -lt $b ] 返回 true。 |
-ge | 檢測左邊的數(shù)是否大于等于右邊的,如果是,則返回 true。 |
[ $a -ge $b ] 返回 false。 |
-le | 檢測左邊的數(shù)是否小于等于右邊的,如果是,則返回 true。 |
[ $a -le $b ] 返回 true。 |
示例:
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 ] 返回 true。 |
-o | 或運算,有一個表達式為 true 則返回 true。 |
[ $a -lt 20 -o $b -gt 100 ] 返回 true。 |
-a | 與運算,兩個表達式都為 true 才返回 true。 |
[ $a -lt 20 -a $b -gt 100 ] 返回 false。 |
示例:
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 ] 返回 false。 |
!= | 檢測兩個字符串是否相等,不相等返回 true。 |
[ $a != $b ] 返回 true。 |
-z | 檢測字符串長度是否為0,為0返回 true。 |
[ -z $a ] 返回 false。 |
-n | 檢測字符串長度是否為0,不為0返回 true。 |
[ -n $a ] 返回 true。 |
str | 檢測字符串是否為空,不為空返回 true。 |
[ $a ] 返回 true。 |
示例:
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 ] 返回 false。 |
-c file | 檢測文件是否是字符設備文件,如果是,則返回 true。 |
[ -c $file ] 返回 false。 |
-d file | 檢測文件是否是目錄,如果是,則返回 true。 |
[ -d $file ] 返回 false。 |
-f file | 檢測文件是否是普通文件(既不是目錄,也不是設備文件),如果是,則返回 true。 |
[ -f $file ] 返回 true。 |
-g file | 檢測文件是否設置了 SGID 位,如果是,則返回 true。 |
[ -g $file ] 返回 false。 |
-k file | 檢測文件是否設置了粘著位(Sticky Bit),如果是,則返回 true。 |
[ -k $file ] 返回 false。 |
-p file | 檢測文件是否是有名管道,如果是,則返回 true。 |
[ -p $file ] 返回 false。 |
-u file | 檢測文件是否設置了 SUID 位,如果是,則返回 true。 |
[ -u $file ] 返回 false。 |
-r file | 檢測文件是否可讀,如果是,則返回 true。 |
[ -r $file ] 返回 true。 |
-w file | 檢測文件是否可寫,如果是,則返回 true。 |
[ -w $file ] 返回 true。 |
-x file | 檢測文件是否可執(zhí)行,如果是,則返回 true。 |
[ -x $file ] 返回 true。 |
-s file | 檢測文件是否為空(文件大小是否大于0),不為空返回 true。 |
[ -s $file ] 返回 true。 |
-e file | 檢測文件(包括目錄)是否存在,如果是,則返回 true。 |
[ -e $file ] 返回 true。 |
示例:
變量 file 表示文件"/var/www/runoob/test.sh",它的大小為100字節(jié),具有 rwx 權(quán)限。下面的代碼,將檢測該文件的各種屬性:
file="./operatorDemo.sh"
if [ -r $file ]
then
echo "文件可讀"
else
echo "文件不可讀"
fi
if [ -w $file ]
then
echo "文件可寫"
else
echo "文件不可寫"
fi
if [ -x $file ]
then
echo "文件可執(zhí)行"
else
echo "文件不可執(zhí)行"
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中的條件語句讓我們可以決定一個操作是否被執(zhí)行。結(jié)果取決于一個包在[[ ]]
里的表達式。
條件表達式可以包含&&
和||
運算符,分別對應 與 和 或 。除此之外還有很多有用的表達式。
共有兩個不同的條件表達式:if
和case
。
基元和組合表達式
由[[ ]]
(sh
中是[ ]
)包起來的表達式被稱作 檢測命令 或 基元。這些表達式幫助我們檢測一個條件的結(jié)果。在下面的表里,為了兼容sh
,我們用的是[ ]
。這里可以找到有關bash中單雙中括號區(qū)別的答案。
使用if
if
在使用上跟其它語言相同。如果中括號里的表達式為真,那么then
和fi
之間的代碼會被執(zhí)行。fi
標志著條件代碼塊的結(jié)束。
### 寫成一行
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
每種情況都是匹配了某個模式的表達式。|
用來分割多個模式,)
用來結(jié)束一個模式序列。第一個匹配上的模式對應的命令將會被執(zhí)行。*
代表任何不匹配以上給定模式的模式。命令塊兒之間要用;;
分隔。
循環(huán)語句
循環(huán)其實不足為奇。跟其它程序設計語言一樣,bash中的循環(huán)也是只要控制條件為真就一直迭代執(zhí)行的代碼塊。
Bash中有四種循環(huán):for
,while
,until
和select
。
for
循環(huán)
for
與它在C語言中的姊妹非常像。看起來是這樣:
for arg in elem1 elem2 ... elemN
do
### 語句
done
在每次循環(huán)的過程中,arg
依次被賦值為從elem1
到elemN
。這些值還可以是通配符或者大括號擴展。
當然,我們還可以把for
循環(huán)寫在一行,但這要求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
文件夾中,并給它們可執(zhí)行權(quán)限,我們的腳本可以這樣寫:
#!/bin/bash
for FILE in $HOME/*.bash; do
mv "$FILE" "${HOME}/scripts"
chmod +x "${HOME}/scripts/${FILE}"
done
while
循環(huán)
while
循環(huán)檢測一個條件,只要這個條件為 真,就執(zhí)行一段命令。被檢測的條件跟if..then
中使用的基元并無二異。因此一個while
循環(huán)看起來會是這樣:
while [[ condition ]]
do
### 語句
done
跟for
循環(huán)一樣,如果我們把do
和被檢測的條件寫到一行,那么必須要在do
之前加一個分號。
比如下面這個例子:
#!/bin/bash
### 0到9之間每個數(shù)的平方
x=0
while [[ $x -lt 10 ]]; do ### x小于10
echo $(( x * x ))
x=$(( x + 1 )) ### x加1
done
until
循環(huán)
until
循環(huán)跟while
循環(huán)正好相反。它跟while
一樣也需要檢測一個測試條件,但不同的是,只要該條件為 假 就一直執(zhí)行循環(huán):
until [[ condition ]]; do
### 語句
done
select
循環(huán)
select
循環(huán)幫助我們組織一個用戶菜單。它的語法幾乎跟for
循環(huán)一致:
select answer in elem1 elem2 ... elemN
do
### 語句
done
select
會打印elem1..elemN
以及它們的序列號到屏幕上,之后會提示用戶輸入。通常看到的是$?
(PS3
變量)。用戶的選擇結(jié)果會被保存到answer
中。如果answer
是一個在1..N
之間的數(shù)字,那么語句
會被執(zhí)行,緊接著會進行下一次迭代 —— 如果不想這樣的話我們可以使用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 ### 避免無限循環(huán)
done
這個例子,先詢問用戶他想使用什么包管理器。接著,又詢問了想安裝什么包,最后執(zhí)行安裝操作。
運行這個腳本,會得到如下輸出:
$ ./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
如果想提前結(jié)束一個循環(huán)或跳過某次循環(huán)執(zhí)行,可以使用 shell 的break
和continue
語句來實現(xiàn)。它們可以在任何循環(huán)中使用。
break
語句用來提前結(jié)束當前循環(huán)。
continue
語句用來跳過某次迭代。
for (( i = 0; i < 10; i++ )); do
if [[ $(( i % 2 )) -eq 0 ]]; then continue; fi
echo $i
done
運行上面的例子,會打印出所有0到9之間的奇數(shù)。
函數(shù)
在腳本中,我們可以定義并調(diào)用函數(shù)。跟其它程序設計語言類似,函數(shù)是一個代碼塊,但有所不同。
bash 中,函數(shù)是一個命令序列,這個命令序列組織在某個名字下面,即 函數(shù)名 。調(diào)用函數(shù)跟其它語言一樣,寫下函數(shù)名字,函數(shù)就會被 調(diào)用 。
我們可以這樣聲明函數(shù):
my_func () {
### 語句
}
my_func ### 調(diào)用 my_func
我們必須在調(diào)用前聲明函數(shù)。
函數(shù)可以接收參數(shù)并返回結(jié)果 —— 返回值。參數(shù),在函數(shù)內(nèi)部,跟非交互式下的腳本參數(shù)處理方式相同 —— 使用位置參數(shù)。返回值可以使用return
命令 返回 。
下面這個函數(shù)接收一個名字參數(shù),返回0
,表示成功執(zhí)行。
### 帶參數(shù)的函數(shù)
greeting () {
if [[ -n $1 ]]; then
echo "Hello, $1!"
else
echo "Hello, unknown!"
fi
return 0
}
greeting Denys ### Hello, Denys!
greeting ### Hello, stranger!
我們之前已經(jīng)介紹過返回值。不帶任何參數(shù)的return
會返回最后一個執(zhí)行的命令的返回值。上面的例子,return 0
會返回一個成功表示執(zhí)行的值,0
。
另外,還有幾個特殊字符用來處理參數(shù):
參數(shù)處理 | 說明 |
---|---|
$# | 傳遞到腳本的參數(shù)個數(shù) |
$* | 以一個單字符串顯示所有向腳本傳遞的參數(shù) |
$$ | 腳本運行的當前進程ID號 |
$! | 后臺運行的最后一個進程的ID號 |
$@ | 與$*相同,但是使用時加引號,并在引號中返回每個參數(shù)。 |
$- | 顯示Shell使用的當前選項,與set命令功能相同。 |
$? | 顯示最后命令的退出狀態(tài)。0表示沒有錯誤,其他任何值表明有錯誤。 |
流和重定向
Bash有很強大的工具來處理程序之間的協(xié)同工作。使用流,我們能將一個程序的輸出發(fā)送到另一個程序或文件,因此,我們能方便地記錄日志或做一些其它我們想做的事。
管道給了我們創(chuàng)建傳送帶的機會,控制程序的執(zhí)行成為可能。
學習如何使用這些強大的、高級的工具是非常非常重要的。
輸入、輸出流
Bash接收輸入,并以字符序列或 字符流 的形式產(chǎn)生輸出。這些流能被重定向到文件或另一個流中。
有三個文件描述符:
代碼 | 描述符 | 描述 |
---|---|---|
0 |
stdin |
標準輸入 |
1 |
stdout |
標準輸出 |
2 |
stderr |
標準錯誤輸出 |
重定向
重定向讓我們可以控制一個命令的輸入來自哪里,輸出結(jié)果到什么地方。這些運算符在控制流的重定向時會被用到:
Operator | Description |
---|---|
> |
重定向輸出 |
&> |
重定向輸出和錯誤輸出 |
&>> |
以附加的形式重定向輸出和錯誤輸出 |
< |
重定向輸入 |
<< |
Here文檔 語法 |
<<< |
Here字符串 |
以下是一些使用重定向的例子:
### ls的結(jié)果將會被寫到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
文件
如果希望執(zhí)行某個命令,但又不希望在屏幕上顯示輸出結(jié)果,那么可以將輸出重定向到 /dev/null:
$ command > /dev/null
/dev/null 是一個特殊的文件,寫入到它的內(nèi)容都會被丟棄;如果嘗試從該文件讀取內(nèi)容,那么什么也讀不到。但是 /dev/null 文件非常有用,將命令的輸出重定向到它,會起到"禁止輸出"的效果。
如果希望屏蔽 stdout 和 stderr,可以這樣寫:
$ command > /dev/null 2>&1
Debugging
shell提供了用于debugging腳本的工具。如果我們想以debug模式運行某腳本,可以在其shebang中使用一個特殊的選項:
#!/bin/bash options
options是一些可以改變shell行為的選項。下表是一些可能對你有用的選項:
Short | Name | Description |
---|---|---|
-f |
noglob | 禁止文件名展開(globbing) |
-i |
interactive | 讓腳本以 交互 模式運行 |
-n |
noexec | 讀取命令,但不執(zhí)行(語法檢查) |
-t |
— | 執(zhí)行完第一條命令后退出 |
-v |
verbose | 在執(zhí)行每條命令前,向stderr 輸出該命令 |
-x |
xtrace | 在執(zhí)行每條命令前,向stderr 輸出該命令以及該命令的擴展參數(shù) |
舉個例子,如果我們在腳本中指定了-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"
資料
- awesome-shell,shell 資源列表
- awesome-bash,bash 資源列表
- bash-handbook
- bash-guide ,bash 基本用法指南
- bash-it,為你日常使用,開發(fā)以及維護 shell 腳本和自定義命令提供了一個可靠的框架
- dotfiles.github.io,上面有bash和其它shell的各種dotfiles集合以及shell框架的鏈接
- Runoob Shell 教程
最后,Stack Overflow上 bash 標簽下有很多你可以學習的問題,當你遇到問題時,也是一個提問的好地方。