一. 常識
bash 指令碼基本規則:
執行時展開變數,得到命令和參數字串,執行
一般流程:
# 1.创建/编辑`.sh`文件(道德约束)
vim test.sh
# 2.添可执行权限
chmod +x test.sh
# 3.执行
./test.sh
簡單示例:
#!/bin/bash
# 声明变量
str='hoho'
# 输出变量值
echo $str
其中,第一行 #!/bin/bash 說明直譯器所在路徑,可以透過 which bash 查看
P.S. #! 叫 shebang(釋伴,就這麼翻譯,也這麼讀),更多資訊請查看 釋伴:Linux 上的 Shebang 符號(#!)
二. 變數
1. 環境變數
HOME # 当前用户目录的绝对路径
USER # 当前用户名
PWD # 当前工作目录
# ...
# 更多变量用`env`命令查看
不用宣告,直接使用,類似於 node 裡的 __dirname 和 __filename
建立環境變數的 3 種方式:
-
在
bashrc檔案(系統級的/etc/bashrc和使用者級的~/.bashrc)中添加永久環境變數(每個新建立的 shell 都擁有) -
在執行指令碼時設置臨時環境變數(僅在執行指令碼的子 shell 內有效)
-
export環境變數(只對後續建立的子 shell 有效)
例如:
# 方式1
# 如果是zsh,对应文件名为`~/.zshrc`
echo _ENV=product >> ~/.bashrc
source ~/.bashrc
echo $_ENV
# 方式2
_ENV=product ./test.sh
# 在./test.sh中可以读到_ENV
echo $_ENV
# 方式3
_ENV=product; export _ENV
# 新开一个shell
bash
echo $_ENV
2. 全域變數與區域變數
#!/bin/bash
# 默认声明全局变量
VAR="global variable"
function fn() {
VAR="updated global variable"
# 只能在function里通过local关键字声明局部变量
local VAR="local variable"
echo $VAR
}
fn
echo $VAR
# 输出
local variable
updated global variable
即用即宣告,預設形式的都是全域變數,只能在函式內部透過 local 關鍵字宣告區域變數。還需要注意:
-
等號兩邊不能有空格,因為每一行會被當作「命令 空格 參數」
-
引號不是必須的,與 CSS 一樣,內容包含空格時引號才有必要
-
沒有提升(hoisting)一說,區域變數作用域是從變數宣告位置到函式體結束,全域變數作用域是從宣告位置到檔案結束
3. 存取變數值
$變數名 取變數值,如 $VAR
變數插值規則:在雙引號中引用的變數會被展開(expanded),單引號中的不會,與 PHP 一樣
{} 可以隔離變數名,把變數名保護起來:
${VAR}abc # VAR的值后面紧跟着字符串abc
存取陣列元素時必須這樣做,例如:
arr=(aa b ccc)
# 输出aa[1],不符合预期
# 因为用$对arr取值,得到aa($arr返回首元),再给后面接上字符串[1]
echo $arr[1]
# 输出b
echo ${arr[1]}
三. 分支與迴圈
1. 條件語句
if 条件 # test命令和[]操作符
then
语句...
else # else if写作elif
语句...
fi
條件部分一般是 test 命令或者 [] 運算子,例如:
if [ $X -lt $Y ]; # X小于Y
if [ -n $X ]; # 变量非空(字符串长度不为0)
if [ -e $path ]; # 文件存在
# 数值比较
if test 2 -gt 1; then echo "number 2 > 1"; fi
# 等价于
if [ 2 -gt 1 ]; then echo "number 2 > 1"; fi
# 字符串比较
if test 2 > 11; then echo "string 2 > 11"; fi
# 等价于
if [ 2 > 11 ]; then echo "string 2 > 11"; fi
其中有 3 個細節,運算元類型、分號和空格:
-
-gt表示比較數值大於,>表示比較字串大於,運算子運算時會自動轉換,無法轉換就報錯 -
;在單行語句中用來區分塊結構,第一個分號表示條件部分結束,第二個分號表示then部分結束,缺一不可 -
空格用來分隔命令和參數(除了空格,預設分隔符還有定位字元和換行,見下面 IFS)
P.S. 字串轉數值常用方式有 ((str)) 和 `expr str`、$(expr str),前者是 bash 運算子,後兩個是外部命令
空格示例:
# 空格很关键
if [ 1=2 ]; # 把1=2整体当操作数(字符串)了,没看见操作符
# []里两端的空格也很关键
if [-e $path]; then # 报错[-e命令找不着,因为被看成了'[-e' '$path]'
可以透過 man test 命令查看其它 test 運算子
bash 也提供了類似於 switch 的東西,只是語法很奇怪:
case $variable in
pattern1)
command...
;; # break
pattern2|pattern3)
command...
;;
patternN)
command...
;;
*) # default case
command...
esac
2. 迴圈語句
有 3 種迴圈:for、while 和 until,語法規則如下:
# for循环
for f in $( ls /var/ ); do
echo $f
done
# 或者单行的(分号区分结构块)
for f in $( ls /var/ ); do echo $f; done
# while循环
times=6
while [ $times -gt 0 ]; do
echo Value of times is: $times
let times=times-1
done
# 单行形式
times=6; while [ $times -gt 0 ]; do echo Value of times is: $times; let times=times-1; done
# until循环
times=0
until [ $times -gt 5 ]; do
echo Value of times is: $times
let times=times+1
done
除了 for...in,還有 C 風格的:
arr=(1 '2 3' 4)
len=3
for (( i=0; i<$len; i++)); do
echo ${arr[${i}]}
done
迴圈的基本規則:
迴圈會遍歷由 IFS(' '、'\t'、'\n')分開的項目
IFS(Internal Field Seprator),內部欄位分隔符,預設是空格、tab 和換行,所以注意這種情況:
for f in $( ls -l /var/ ); do echo $f; done
輸出的結果不符合預期:
total
0
drwx------
2
root
wheel
68
8
23
2015
agentx
本來應該是這樣:
total 0
drwx------ 2 root wheel 68 8 23 2015 agentx
要迴圈讀整行的話,需要修改 IFS:
# 限制分隔符只认换行
IFS=$'\n'; for f in $( ls -l /var/ ); do echo $f; done
另外,迴圈通常配合萬用字元 * 使用,例如:
# 通配符
echo * # 当前目录下所有文件/文件夹名,空格分隔
echo *.html # 当前目录下所有html格式文件
# 找test目录下所有html文件
for htmlFile in `echo ~/Documents/projs/test/*.html`; do echo $htmlFile; done
# 找test目录下所有html文件,包括子孙目录
for htmlFile in `echo ~/Documents/projs/test/**/*.html`; do echo $htmlFile; done
迴圈+萬用字元 操作目錄檔案非常方便
四. 函式
1. 函式宣告
function function_name {
command...
}
# 或者
function_name () {
command...
}
省略 function 關鍵字的話,函式名稱後面必須要有 (),否則就被當作命令了,例如:
# 报错,parse error near `}'
fn {echo fn}; fn
不省略 function 關鍵字的話,函式名稱後面有沒有 () 無所謂
函式宣告順序不很嚴格,但要保證先宣告後呼叫,例如:
fn1() {echo fn1:`fn2 $1`}
fn2() {echo fn2:$1}
fn1 hoho
# 输出
# fn1:fn2:hoho
如果在宣告 fn2 之前就呼叫 fn1,就會報錯找不到 fn2,如下:
# 报错command not found: fn2
fn1() {echo fn1:`fn2 $1`}; fn1 hoho; fn2() {echo fn2:$1}
2. 呼叫與傳參
參數透過位置變數獲得,不顯式宣告形參:
# 声明函数
fn() {echo $0$1}
# 无参调用
fn
# 传入一个字符串参数hoho
fn hoho
函式作用域內,提供了一些位置變數(都是唯讀的):
$0 # 函数名(不算参数,因为$*和$@不包含$0,$#也不计$0)
$n # 第n个参数,参数从1开始
$* # 由所有参数拼成的字符串,用空格分隔
$@ # 同上,区别是每个参数会被双引号保护起来
$# # 参数个数
P.S. 注意 $10 不是 ${10},前者是 $1 後面跟個字串 0,後者是第 10 個參數的值
另外,這些位置變數也適用於透過命令列向指令碼傳參,例如:
# sub.sh
echo $1-$2=`expr $1 - $2`
# 命令行执行
./sum.sh 1 2
# 输出
# 1-2=-1
$* 與 $@ 的區別很重要,簡單理解:
$*=$1 $2 $3...
$@="$1" "$2" "$3"...
示例:
# 没有区别
fn1() {for arg in $*; do echo line:$arg; done}; fn1 "a" "b c" "d"
fn2() {for arg in $@; do echo line:$arg; done}; fn2 "a" "b c" "d"
# 输出
# line:a
# line:b c
# line:d
# 用双引号包起来时能发现区别
fn1() {for arg in "$*"; do echo line:$arg; done}; fn1 "a" "b c" "d"
# 输出
# line:a
# b c
# d
fn2() {for arg in "$@"; do echo line:$arg; done}; fn2 "a" "b c" "d"
# 输出
# line:a
# line:b c
# line:d
用雙引號包起來時,迴圈次數有差異,$* 只迴圈一次,$@ 迴圈 3 次,所以一般建議使用 $@
3. 回傳值
3 種方式都不好用,沒事就不要回傳了(直接修改外部變數),非要回傳值的話,建議用子 shell 執行,echo 傳回的方式,但注意事項(見程式碼註釋)也很麻煩,示例如下:
# 1.return
fn() {
return -2
}
fn
# 取出上一条命令的返回值,0表示正常,非0不正常
# 实际输出是254,超出范围的会被框进来,256会变成0,-1变成255
echo $?
# 缺点:return表示函数执行状态,只能返回[0, 255]的整数,无法return字符串
# 其次`$?`必须紧跟在函数调用后面
# 2.子shell执行,echo传回
fn() {
echo -2
# 避免错误信息进入标准输出,直接丢掉
cat xxx 2> /dev/null
}
echo $(fn)
# 缺点:给标准输出的结果可能不干净
# (函数体不止一条`echo`语句,或者有`print`、`printf`之类的也输出到标准输出的语句)
# 另外,如果函数执行过程中出错了,错误信息也会混进去(虽然可以避免,但比较麻烦)
# 3.所谓的传引用
fn() {
# 约定第一个参数传入的字符串是返回变量名
local res=$1
# 计算1+2,再按照返回变量名创建全局变量,带回结果
eval $res=$(($2 + $3))
}
fn result 1 2
echo $result
# 缺点:其实就是全局变量传值,只是全局变量名动态传入,没有在函数里写死而已
五. 陣列
1. 宣告與賦值
# 空数组
arr=()
# 字符串数组,空格分隔元素,不用逗号
arr=(1 2 3 'we together')
# 直接赋值,没有就新增一个
arr[0]=4
arr[6]='sixth'
陣列索引從 0 開始,賦值時不需要保證連續,沒有的話會新增一個
2. 遍歷
for 迴圈不用知道陣列長度:
arr=(1 2 3 '4 5')
for i in "${arr[@]}"; do echo $i; done
特別注意 "${arr[@]}",與函式裡的位置變數類似,$* 與 $@ 的迴圈次數不同:
for i in "${arr[@]}"; do echo $i; done
# 输出
# 1
# 2
# 3
# 4 5
for i in "${arr[*]}"; do echo $i; done
# 输出
# 1 2 3 4 5
while 和 until 需要知道陣列長度:
# 取数组长度
len=${#arr[@]}
i=1
while [ $i -lt $len ]; do echo $arr[$i]; i=$((i+1)); done
# 或者until
until [ $len -lt 1 ]; do len=$((len-1)); echo ${arr[$len]}; done
注意:${#str} 是取字串長度,與 ${#arr[@]} 取陣列長度很像,容易弄錯
六. 命令替換
命令替換是指在 bash 指令碼中執行 shell 命令,並得到其輸出結果(差不多是這意思,沒有找到嚴格定義)
直接執行的話,結果會被輸出到標準輸出(螢幕),想把結果取出來的話,就需要用到命令替換:
# 直接执行 屏幕输出了ls命令的结果
ls
# 命令替换 屏幕不输出结果,由自定义变量记下
lsResult=`ls`
# 看起来像是丢弃结果(不输出也不记)
# 实际上可能会报错,`ls`命令返回结果字符串,会被当作命令继续执行
`ls`
命令替換常用的方式有 2 種,反撇號擴充和圓括號擴充,例如:
# 反撇号扩展,不允许嵌套
files=`ls`
# 圆括号扩展,允许嵌套
files="$(ls)"
# 嵌套示例
# 当前目录下文件及文件夹数量+1
$(expr ${#$(ls)[@]} + 1)
有趣 的一點:兩種命令替換方式都是新建 shell(也就是在子 shell 中)執行命令,不會對目前 shell 產生任何影響,所以可以方便的隔離操作環境,例如:
# 去新环境执行cd
lsParent="$(cd ../; ls)"
# 执行后pwd不变,不用再cd回来
到這裡基本足夠完成稍複雜的(有用的)bash 指令碼了
暫無評論,快來發表你的看法吧