文章目錄
- Linux-Shell
-
- 概論
- 注釋
- 變量
- 預設變量
- 數組
- expr指令
- read指令
- echo指令
- printf指令
- test指令與判斷符号[]
- 判斷語句
- 循環語句
- 函數
- exit指令
- 檔案重定向
- 引入外部腳本
Linux-Shell
概論
概況
shell是我們通過指令行與作業系統溝通的語言。
shell腳本可以直接在指令行中執行,也可以将一套邏輯組織成一個檔案,友善複用。
AC Terminal中的指令行可以看成是一個“shell腳本在逐行執行”。
Linux中常見的shell腳本有很多種,常見的有:
- Bourne Shell(
或/usr/bin/sh
)/bin/sh
- Bourne Again Shell(
)/bin/bash
- C Shell(
)/usr/bin/csh
- K Shell(
)/usr/bin/ksh
- zsh
- …
Linux系統中一般預設使用bash,是以接下來講解bash中的文法。
檔案開頭需要寫
#! /bin/bash
,指明bash為腳本解釋器。
學習技巧
不要死記硬背,遇到含糊不清的地方,可以在AC Terminal裡實際運作一遍
腳本示例
建立一個
test.sh
檔案,内容如下:
#! /bin/bash
echo "Hello World!"
運作方式
作為可執行檔案
[email protected]:~$ chmod +x test.sh # 使腳本具有可執行權限
[email protected]:~$ ./test.sh # 目前路徑下執行
Hello World! # 腳本輸出
[email protected]:~$ /home/acs/test.sh # 絕對路徑下執行
Hello World! # 腳本輸出
[email protected]:~$ ~/test.sh # 家目錄路徑下執行
Hello World! # 腳本輸出
用解釋器執行
[email protected]:~$ bash test.sh
Hello World! # 腳本輸出
注釋
單行注釋
每行中
#
之後的内容均是注釋。
# 這是一行注釋
echo 'Hello World' # 這也是注釋
多行注釋
格式:
:<<EOF
第一行注釋
第二行注釋
第三行注釋
EOF
其中
EOF
可以換成其它任意字元串。例如:
:<<abc
第一行注釋
第二行注釋
第三行注釋
abc
:<<!
第一行注釋
第二行注釋
第三行注釋
!
變量
定義變量
定義變量,不需要加
$
符号,例如:
name1='yxc' # 單引号定義字元串
name2="yxc" # 雙引号定義字元串
name3=yxc # 也可以不加引号,同樣表示字元串
使用變量
使用變量,需要加上
$
符号,或者
${}
符号。花括号是可選的,主要為了幫助解釋器識别變量邊界。
name=yxc
echo $name # 輸出yxc
echo ${name} # 輸出yxc
echo ${name}acwing # 輸出yxcacwing
隻讀變量
使用
readonly
或者
declare
可以将變量變為隻讀。
name=yxc
readonly name
declare -r name # 兩種寫法均可
name=abc # 會報錯,因為此時name隻讀
删除變量
unset
可以删除變量。
name=yxc
unset name
echo $name # 輸出空行
變量類型
-
自定義變量(局部變量)
子程序不能通路的變量
-
環境變量(全局變量)
子程序可以通路的變量
自定義變量改成環境變量:
[email protected]:~$ name=yxc # 定義變量
[email protected]:~$ export name # 第一種方法
[email protected]:~$ declare -x name # 第二種方法
環境變量改為自定義變量:
[email protected]:~$ export name=yxc # 定義環境變量
[email protected]:~$ declare +x name # 改為自定義變量
字元串
字元串可以用單引号,也可以用雙引号,也可以不用引号。
單引号與雙引号的差別:
- 單引号中的内容會原樣輸出,不會執行、不會取變量;
- 雙引号中的内容可以執行、可以取變量;
name=yxc # 不用引号
echo 'hello, $name \"hh\"' # 單引号字元串,輸出 hello, $name \"hh\"
echo "hello, $name \"hh\"" # 雙引号字元串,輸出 hello, yxc "hh"
擷取字元串長度
name="yxc"
echo ${#name} # 輸出3
提取子串
name="hello, yxc"
echo ${name:0:5} # 提取從0開始的5個字元
預設變量
檔案參數變量
在執行shell腳本時,可以向腳本傳遞參數。
$1
是第一個參數,
$2
是第二個參數,以此類推。特殊的,
$0
是檔案名(包含路徑)。例如:
建立檔案
test.sh
:
#! /bin/bash
echo "檔案名:"$0
echo "第一個參數:"$1
echo "第二個參數:"$2
echo "第三個參數:"$3
echo "第四個參數:"$4
然後執行該腳本:
[email protected]:~$ chmod +x test.sh
[email protected]:~$ ./test.sh 1 2 3 4
檔案名:./test.sh
第一個參數:1
第二個參數:2
第三個參數:3
第四個參數:4
其它參數相關變量
參數 | 作用 |
---|---|
| 代表檔案傳入的參數個數,如上例中值為4 |
| 由所有參數構成的用空格隔開的字元串,如上例中值為 |
| 每個參數分别用雙引号括起來的字元串,如上例中值為 |
| 腳本目前運作的程序ID |
| 上一條指令的退出狀态(注意不是stdout,而是exit code)。0表示正常退出,其他值表示錯誤 |
| 傳回 這條指令的stdout(可嵌套) |
| 傳回 這條指令的stdout(不可嵌套) |
數組
數組中可以存放多個不同類型的值,隻支援一維數組,初始化時不需要指明數組大小。
數組下标從0開始。
定義
數組用小括号表示,元素之間用空格隔開。例如:
也可以直接定義數組中某個元素的值:
array[0]=1
array[1]=abc
array[2]="def"
array[3]=yxc
讀取數組中某個元素的值
格式:
例如:
array=(1 abc "def" yxc)
echo ${array[0]}
echo ${array[1]}
echo ${array[2]}
echo ${array[3]}
讀取整個數組
格式:
${array[@]} # 第一種寫法
${array[*]} # 第二種寫法
例如:
array=(1 abc "def" yxc)
echo ${array[@]} # 第一種寫法
echo ${array[*]} # 第二種寫法
數組長度
類似于字元串
${#array[@]} # 第一種寫法
${#array[*]} # 第二種寫法
例如:
array=(1 abc "def" yxc)
echo ${#array[@]} # 第一種寫法
echo ${#array[*]} # 第二種寫法
expr指令
expr
指令使用者求表達式的值,格式為:
expr 表達式
表達式說明:
- 用空格隔開每一項
- 用反斜杠放在shell特定的字元前面(發現表達式運作錯誤時,可以試試轉義)
- 對包含空格和其他特殊字元的字元串要用引号括起來
- expr會在
中輸出結果。如果為邏輯關系表達式,則結果為真,stdout
為1,否則為0。stdout
- expr的
:如果為邏輯關系表達式,則結果為真,exit code
為0,否則為1。exit code
字元串表達式
-
傳回length STRING
的長度STRING
-
index STRING CHARSET
中任意單個字元在CHARSET
中最前面的字元位置,下标從1開始。如果在STRING
中完全不存在STRING
字元,則傳回0。CHARSET中的
-
傳回substr STRING POSITION LENGTH
字元串中從STRING
開始,長度最大為POSITION
的子串。如果LENGTH
或POSITION
為負數,0或非數值,則傳回空字元串。LENGTH
示例:
str="Hello World!"
echo `expr length "$str"` # ``不是單引号,表示執行該指令,輸出12
echo `expr index "$str" aWd` # 輸出7,下标從1開始
echo `expr substr "$str" 2 3` # 輸出 ell
整數表達式
expr
支援普通的算術操作,算術表達式優先級低于字元串表達式,高于邏輯關系表達式。
-
加減運算。兩端參數會轉換為整數,如果轉換失敗則報錯。+ -
-
乘,除,取模運算。兩端參數會轉換為整數,如果轉換失敗則報錯。* / %
-
可以該表優先級,但需要用反斜杠轉義()
示例:
a=3
b=4
echo `expr $a + $b` # 輸出7
echo `expr $a - $b` # 輸出-1
echo `expr $a \* $b` # 輸出12,*需要轉義
echo `expr $a / $b` # 輸出0,整除
echo `expr $a % $b` # 輸出3
echo `expr \( $a + 1 \) \* \( $b + 1 \)` # 輸出20,值為(a + 1) * (b + 1)
邏輯關系表達式
-
如果第一個參數非空且非0,則傳回第一個參數的值,否則傳回第二個參數的值,但要求第二個參數的值也是非空或非0,否則傳回0。如果第一個參數是非空或非0時,不會計算第二個參數。|
-
如果兩個參數都非空且非0,則傳回第一個參數,否則傳回0。如果第一個參為0或為空,則不會計算第二個參數。&
-
比較兩端的參數,如果為true,則傳回1,否則傳回0。”==”是”=”的同義詞。”expr”首先嘗試将兩端參數轉換為整數,并做算術比較,如果轉換失敗,則按字元集排序規則做字元比較。< <= = == != >= >
-
可以該表優先級,但需要用反斜杠轉義()
示例:
a=3
b=4
echo `expr $a \> $b` # 輸出0,>需要轉義
echo `expr $a '<' $b` # 輸出1,也可以将特殊字元用引号引起來
echo `expr $a '>=' $b` # 輸出0
echo `expr $a \<\= $b` # 輸出1
c=0
d=5
echo `expr $c \& $d` # 輸出0
echo `expr $a \& $b` # 輸出3
echo `expr $c \| $d` # 輸出5
echo `expr $a \| $b` # 輸出3
read指令
read
指令用于從标準輸入中讀取單行資料。當讀到檔案結束符時,
exit code
為1,否則為0。
參數說明
-
: 後面可以接提示資訊-p
-
:後面跟秒數,定義輸入字元的等待時間,超過等待時間後會自動忽略此指令-t
執行個體:
[email protected]:~$ read name # 讀入name的值
acwing yxc # 标準輸入
[email protected]:~$ echo $name # 輸出name的值
acwing yxc #标準輸出
[email protected]:~$ read -p "Please input your name: " -t 30 name # 讀入name的值,等待時間30秒
Please input your name: acwing yxc # 标準輸入
[email protected]:~$ echo $name # 輸出name的值
acwing yxc # 标準輸出
echo指令
echo
用于輸出字元串。指令格式:
echo STRING
顯示普通字元串
echo "Hello AC Terminal"
echo Hello AC Terminal # 引号可以省略
顯示轉義字元
echo "\"Hello AC Terminal\"" # 注意隻能使用雙引号,如果使用單引号,則不轉義
echo \"Hello AC Terminal\" # 也可以省略雙引号
顯示變量
name=yxc
echo "My name is $name" # 輸出 My name is yxc
顯示換行
echo -e "Hi\n" # -e 開啟轉義
echo "acwing"
輸出結果:
Hi
acwing
顯示不換行
echo -e "Hi \c" # -e 開啟轉義 \c 不換行
echo "acwing"
輸出結果:
Hi acwing
顯示結果定向至檔案
原樣輸出字元串,不進行轉義或取變量(用單引号)
name=acwing
echo '$name\"'
輸出結果
$name\"
顯示指令的執行結果
輸出結果:
printf指令
printf
指令用于格式化輸出,類似于
C/C++
中的
printf
函數。
預設不會在字元串末尾添加換行符。
指令格式:
用法示例
腳本内容:
printf "%10d.\n" 123 # 占10位,右對齊
printf "%-10.2f.\n" 123.123321 # 占10位,保留2位小數,左對齊
printf "My name is %s\n" "yxc" # 格式化輸出字元串
printf "%d * %d = %d\n" 2 3 `expr 2 \* 3` # 表達式的值作為參數
輸出結果:
123.
123.12 .
My name is yxc
2 * 3 = 6
test指令與判斷符号[]
邏輯運算符&&和||
-
表示與,&&
表示或||
- 二者具有短路原則:
:當expr1 && expr2
為假時,直接忽略expr1
expr2
:當expr1 || expr2
為真時,直接忽略expr1
expr2
- 表達式的
為0,表示真;為非零,表示假。(與exit code
中的定義相反)C/C++
test指令
在指令行中輸入
man test
,可以檢視
test
指令的用法。
test
指令用于判斷檔案類型,以及對變量做比較。
test
指令用
exit code
傳回結果,而不是使用
stdout
。0表示真,非0表示假。
例如:
test 2 -lt 3 # 為真,傳回值為0
echo $? # 輸出上個指令的傳回值,輸出0
[email protected]:~$ ls # 列出目前目錄下的所有檔案
homework output.txt test.sh tmp
[email protected]:~$ test -e test.sh && echo "exist" || echo "Not exist"
exist # test.sh 檔案存在
[email protected]:~$ test -e test2.sh && echo "exist" || echo "Not exist"
Not exist # testh2.sh 檔案不存在
檔案類型判斷
指令格式:
測試參數 | 代表意義 |
---|---|
-e | 檔案是否存在 |
-f | 是否為檔案 |
-d | 是否為目錄 |
檔案權限判斷
指令格式:
測試參數 | 代表意義 |
---|---|
-r | 檔案是否可讀 |
-w | 檔案是否可寫 |
-x | 檔案是否可執行 |
-s | 是否為非空檔案 |
整數間的比較
指令格式:
測試參數 | 代表意義 |
---|---|
-eq | a是否等于b |
-ne | a是否不等于b |
-gt | a是否大于b |
-lt | a是否小于b |
-ge | a是否大于等于b |
-le | a是否小于等于b |
字元串比較
測試參數 | 代表意義 |
---|---|
test -z STRING | 判斷STRING是否為空,如果為空,則傳回true |
test -n STRING | 判斷STRING是否非空,如果非空,則傳回true(-n可以省略) |
test str1 == str2 | 判斷str1是否等于str2 |
test str1 != str2 | 判斷str1是否不等于str2 |
多重條件判定
指令格式:
test -r filename -a -x filename
測試參數 | 代表意義 |
---|---|
-a | 兩條件是否同時成立 |
-o | 兩條件是否至少一個成立 |
! | 取反。如 test ! -x file,當file不可執行時,傳回true |
判斷符号[]
[]
與
test
用法幾乎一模一樣,更常用于
if
語句中。另外
[[]]
是
[]
的加強版,支援的特性更多。
例如:
[ 2 -lt 3 ] # 為真,傳回值為0
echo $? # 輸出上個指令的傳回值,輸出0
[email protected]:~$ ls # 列出目前目錄下的所有檔案
homework output.txt test.sh tmp
[email protected]:~$ [ -e test.sh ] && echo "exist" || echo "Not exist"
exist # test.sh 檔案存在
[email protected]:~$ [ -e test2.sh ] && echo "exist" || echo "Not exist"
Not exist # testh2.sh 檔案不存在
注意:
-
内的每一項都要用空格隔開[]
- 中括号内的變量,最好用雙引号括起來
- 中括号内的常數,最好用單或雙引号括起來
例如:
name="acwing yxc"
[ $name == "acwing yxc" ] # 錯誤,等價于 [ acwing yxc == "acwing yxc" ],參數太多
[ "$name" == "acwing yxc" ] # 正确
判斷語句
if…then形式
類似于
C/C++
中的
if-else
語句。
單層if
指令格式:
if condition
then
語句1
語句2
...
fi
示例:
a=3
b=4
if [ "$a" -lt "$b" ] && [ "$a" -gt 2 ]
then
echo ${a}在範圍内
fi
輸出結果:
3在範圍内
單層if-else
指令格式
if condition
then
語句1
語句2
...
else
語句1
語句2
...
fi
示例:
a=3
b=4
if ! [ "$a" -lt "$b" ]
then
echo ${a}不小于${b}
else
echo ${a}小于${b}
fi
輸出結果:
3小于4
多層if-elif-elif-else
指令格式
if condition
then
語句1
語句2
...
elif condition
then
語句1
語句2
...
elif condition
then
語句1
語句2
else
語句1
語句2
...
fi
示例:
a=4
if [ $a -eq 1 ]
then
echo ${a}等于1
elif [ $a -eq 2 ]
then
echo ${a}等于2
elif [ $a -eq 3 ]
then
echo ${a}等于3
else
echo 其他
fi
輸出結果:
其他
case…esac形式
類似于
C/C++
中的
switch
語句。
指令格式
case $變量名稱 in
值1)
語句1
語句2
...
;; # 類似于C/C++中的break
值2)
語句1
語句2
...
;;
*) # 類似于C/C++中的default
語句1
語句2
...
;;
esac
示例:
a=4
case $a in
1)
echo ${a}等于1
;;
2)
echo ${a}等于2
;;
3)
echo ${a}等于3
;;
*)
echo 其他
;;
esac
輸出結果:
其他
循環語句
for…in…do…done
指令格式:
for var in val1 val2 val3
do
語句1
語句2
...
done
示例1,輸出a 2 cc,每個元素一行:
for i in a 2 cc
do
echo $i
done
示例2,輸出目前路徑下的所有檔案名,每個檔案名一行:
for file in `ls`
do
echo $file
done
示例3,輸出1-10
for i in $(seq 1 10)
do
echo $i
done
示例4,使用{1…10} 或者 {a…z}
for i in {a..z}
do
echo $i
done
for ((…;…;…)) do…done
指令格式:
for ((expression; condition; expression))
do
語句1
語句2
done
示例,輸出1-10,每個數占一行:
for ((i=1; i<=10; i++))
do
echo $i
done
while…do…done循環
指令格式:
while condition
do
語句1
語句2
...
done
示例,檔案結束符為
Ctrl+d
,輸入檔案結束符後
read
指令傳回false。
while read name
do
echo $name
done
until…do…done循環
當條件為真時結束。
指令格式:
until condition
do
語句1
語句2
...
done
示例,當使用者輸入
yes
或者
YES
時結束,否則一直等待讀入。
until [ "${word}" == "yes" ] || [ "${word}" == "YES" ]
do
read -p "Please input yes/YES to stop this program: " word
done
break指令
跳出目前一層循環,注意與
C/C++
不同的是:
break
不能跳出case
語
句。
示例
while read name
do
for ((i=1;i<=10;i++))
do
case $i in
8)
break
;;
*)
echo $i
;;
esac
done
done
該示例每讀入非EOF的字元串,會輸出一遍1-7。
該程式可以輸入Ctrl+d檔案結束符來結束,也可以直接用Ctrl+c殺掉該程序。
continue指令
跳出目前循環。
示例:
for ((i=1;i<=10;i++))
do
if [ `expr $i % 2` -eq 0 ]
then
continue
fi
echo $i
done
該程式輸出1-10中的所有奇數。
死循環的處理方式
如果AC Terminal可以打開該程式,則輸入
Ctrl+c
即可。
否則可以直接關閉程序:
- 使用
指令找到程序的PIDtop
- 輸入
即可關掉此程序kill -9 PID
函數
bash
中的函數類似于
C/C++
中的函數,但
return
的傳回值與
C/C++
不同,傳回的是
exit code
,取值為0-255,0表示正常結束。
如果想擷取函數的輸出結果,可以通過
echo
輸出到
stdout
中,然後通過
$(function_name)
來擷取
stdout
中的結果。
函數的
return
值可以通過
$?
來擷取。
指令格式:
[function] func_name() { # function關鍵字可以省略
語句1
語句2
...
}
不擷取
return
值和
stdout
值
示例
func() {
name=yxc
echo "Hello $name"
}
func
輸出結果:
Hello yxc
擷取 return值和stdout值
不寫
return
時,預設
return 0
。
示例
func() {
name=yxc
echo "Hello $name"
return 123
}
output=$(func)
ret=$?
echo "output = $output"
echo "return = $ret"
輸出結果:
output = Hello yxc
return = 123
函數的輸入參數
在函數内,
$1
表示第一個輸入參數,
$2
表示第二個輸入參數,依此類推。
注意:函數内的
$0
仍然是檔案名,而不是函數名。
示例:
func() { # 遞歸計算 $1 + ($1 - 1) + ($1 - 2) + ... + 0
word=""
while [ "${word}" != 'y' ] && [ "${word}" != 'n' ]
do
read -p "要進入func($1)函數嗎?請輸入y/n:" word
done
if [ "$word" == 'n' ]
then
echo 0
return 0
fi
if [ $1 -le 0 ]
then
echo 0
return 0
fi
sum=$(func $(expr $1 - 1))
echo $(expr $sum + $1)
}
echo $(func 10)
輸出結果:
55
函數内的局部變量
可以在函數内定義局部變量,作用範圍僅在目前函數内。
可以在遞歸函數中定義局部變量。
指令格式:
local 變量名=變量值
例如:
#! /bin/bash
func() {
local name=yxc
echo $name
}
func
echo $name
輸出結果:
yxc
第一行為函數内的name變量,第二行為函數外調用name變量,會發現此時該變量不存在。
exit指令
exit
指令用來退出目前
shell
程序,并傳回一個退出狀态;使用
$?
可以接收這個退出狀态。
exit
指令可以接受一個整數值作為參數,代表退出狀态。如果不指定,預設狀态值是 0。
exit
退出狀态隻能是一個介于 0~255 之間的整數,其中隻有 0 表示成功,其它值都表示失敗。
示例:
建立腳本
test.sh
,内容如下:
#! /bin/bash
if [ $# -ne 1 ] # 如果傳入參數個數等于1,則正常退出;否則非正常退出。
then
echo "arguments not valid"
exit 1
else
echo "arguments valid"
exit 0
fi
執行該腳本:
[email protected]:~$ chmod +x test.sh
[email protected]:~$ ./test.sh acwing
arguments valid
[email protected]:~$ echo $? # 傳入一個參數,則正常退出,exit code為0
0
[email protected]:~$ ./test.sh
arguments not valid
[email protected]:~$ echo $? # 傳入參數個數不是1,則非正常退出,exit code為1
1
檔案重定向
每個程序預設打開3個檔案描述符:
-
标準輸入,從指令行讀取資料,檔案描述符為0stdin
-
标準輸出,向指令行輸出資料,檔案描述符為1stdout
-
标準錯誤輸出,向指令行輸出資料,檔案描述符為2stderr
可以用檔案重定向将這三個檔案重定向到其他檔案中。
重定向指令清單
指令 | 說明 |
---|---|
| 将 重定向到 中 |
| 将 重定向到 中 |
| 将 以追加方式重定向到 中 |
| 将檔案描述符 重定向到 中 |
| 将檔案描述符 以追加方式重定向到 中 |
輸入和輸出重定向
echo -e "Hello \c" > output.txt # 将stdout重定向到output.txt中
echo "World" >> output.txt # 将字元串追加到output.txt中
read str < output.txt # 從output.txt中讀取字元串
echo $str # 輸出結果:Hello World
同時重定向stdin和stdout
建立bash腳本:
#! /bin/bash
read a
read b
echo $(expr "$a" + "$b")
建立input.txt,裡面的内容為:
3
4
執行指令:
[email protected]:~$ chmod +x test.sh # 添加可執行權限
[email protected]:~$ ./test.sh < input.txt > output.txt # 從input.txt中讀取内容,将輸出寫入output.txt中
[email protected]:~$ cat output.txt # 檢視output.txt中的内容
7
引入外部腳本
類似于
C/C++
中的
include
操作,
bash
也可以引入其他檔案中的代碼。
文法格式:
. filename # 注意點和檔案名之間有一個空格
或
source filename
示例
建立
test1.sh
,内容為:
#! /bin/bash
name=yxc # 定義變量name
然後建立
test2.sh
,内容為:
#! /bin/bash
source test1.sh # 或 . test1.sh
echo My name is: $name # 可以使用test1.sh中的變量
執行指令:
[email protected]:~$ chmod +x test2.sh
[email protected]:~$ ./test2.sh
My name is: yxc
轉載自AcWing-Linux基礎課