因爲 bash 是 Linux 標準默認的 shell 解釋器,能夠說 bash 是 shell 編程的基礎。php
本文主要介紹 bash 的語法,對於 linux 指令不作任何介紹。html
:notebook: 本文已歸檔到:「blog」 :keyboard: 本文的源碼已歸檔到 linux-tutorialjava
███████╗██╗ ██╗███████╗██╗ ██╗
██╔════╝██║ ██║██╔════╝██║ ██║
███████╗███████║█████╗ ██║ ██║
╚════██║██╔══██║██╔══╝ ██║ ██║
███████║██║ ██║███████╗███████╗███████╗
複製代碼
Ken Thompson 的 sh 是第一種 Unix Shell,Windows Explorer 是一個典型的圖形界面 Shell。python
Shell 腳本(shell script),是一種爲 shell 編寫的腳本程序,通常文件後綴爲 .sh
。linux
業界所說的 shell 一般都是指 shell 腳本,但 shell 和 shell script 是兩個不一樣的概念。git
Shell 編程跟 java、php 編程同樣,只要有一個能編寫代碼的文本編輯器和一個能解釋執行的腳本解釋器就能夠了。github
Shell 的解釋器種類衆多,常見的有:shell
在 shell 腳本,#!
告訴系統其後路徑所指定的程序便是解釋此腳本文件的 Shell 解釋器。#!
被稱做shebang(也稱爲 Hashbang )。npm
因此,你應該會在 shell 中,見到諸如如下的註釋:編程
#!/bin/sh 複製代碼
#!/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
source /path/to/script.sh
./path/to/script.sh
複製代碼
上面的例子中,script.sh
是一個包含 shell 解釋器能夠識別並執行的命令的普通文本文件,sh
和bash
是 shell 解釋器程序。你可使用任何喜歡的編輯器建立script.sh
(vim,nano,Sublime Text, Atom 等等)。
其中,source /path/to/script.sh
和 ./path/to/script.sh
是等價的。
除此以外,你還能夠經過chmod
命令給文件添加可執行的權限,來直接執行腳本文件:
chmod +x /path/to/script.sh #使腳本具備執行權限
/path/to/test.sh
複製代碼
這種方式要求腳本文件的第一行必須指明運行該腳本的程序,好比:
:keyboard: 『示例源碼』 helloworld.sh
#!/usr/bin/env bash
echo "Hello, world!"
複製代碼
上面的例子中,咱們使用了一個頗有用的命令echo
來輸出字符串到屏幕上。
前面雖然兩次提到了#!
,可是本着重要的事情說三遍的精神,這裏再強調一遍:
在 shell 腳本,#!
告訴系統其後路徑所指定的程序便是解釋此腳本文件的 Shell 解釋器。#!
被稱做shebang(也稱爲 Hashbang )。
#!
決定了腳本能夠像一個獨立的可執行文件同樣執行,而不用在終端以前輸入sh
, bash
, python
, php
等。
# 如下兩種方式均可以指定 shell 解釋器爲 bash,第二種方式更好
#!/bin/bash
#!/usr/bin/env bash 複製代碼
註釋能夠說明你的代碼是什麼做用,以及爲何這樣寫。
shell 語法中,註釋是特殊的語句,會被 shell 解釋器忽略。
#
開頭,到行尾結束。:<<EOF
開頭,到 EOF
結束。:keyboard: 『示例源碼』 comment-demo.sh
#--------------------------------------------
# shell 註釋示例
# author:zp
#--------------------------------------------
# echo '這是單行註釋'
########## 這是分割線 ##########
:<<EOF
echo '這是多行註釋'
echo '這是多行註釋'
echo '這是多行註釋'
EOF
複製代碼
echo 用於字符串的輸出。
輸出普通字符串:
echo "hello, world"
# Output: hello, world
複製代碼
輸出含變量的字符串:
echo "hello, \"zp\""
# Output: hello, "zp"
複製代碼
輸出含變量的字符串:
name=zp
echo "hello, \"${name}\""
# Output: hello, "zp"
複製代碼
輸出含換行符的字符串:
# 輸出含換行符的字符串
echo "YES\nNO"
# Output: YES\nNO
echo -e "YES\nNO" # -e 開啓轉義
# Output:
# YES
# NO
複製代碼
輸出含不換行符的字符串:
echo "YES"
echo "NO"
# Output:
# YES
# NO
echo -e "YES\c" # -e 開啓轉義 \c 不換行
echo "NO"
# Output:
# YESNO
複製代碼
輸出重定向至文件
echo "test" > test.txt
複製代碼
輸出執行結果
echo `pwd`
# Output:(當前目錄路徑)
複製代碼
:keyboard: 『示例源碼』 echo-demo.sh
printf 用於格式化輸出字符串。
默認,printf 不會像 echo 同樣自動添加換行符,若是須要換行能夠手動添加 \n
。
:keyboard: 『示例源碼』 printf-demo.sh
# 單引號
printf '%d %s\n' 1 "abc"
# Output:1 abc
# 雙引號
printf "%d %s\n" 1 "abc"
# Output:1 abc
# 無引號
printf %s abcdef
# Output: abcdef(並不會換行)
# 格式只指定了一個參數,但多出的參數仍然會按照該格式輸出
printf "%s\n" abc def
# Output:
# abc
# def
printf "%s %s %s\n" a b c d e f g h i j
# Output:
# a b c
# d e f
# g h i
# j
# 若是沒有參數,那麼 %s 用 NULL 代替,%d 用 0 代替
printf "%s and %d \n"
# Output:
# and 0
# 格式化輸出
printf "%-10s %-8s %-4s\n" 姓名 性別 體重kg
printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234
printf "%-10s %-8s %-4.2f\n" 楊過 男 48.6543
printf "%-10s %-8s %-4.2f\n" 郭芙 女 47.9876
# Output:
# 姓名 性別 體重kg
# 郭靖 男 66.12
# 楊過 男 48.65
# 郭芙 女 47.99
複製代碼
序列 | 說明 |
---|---|
\a |
警告字符,一般爲 ASCII 的 BEL 字符 |
\b |
後退 |
\c |
抑制(不顯示)輸出結果中任何結尾的換行字符(只在%b 格式指示符控制下的參數字符串中有效),並且,任何留在參數裏的字符、任何接下來的參數以及任何留在格式字符串中的字符,都被忽略 |
\f |
換頁(formfeed) |
\n |
換行 |
\r |
回車(Carriage return) |
\t |
水平製表符 |
\v |
垂直製表符 |
\\ |
一個字面上的反斜槓字符 |
\ddd |
表示 1 到 3 位數八進制值的字符。僅在格式字符串中有效 |
\0ddd |
表示 1 到 3 位的八進制值字符 |
跟許多程序設計語言同樣,你能夠在 bash 中建立變量。
Bash 中沒有數據類型,bash 中的變量能夠保存一個數字、一個字符、一個字符串等等。同時無需提早聲明變量,給變量賦值會直接建立變量。
訪問變量的語法形式爲:${var}
和 $var
。
變量名外面的花括號是可選的,加不加都行,加花括號是爲了幫助解釋器識別變量的邊界,因此推薦加花括號。
word="hello"
echo ${word}
# Output: hello
複製代碼
使用 readonly 命令能夠將變量定義爲只讀變量,只讀變量的值不能被改變。
rword="hello"
echo ${rword}
readonly rword
# rword="bye" # 若是放開註釋,執行時會報錯
複製代碼
使用 unset 命令能夠刪除變量。變量被刪除後不能再次使用。unset 命令不能刪除只讀變量。
dword="hello" # 聲明變量
echo ${dword} # 輸出變量值
# Output: hello
unset dword # 刪除變量
echo ${dword}
# Output: (空)
複製代碼
export
關鍵字,shell 腳本也能夠定義環境變量。常見的環境變量:
變量 | 描述 |
---|---|
$HOME |
當前用戶的用戶目錄 |
$PATH |
用分號分隔的目錄列表,shell 會到這些目錄中查找命令 |
$PWD |
當前工做目錄 |
$RANDOM |
0 到 32767 之間的整數 |
$UID |
數值類型,當前用戶的用戶 ID |
$PS1 |
主要系統輸入提示符 |
$PS2 |
次要系統輸入提示符 |
這裏 有一張更全面的 Bash 環境變量列表。
⌨️ 『示例源碼』 variable-demo.sh
shell 字符串能夠用單引號 ''
,也能夠用雙引號 「」
,也能夠不用引號。
綜上,推薦使用雙引號。
# 使用單引號拼接
name1='white'
str1='hello, '${name1}''
str2='hello, ${name1}'
echo ${str1}_${str2}
# Output:
# hello, white_hello, ${name1}
# 使用雙引號拼接
name2="black"
str3="hello, "${name2}""
str4="hello, ${name2}"
echo ${str3}_${str4}
# Output:
# hello, black_hello, black
複製代碼
text="12345"
echo ${#text}
# Output:
# 5
複製代碼
text="12345"
echo ${text:2:2}
# Output:
# 34
複製代碼
從第 3 個字符開始,截取 2 個字符
#!/usr/bin/env bash
text="hello"
echo `expr index "${text}" ll`
# Execute: ./str-demo5.sh
# Output:
# 3
複製代碼
查找 ll
子字符在 hello
字符串中的起始位置。
⌨️ 『示例源碼』 string-demo.sh
bash 只支持一維數組。
數組下標從 0 開始,下標能夠是整數或算術表達式,其值應大於或等於 0。
# 建立數組的不一樣方式
nums=([2]=2 [0]=0 [1]=1)
colors=(red yellow "dark blue")
複製代碼
echo ${nums[1]}
# Output: 1
複製代碼
echo ${colors[*]}
# Output: red yellow dark blue
echo ${colors[@]}
# Output: red yellow dark blue
複製代碼
上面兩行有很重要(也很微妙)的區別:
爲了將數組中每一個元素單獨一行輸出,咱們用 printf
命令:
printf "+ %s\n" ${colors[*]}
# Output:
# + red
# + yellow
# + dark
# + blue
複製代碼
爲何dark
和blue
各佔了一行?嘗試用引號包起來:
printf "+ %s\n" "${colors[*]}"
# Output:
# + red yellow dark blue
複製代碼
如今全部的元素都在一行輸出 —— 這不是咱們想要的!讓咱們試試${colors[@]}
printf "+ %s\n" "${colors[@]}"
# Output:
# + red
# + yellow
# + dark blue
複製代碼
在引號內,${colors[@]}
將數組中的每一個元素擴展爲一個單獨的參數;數組元素中的空格得以保留。
echo ${nums[@]:0:2}
# Output:
# 0 1
複製代碼
在上面的例子中,${array[@]}
擴展爲整個數組,:0:2
取出了數組中從 0 開始,長度爲 2 的元素。
echo ${#nums[*]}
# Output:
# 3
複製代碼
向數組中添加元素也很是簡單:
colors=(white "${colors[@]}" green black)
echo ${colors[@]}
# Output:
# white red yellow dark blue green black
複製代碼
上面的例子中,${colors[@]}
擴展爲整個數組,並被置換到複合賦值語句中,接着,對數組colors
的賦值覆蓋了它原來的值。
用unset
命令來從數組中刪除一個元素:
unset nums[0]
echo ${nums[@]}
# Output:
# 1 2
複製代碼
:keyboard: 『示例源碼』 array-demo.sh
下表列出了經常使用的算術運算符,假定變量 x 爲 10,變量 y 爲 20:
運算符 | 說明 | 舉例 |
---|---|---|
+ | 加法 | expr $x + $y 結果爲 30。 |
- | 減法 | expr $x - $y 結果爲 -10。 |
* | 乘法 | expr $x * $y 結果爲 200。 |
/ | 除法 | expr $y / $x 結果爲 2。 |
% | 取餘 | expr $y % $x 結果爲 0。 |
= | 賦值 | x=$y 將把變量 y 的值賦給 x。 |
== | 相等。用於比較兩個數字,相同則返回 true。 | [ $x == $y ] 返回 false。 |
!= | 不相等。用於比較兩個數字,不相同則返回 true。 | [ $x != $y ] 返回 true。 |
**注意:**條件表達式要放在方括號之間,而且要有空格,例如: [$x==$y]
是錯誤的,必須寫成 [ $x == $y ]
。
:keyboard: 『示例源碼』 operator-demo.sh
x=10
y=20
echo "x=${x}, y=${y}"
val=`expr ${x} + ${y}`
echo "${x} + ${y} = $val"
val=`expr ${x} - ${y}`
echo "${x} - ${y} = $val"
val=`expr ${x} \* ${y}`
echo "${x} * ${y} = $val"
val=`expr ${y} / ${x}`
echo "${y} / ${x} = $val"
val=`expr ${y} % ${x}`
echo "${y} % ${x} = $val"
if [[ ${x} == ${y} ]]
then
echo "${x} = ${y}"
fi
if [[ ${x} != ${y} ]]
then
echo "${x} != ${y}"
fi
# Execute: ./operator-demo.sh
# Output:
# x=10, y=20
# 10 + 20 = 30
# 10 - 20 = -10
# 10 * 20 = 200
# 20 / 10 = 2
# 20 % 10 = 0
# 10 != 20
複製代碼
關係運算符只支持數字,不支持字符串,除非字符串的值是數字。
下表列出了經常使用的關係運算符,假定變量 x 爲 10,變量 y 爲 20:
運算符 | 說明 | 舉例 |
---|---|---|
-eq |
檢測兩個數是否相等,相等返回 true。 | [ $a -eq $b ] 返回 false。 |
-ne |
檢測兩個數是否相等,不相等返回 true。 | [ $a -ne $b ] 返回 true。 |
-gt |
檢測左邊的數是否大於右邊的,若是是,則返回 true。 | [ $a -gt $b ] 返回 false。 |
-lt |
檢測左邊的數是否小於右邊的,若是是,則返回 true。 | [ $a -lt $b ] 返回 true。 |
-ge |
檢測左邊的數是否大於等於右邊的,若是是,則返回 true。 | [ $a -ge $b ] 返回 false。 |
-le |
檢測左邊的數是否小於等於右邊的,若是是,則返回 true。 | [ $a -le $b ] 返回 true。 |
:keyboard: 『示例源碼』 operator-demo2.sh
x=10
y=20
echo "x=${x}, y=${y}"
if [[ ${x} -eq ${y} ]]; then
echo "${x} -eq ${y} : x 等於 y"
else
echo "${x} -eq ${y}: x 不等於 y"
fi
if [[ ${x} -ne ${y} ]]; then
echo "${x} -ne ${y}: x 不等於 y"
else
echo "${x} -ne ${y}: x 等於 y"
fi
if [[ ${x} -gt ${y} ]]; then
echo "${x} -gt ${y}: x 大於 y"
else
echo "${x} -gt ${y}: x 不大於 y"
fi
if [[ ${x} -lt ${y} ]]; then
echo "${x} -lt ${y}: x 小於 y"
else
echo "${x} -lt ${y}: x 不小於 y"
fi
if [[ ${x} -ge ${y} ]]; then
echo "${x} -ge ${y}: x 大於或等於 y"
else
echo "${x} -ge ${y}: x 小於 y"
fi
if [[ ${x} -le ${y} ]]; then
echo "${x} -le ${y}: x 小於或等於 y"
else
echo "${x} -le ${y}: x 大於 y"
fi
# Execute: ./operator-demo2.sh
# Output:
# x=10, y=20
# 10 -eq 20: x 不等於 y
# 10 -ne 20: x 不等於 y
# 10 -gt 20: x 不大於 y
# 10 -lt 20: x 小於 y
# 10 -ge 20: x 小於 y
# 10 -le 20: x 小於或等於 y
複製代碼
下表列出了經常使用的布爾運算符,假定變量 x 爲 10,變量 y 爲 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。 |
:keyboard: 『示例源碼』 operator-demo3.sh
x=10
y=20
echo "x=${x}, y=${y}"
if [[ ${x} != ${y} ]]; then
echo "${x} != ${y} : x 不等於 y"
else
echo "${x} != ${y}: x 等於 y"
fi
if [[ ${x} -lt 100 && ${y} -gt 15 ]]; then
echo "${x} 小於 100 且 ${y} 大於 15 : 返回 true"
else
echo "${x} 小於 100 且 ${y} 大於 15 : 返回 false"
fi
if [[ ${x} -lt 100 || ${y} -gt 100 ]]; then
echo "${x} 小於 100 或 ${y} 大於 100 : 返回 true"
else
echo "${x} 小於 100 或 ${y} 大於 100 : 返回 false"
fi
if [[ ${x} -lt 5 || ${y} -gt 100 ]]; then
echo "${x} 小於 5 或 ${y} 大於 100 : 返回 true"
else
echo "${x} 小於 5 或 ${y} 大於 100 : 返回 false"
fi
# Execute: ./operator-demo3.sh
# Output:
# x=10, y=20
# 10 != 20 : x 不等於 y
# 10 小於 100 且 20 大於 15 : 返回 true
# 10 小於 100 或 20 大於 100 : 返回 true
# 10 小於 5 或 20 大於 100 : 返回 false
複製代碼
如下介紹 Shell 的邏輯運算符,假定變量 x 爲 10,變量 y 爲 20:
運算符 | 說明 | 舉例 |
---|---|---|
&& |
邏輯的 AND | [[ ${x} -lt 100 && ${y} -gt 100 ]] 返回 false |
|| |
邏輯的 OR | [[ ${x} -lt 100 || ${y} -gt 100 ]] 返回 true |
:keyboard: 『示例源碼』 operator-demo4.sh
x=10
y=20
echo "x=${x}, y=${y}"
if [[ ${x} -lt 100 && ${y} -gt 100 ]]
then
echo "${x} -lt 100 && ${y} -gt 100 返回 true"
else
echo "${x} -lt 100 && ${y} -gt 100 返回 false"
fi
if [[ ${x} -lt 100 || ${y} -gt 100 ]]
then
echo "${x} -lt 100 || ${y} -gt 100 返回 true"
else
echo "${x} -lt 100 || ${y} -gt 100 返回 false"
fi
# Execute: ./operator-demo4.sh
# Output:
# x=10, y=20
# 10 -lt 100 && 20 -gt 100 返回 false
# 10 -lt 100 || 20 -gt 100 返回 true
複製代碼
下表列出了經常使用的字符串運算符,假定變量 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。 |
:keyboard: 『示例源碼』 operator-demo5.sh
x="abc"
y="xyz"
echo "x=${x}, y=${y}"
if [[ ${x} = ${y} ]]; then
echo "${x} = ${y} : x 等於 y"
else
echo "${x} = ${y}: x 不等於 y"
fi
if [[ ${x} != ${y} ]]; then
echo "${x} != ${y} : x 不等於 y"
else
echo "${x} != ${y}: x 等於 y"
fi
if [[ -z ${x} ]]; then
echo "-z ${x} : 字符串長度爲 0"
else
echo "-z ${x} : 字符串長度不爲 0"
fi
if [[ -n "${x}" ]]; then
echo "-n ${x} : 字符串長度不爲 0"
else
echo "-n ${x} : 字符串長度爲 0"
fi
if [[ ${x} ]]; then
echo "${x} : 字符串不爲空"
else
echo "${x} : 字符串爲空"
fi
# Execute: ./operator-demo5.sh
# Output:
# x=abc, y=xyz
# abc = xyz: x 不等於 y
# abc != xyz : x 不等於 y
# -z abc : 字符串長度不爲 0
# -n abc : 字符串長度不爲 0
# abc : 字符串不爲空
複製代碼
文件測試運算符用於檢測 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 | 檢測文件是否可執行,若是是,則返回 true。 | [ -x $file ] 返回 true。 |
-s file | 檢測文件是否爲空(文件大小是否大於 0),不爲空返回 true。 | [ -s $file ] 返回 true。 |
-e file | 檢測文件(包括目錄)是否存在,若是是,則返回 true。 | [ -e $file ] 返回 true。 |
:keyboard: 『示例源碼』 operator-demo6.sh
file="/etc/hosts"
if [[ -r ${file} ]]; then
echo "${file} 文件可讀"
else
echo "${file} 文件不可讀"
fi
if [[ -w ${file} ]]; then
echo "${file} 文件可寫"
else
echo "${file} 文件不可寫"
fi
if [[ -x ${file} ]]; then
echo "${file} 文件可執行"
else
echo "${file} 文件不可執行"
fi
if [[ -f ${file} ]]; then
echo "${file} 文件爲普通文件"
else
echo "${file} 文件爲特殊文件"
fi
if [[ -d ${file} ]]; then
echo "${file} 文件是個目錄"
else
echo "${file} 文件不是個目錄"
fi
if [[ -s ${file} ]]; then
echo "${file} 文件不爲空"
else
echo "${file} 文件爲空"
fi
if [[ -e ${file} ]]; then
echo "${file} 文件存在"
else
echo "${file} 文件不存在"
fi
# Execute: ./operator-demo6.sh
# Output:(根據文件的實際狀況,輸出結果可能不一樣)
# /etc/hosts 文件可讀
# /etc/hosts 文件可寫
# /etc/hosts 文件不可執行
# /etc/hosts 文件爲普通文件
# /etc/hosts 文件不是個目錄
# /etc/hosts 文件不爲空
# /etc/hosts 文件存在
複製代碼
跟其它程序設計語言同樣,Bash 中的條件語句讓咱們能夠決定一個操做是否被執行。結果取決於一個包在[[ ]]
裏的表達式。
由[[ ]]
(sh
中是[ ]
)包起來的表達式被稱做 檢測命令 或 基元。這些表達式幫助咱們檢測一個條件的結果。這裏能夠找到有關bash 中單雙中括號區別的答案。
共有兩個不一樣的條件表達式:if
和case
。
if
(1)if
語句
if
在使用上跟其它語言相同。若是中括號裏的表達式爲真,那麼then
和fi
之間的代碼會被執行。fi
標誌着條件代碼塊的結束。
# 寫成一行
if [[ 1 -eq 1 ]]; then echo "1 -eq 1 result is: true"; fi
# Output: 1 -eq 1 result is: true
# 寫成多行
if [[ "abc" -eq "abc" ]]
then
echo ""abc" -eq "abc" result is: true"
fi
# Output: abc -eq abc result is: true
複製代碼
(2)if else
語句
一樣,咱們可使用if..else
語句,例如:
if [[ 2 -ne 1 ]]; then
echo "true"
else
echo "false"
fi
# Output: true
複製代碼
(3)if elif else
語句
有些時候,if..else
不能知足咱們的要求。別忘了if..elif..else
,使用起來也很方便。
x=10
y=20
if [[ ${x} > ${y} ]]; then
echo "${x} > ${y}"
elif [[ ${x} < ${y} ]]; then
echo "${x} < ${y}"
else
echo "${x} = ${y}"
fi
# Output: 10 < 20
複製代碼
:keyboard: 『示例源碼』 if-demo.sh
case
若是你須要面對不少狀況,分別要採起不一樣的措施,那麼使用case
會比嵌套的if
更有用。使用case
來解決複雜的條件判斷,看起來像下面這樣:
:keyboard: 『示例源碼』 case-demo.sh
exec
case ${oper} in
"+")
val=`expr ${x} + ${y}`
echo "${x} + ${y} = ${val}"
;;
"-")
val=`expr ${x} - ${y}`
echo "${x} - ${y} = ${val}"
;;
"*")
val=`expr ${x} \* ${y}`
echo "${x} * ${y} = ${val}"
;;
"/")
val=`expr ${x} / ${y}`
echo "${x} / ${y} = ${val}"
;;
*)
echo "Unknown oper!"
;;
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
文件夾中,並給它們可執行權限,咱們的腳本能夠這樣寫:
DIR=/home/zp
for FILE in ${DIR}/*.sh; do
mv "$FILE" "${DIR}/scripts"
done
# 將 /home/zp 目錄下全部 sh 文件拷貝到 /home/zp/scripts
複製代碼
:keyboard: 『示例源碼』 for-demo.sh
while
循環while
循環檢測一個條件,只要這個條件爲 真,就執行一段命令。被檢測的條件跟if..then
中使用的基元並沒有二異。所以一個while
循環看起來會是這樣:
while [[ condition ]]
do
### 語句
done
複製代碼
跟for
循環同樣,若是咱們把do
和被檢測的條件寫到一行,那麼必需要在do
以前加一個分號。
好比下面這個例子:
### 0到9之間每一個數的平方
x=0
while [[ ${x} -lt 10 ]]; do
echo $((x * x))
x=$((x + 1))
done
# Output:
# 0
# 1
# 4
# 9
# 16
# 25
# 36
# 49
# 64
# 81
複製代碼
:keyboard: 『示例源碼』 while-demo.sh
until
循環until
循環跟while
循環正好相反。它跟while
同樣也須要檢測一個測試條件,但不一樣的是,只要該條件爲 假 就一直執行循環:
x=0
until [[ ${x} -ge 5 ]]; do
echo ${x}
x=`expr ${x} + 1`
done
# Output:
# 0
# 1
# 2
# 3
# 4
複製代碼
:keyboard: 『示例源碼』 until-demo.sh
select
循環select
循環幫助咱們組織一個用戶菜單。它的語法幾乎跟for
循環一致:
select answer in elem1 elem2 ... elemN
do
### 語句
done
複製代碼
select
會打印elem1..elemN
以及它們的序列號到屏幕上,以後會提示用戶輸入。一般看到的是$?
(PS3
變量)。用戶的選擇結果會被保存到answer
中。若是answer
是一個在1..N
之間的數字,那麼語句
會被執行,緊接着會進行下一次迭代 —— 若是不想這樣的話咱們可使用break
語句。
一個可能的實例可能會是這樣:
#!/usr/bin/env 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: gitbook-cli
複製代碼
:keyboard: 『示例源碼』 select-demo.sh
break
和 continue
若是想提早結束一個循環或跳過某次循環執行,可使用 shell 的break
和continue
語句來實現。它們能夠在任何循環中使用。
break
語句用來提早結束當前循環。
continue
語句用來跳過某次迭代。
:keyboard: 『示例源碼』 break-demo.sh
# 查找 10 之內第一個能整除 2 和 3 的正整數
i=1
while [[ ${i} -lt 10 ]]; do
if [[ $((i % 3)) -eq 0 ]] && [[ $((i % 2)) -eq 0 ]]; then
echo ${i}
break;
fi
i=`expr ${i} + 1`
done
# Output: 6
複製代碼
:keyboard: 『示例源碼』 continue-demo.sh
# 打印10之內的奇數
for (( i = 0; i < 10; i ++ )); do
if [[ $((i % 2)) -eq 0 ]]; then
continue;
fi
echo ${i}
done
# Output:
# 1
# 3
# 5
# 7
# 9
複製代碼
bash 函數定義語法以下:
[ function ] funname [()] {
action;
[return int;]
}
複製代碼
:bulb: 說明:
- 函數定義時,
function
關鍵字無關緊要。- 函數返回值 - return 返回函數返回值,返回值類型只能爲整數(0-255)。若是不加 return 語句,shell 默認將以最後一條命令的運行結果,做爲函數返回值。
- 函數返回值在調用該函數後經過
$?
來得到。- 全部函數在使用前必須定義。這意味着必須將函數放在腳本開始部分,直至 shell 解釋器首次發現它時,纔可使用。調用函數僅使用其函數名便可。
:keyboard: 『示例源碼』 function-demo.sh
#!/usr/bin/env bash
calc(){
PS3="choose the oper: "
select oper in + - \* / # 生成操做符選擇菜單
do
echo -n "enter first num: " && read x # 讀取輸入參數
echo -n "enter second num: " && read y # 讀取輸入參數
exec
case ${oper} in
"+")
return $((${x} + ${y}))
;;
"-")
return $((${x} - ${y}))
;;
"*")
return $((${x} * ${y}))
;;
"/")
return $((${x} / ${y}))
;;
*)
echo "${oper} is not support!"
return 0
;;
esac
break
done
}
calc
echo "the result is: $?" # $? 獲取 calc 函數返回值
複製代碼
執行結果:
$ ./function-demo.sh
1) +
2) -
3) *
4) /
choose the oper: 3
enter first num: 10
enter second num: 10
the result is: 100
複製代碼
位置參數是在調用一個函數並傳給它參數時建立的變量。
位置參數變量表:
變量 | 描述 |
---|---|
$0 |
腳本名稱 |
$1 … $9 |
第 1 個到第 9 個參數列表 |
${10} … ${N} |
第 10 個到 N 個參數列表 |
$* or $@ |
除了$0 外的全部位置參數 |
$# |
不包括$0 在內的位置參數的個數 |
$FUNCNAME |
函數名稱(僅在函數內部有值) |
:keyboard: 『示例源碼』 function-demo2.sh
#!/usr/bin/env bash
x=0
if [[ -n $1 ]]; then
echo "第一個參數爲:$1"
x=$1
else
echo "第一個參數爲空"
fi
y=0
if [[ -n $2 ]]; then
echo "第二個參數爲:$2"
y=$2
else
echo "第二個參數爲空"
fi
paramsFunction(){
echo "函數第一個入參:$1"
echo "函數第二個入參:$2"
}
paramsFunction ${x} ${y}
複製代碼
執行結果:
$ ./function-demo2.sh
第一個參數爲空
第二個參數爲空
函數第一個入參:0
函數第二個入參:0
$ ./function-demo2.sh 10 20
第一個參數爲:10
第二個參數爲:20
函數第一個入參:10
函數第二個入參:20
複製代碼
執行 ./variable-demo4.sh hello world
,而後在腳本中經過 $1
、$2
... 讀取第 1 個參數、第 2 個參數。。。
另外,還有幾個特殊字符用來處理參數:
參數處理 | 說明 |
---|---|
$# |
返回參數個數 |
$* |
返回全部參數 |
$$ |
腳本運行的當前進程 ID 號 |
$! |
後臺運行的最後一個進程的 ID 號 |
$@ |
返回全部參數 |
$- |
返回 Shell 使用的當前選項,與 set 命令功能相同。 |
$? |
函數返回值 |
:keyboard: 『示例源碼』 function-demo3.sh
runner() {
return 0
}
name=zp
paramsFunction(){
echo "函數第一個入參:$1"
echo "函數第二個入參:$2"
echo "傳遞到腳本的參數個數:$#"
echo "全部參數:"
printf "+ %s\n" "$*"
echo "腳本運行的當前進程 ID 號:$$"
echo "後臺運行的最後一個進程的 ID 號:$!"
echo "全部參數:"
printf "+ %s\n" "$@"
echo "Shell 使用的當前選項:$-"
runner
echo "runner 函數的返回值:$?"
}
paramsFunction 1 "abc" "hello, \"zp\""
# Output:
# 函數第一個入參:1
# 函數第二個入參:abc
# 傳遞到腳本的參數個數:3
# 全部參數:
# + 1 abc hello, "zp"
# 腳本運行的當前進程 ID 號:26400
# 後臺運行的最後一個進程的 ID 號:
# 全部參數:
# + 1
# + abc
# + hello, "zp"
# Shell 使用的當前選項:hB
# runner 函數的返回值:0
複製代碼
擴展 發生在一行命令被分紅一個個的 記號(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 |
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
複製代碼
shell 提供了用於 debug 腳本的工具。
若是想採用 debug 模式運行某腳本,能夠在其 shebang 中使用一個特殊的選項:
#!/bin/bash options
複製代碼
options 是一些能夠改變 shell 行爲的選項。下表是一些可能對你有用的選項:
Short | Name | Description |
---|---|---|
-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
命令會很方便。這個命令能夠啓用或禁用選項。使用-
啓用選項,+
禁用選項:
:keyboard: 『示例源碼』 debug-demo.sh
# 開啓 debug
set -x
for (( i = 0; i < 3; i++ )); do
printf ${i}
done
# 關閉 debug
set +x
# Output:
# + (( i = 0 ))
# + (( i < 3 ))
# + printf 0
# 0+ (( i++ ))
# + (( i < 3 ))
# + printf 1
# 1+ (( i++ ))
# + (( i < 3 ))
# + printf 2
# 2+ (( i++ ))
# + (( i < 3 ))
# + set +x
for i in {1..5}; do printf ${i}; done
printf "\n"
# Output: 12345
複製代碼
:notebook: 本文已歸檔到:「blog」
最後,Stack Overflow 上 bash 標籤下有不少你能夠學習的問題,當你遇到問題時,也是一個提問的好地方。