[TOC]
# Shell簡介
## 什么是Shell
Shell是在Linux下的命令解釋型語言(command-language interpreter),它的中文翻譯為“殼”主要是用于人機交互。
## Shell種類
Linux Shell的種類很多,目前流行的Shell包括ash、bash、ksh、csh、zsh等,用戶可以通過查看/etc/shells 文件中的內容來查看自己主機中當前有哪些種類的Shell。
```
[djangowang@localhost ~]# cat /etc/shells
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.
/bin/bash
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh
```
## 編寫第一個Shell程序
通常寫程序我們都會從Hello World開始,編寫第一個Shell程序我們也從他開始。關于Hello World的由來我們可以參考( http://blog.puppeter.com/read.php?25 )。以下為第一個Shell腳本程序,通過vim命令編輯hello.sh文件,其中sh為Shell腳本的擴展名文件。
```
#! /bin/bash Bash的命令解釋器
# author:djangowang 標識腳本作者的名字
# time : 2021.1.27 標識腳本開發的時間
# filename: hello.sh 標識腳本的名字
# 建議初學者每次寫腳本按照以上的書寫方式,優勢是并行開發過程中能查到腳本的作者和開發時間,方便后續有問題的回溯
echo "hello wolrd" # 調用系統命令打印結果。
```
# 變量
變量在Bash中變量顧名思義通常是可變的量,它來源于數學是計算機語言中能儲存計算結果或能表示值抽象概念。
## 變量規范
變量名的命名須遵循如下規則:
* \_ 命名只能使用英文字母,數字和下劃線,首個字符不能以數字開頭
* \_ 中間不能有空格,可以使用下劃線(\_\_)
* 不能使用標點符號。 不能使用Bash里的關鍵字(可用help命令查看保留關鍵字)
## Bash變量案例
在Bash中變量通過"$"符來表示,以下是一個Bash腳本的案例。
```
#!/bin/bash
name="This is Shell script" # 將字符串賦值給name
echo $name # 打印變量$name
```
這腳本最終的結果就會在屏幕上打印出This is Shell script。
## 變量四種賦值方式
這四種方式包括:
* 直接賦值
* read命令賦值
* 命令賦值
* 位置參數賦值
**直接賦值**
以下是一個Bash腳本,它將字符串“hi my name is djangowang”賦值給變量name并通過echo命令打印變量中的內容。
```
#!/bin/bash
name="hi my name is djangowang"
echo $name
```
**read命令賦值**
read是Bash中的內建命令,它從鍵盤獲取標準輸出并賦值給變量。以下是將鍵盤輸入的內容賦值給變量name,并通過echo命令打印變量中的內容。
```
#!/bin/bash
read name
echo $name
```
**命令賦值**
獲取系統命令的標準輸出并將標準輸出內容賦值給變量command,并通過echo命令打印變量中的內容。這里注意命令賦值方式共分為兩種見以下案例。
```
#!/bin/bash
command = `date` # 推薦賦值方式 ,其中“`” 是鍵盤按鍵1邊上的符號。
echo $command
# 或
command = $(date)
echo $command
```
**位置參數賦值**
位置參數賦值是通過通過執行腳本時傳遞參數賦值給變量。譬如以下腳本名為test.sh內容如下,通過執行/bin/sh test.sh hello,其中hello就是位置參數他會通過$1賦值給command變量,這里注意如果位置變量有空格又需要同時傳給位置變量1可以通過“”來擴起來,譬如/bin/sh test.sh hello "hello world"。這里位置變量通過空格作為變量的分割符。
```
#!/bin/bash
command = $1
echo $command
```
意位置變量通常為數字$1-$9,10以上要用大括號擴起來如${10},${10}以下是案例。
```
#!/bin/bash
# argc.sh a b c d e f g h i j k
echo $1
echo $2
echo $3
echo $4
echo $5
echo $6
echo $7
echo $8
echo $9
echo ${10}
echo ${11}
```
以上程序有個問題,如果位置參數要是大于10或更多這樣寫程序成本會很高且程序易讀性也不好,這時我們可以使用shift命令,它用于參數的自動左移。
```
#!/bin/bash
while [ $# != 0 ]
do
echo "prama is $1,prama size is $#"
shift
done
```
## 定義變量類型
在Bash中默認為字符串類型,可以通過declare關鍵字指定變量的類型,還可以設置變量的屬性或者刪除變量。 以下介紹變量定義的七種方式:
* 字符串型
* 數值型
* 數組
* 函數
* 設置環境變量
* 只讀變量
* unset變量
**字符串型**
Bash中的默認數據類型為字符串型。
```
#!/bin/bash
string="hi my name is djangowang"
echo $string
```
**數值型**
在Bash中字符串類型只能用于字符串比較,不能進行數學運算。我們通過declare -i來定義將變量改為數值型,并進行數學運算。
```
declare -i number # 定義一個數值型
```
我們來對比一下字符串型與數字型。
```
#!/bin/bash
# 字符串
n=6/3
echo "n = $n" # n = 6/3
# 數值型
declare -i n
n=6/3
echo "n = $n" # n = 2
```
**數組**
數組中可以存放多個值。Bash只支持一維數組,不支持多維數組,初始化時不需要定義數組大小,與大部分編程語言類似數組元素的下標由0開始。
```
declare -a array
```
數組案例。
```
#!/bin/bash
declare -a array
array=(A B "C" D)
echo "第一個元素為: ${array[0]}"
echo "第二個元素為: ${array[1]}"
echo "第三個元素為: ${array[2]}"
echo "第四個元素為: ${array[3]}"
```
**函數**
declare -f 函數名 ,用于顯示函數內容。
```
#!/bin/bash
function a(){
echo "test1"
}
function b(){
echo "test1"
}
declare -f # 顯示以上函數
declare -f a # 接函數名,顯示指定的函數
```
在Shell編程實戰中應用不是很多。
**設置環境變量**
declare -x指定的變量會成為環境變量,可供Shell以外的程序來使用。
```
#!/bin/bash
declare -x STRING="hello world" # 定義一個string的環境變量,建議環境變量為大寫
export -p # 列出所有的Shell賦予程序的環境變量
```
**只讀變量**
declare -r var1與readonly var1作用相同。當設置只讀變量后,變量內容不可以修改。
```
declare -r var1 # 設置一個只讀變量
#或
readonly var1
readonly -p # 用于顯示只讀變量的清單
```
案例
```
#!/bin/bash
url="http://blog.puppeter.com/"
declare -r url # 或readonly url變量
url="http://blog.puppeter.com/" # 當修改變量時會報錯誤“/bin/sh: NAME: This variable is read only”
```
**unset變量**
unset用于刪除變量。他有兩個參數-f(僅刪除函數)-v(僅刪除變量)默認值。
```
#!/bin/bash
foo="hello world"
echo $foo # 輸出hello world
unset foo # 刪除foo變量
echo $foo # 為空
```
# 變量類型
在Bash中的變量是有作用域的,分別為:
* 局部變量
* 環境變量
* 內部變量
## 局部變量
局部變量在腳本或命令中定義,僅在當前Bash實例中有效,其他Bash啟動的程序不能訪問局部變量。局部變量的關鍵字為"local",以下為局部變量案例。
```
#!/bin/bash
function hello()
{
local text="Hello World!!!" # 定義局部變量
echo $text
}
text="this is test"
hello
echo $text # 可以試著去掉函數中的local,再執行本腳本的效果
```
前者輸出結果為。
```
Hello World!!!
this is test
```
去掉local輸出結果為。
```
Hello World!!!
Hello World!!!
```
## 環境變量
所有的程序包括Bash啟動的程序都能訪問環境變量,有些程序需要環境變量來保證其正常運行。在Bash中可以通過以下三個命令來查看環境變量,他們區別在于:
* set 用來顯示本地變量
* env 用來顯示環境變量
* export -p 用來顯示和設置環境變量
*注:變量和環境變量的區別是:變量不能被子進程繼承,而環境變量會被子進程繼承。*
我們還可以通過以下兩個文件來設置環境變量:
* /etc/profile
* $HOME/.bash_profile
譬如bash_profile文件內容,
我們可以將自己的環境變量放在PATH后。
```
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/bin
export PATH
```
## 內部變量
內部變量是Bash程序設置的特殊變量。Bash變量中有一部分是環境變量,有一部分是局部變量,這些變量保證了Bash的正常運行,以下包含Bash的。
| **內部變量** | **解釋** |
| --- | --- |
| $BASH\_VERSION | Bash版本 |
| $HOSTNAME | hostname |
| $HOME | 宿主目錄(家目錄) |
| $PATH | 環境變量 |
| $RANDOM | 隨機整數 |
# Bash符號相關
在Bash中存在一些特殊符號,他們主要用于標準輸出時的一些格式展現,如以下:
特殊符號對照表,表1。
| 符號 | 含義 |
| --- | --- |
| \\n | 新行 |
| \\r | 回車 |
| \\t | 制表符 |
| \\v | 垂直的制表符 |
| \\b | 后退符 |
| \\a | 警告(蜂鳴或是閃動) |
在Bash中這些特殊符號主要用于以下兩個命令的場景:
場景1:echo命令是我們學習Bash編程中一個很常用的命令,他用于打印信息到標準輸出,譬如。
```
[root@blog.puppeter.com_centos ~]# echo "hello world" # 打印hello world到標準輸出
```
目前echo有兩個參數:
* \-n 不解析參數內的特殊符號
* \-e 默認值,解析參數內的特殊符號
```
[root@blog.puppeter.com_centos ~]# echo -n "hello\tworld" # 不解析參數內制表符,同時不執行echo后的\n,特殊符號見表1
[root@blog.puppeter.com_centos ~]# echo -e "hello\tworld" # 解析參數中的制表符。
```
場景2:我們再來看一下Printf命令。與echo相同的都是打印內容到屏幕上,但printf命令模仿 C 程序庫(library里的 printf() 程序,它由 POSIX 標準所定義,因此使用printf的腳本比使用echo移植性好,以下為案例。
```
#!/bin/bash
printf "%-10s %-8s %-4s\n" 姓名 性別 體重kg
printf "%-10s %-8s %-4.2f\n" wds 男 66.1
printf "%-10s %-8s %-4.2f\n" djangowang 男 77.6543
printf "%-10s %-8s %-4.2f\n" hanmeimei 女 57.9876
```
我們再來看一下Printf命令。與echo相同的都是打印內容到屏幕上,但printf命令模仿 C 程序庫(library里的 printf() 程序,它由 POSIX 標準所定義,因此使用printf的腳本比使用echo移植性好,以下為案例。
```
#!/bin/bash
printf "%-10s %-8s %-4s\n" 姓名 性別 體重kg
printf "%-10s %-8s %-4.2f\n" wds 男 66.1
printf "%-10s %-8s %-4.2f\n" djangowang 男 77.6543
printf "%-10s %-8s %-4.2f\n" hanmeimei 女 57.9876
```
# Read命令
read是Bash的內建命令,主要用于從鍵盤讀取內容賦值給變量,它有以下常用參數:
* \-p :指定多個變量
```
#!/bin/bash
read -p “please input your name " name
echo "your name is $name"
exit 0
```
* \-n :計數輸入的字符
```
#!/bin/bash
read -n6 -p “please input your password(lengh must be over 6 numbers)" passwd
exit 0
```
* \-s :隱藏輸入,不回顯內容到終端
```
#!/bin/bash
read -s -n6 -p “please input your password(lengh must be over 6 numbers)" passwd
exit 0
```
* \-t :超時等待時間
```
#!/bin/bash
read -t 5 -p “please input your name" name
if [ $? -eq 0 ];then
echo "your name is $name"
else
echo "timeout"
fi
exit 0
```
# 條件語句
* if..then..fi
* if..then..else..fi
* if..then..elif..fi
## if..then..fi
首先來看一下if..then..fi的語法,它有多種書寫方式 。
**方式1**
```
#!/bin/bash
if [ 條件語句 ];then # 推薦書寫方式
執行內容
fi
```
**方式2**
```
if [ 條件語句 ]
then
執行內容
fi
```
**方式3**
```
if (());then
執行內容
fi
```
**方式4**
```
if command ;then
執行內容
fi
```
*注:初學者一定要注意以上if..then語法中,條件語句兩邊都是有空格的,缺少一個空格都會報錯且不容易被注意到。*
再來看一下關于if..then..fi的案例。
1.if..then的\[\]案例,比較兩個值是否相等,此方式主要用于字符串匹配。
```
#!/bin/sh
a=10
b=20
if [ $a == $b ];then # 如果if和then寫在一行,需要通過;來進行分割
echo "a is equal to b"
fi
if [ $a != $b ]
then
echo "a is not equal to b"
fi
```
2.if..then的(())案例,數學方式比較大小,此方式主要用于數學方式的比較。
```
#!/bin/bash
i=100
if ((10 <$i));then # 數學比較方式
echo "true"
fi
```
3.if..command,判斷是否為目錄,此方式主要用于調用命令來判斷命令返回最終結果。
```
#!/bin/bash
dir=/home/
if cd "$dir" 2>/dev/null; then # "2>/dev/null" 會隱藏錯誤信息.
echo "Now in $dir."
fi
```
## if..then..else..fi
if..then..else..fi的語法。
**方式1**
```
#!/bin/bash
if [ 條件語句 ];then # 推薦書寫方式
執行內容
else
執行內容2
fi
```
**方式2**
```
if [ 條件語句 ]
then
執行內容
else
執行內容2
fi
```
再來看一下if..then..else..fi的案例。
```
#!/bin/sh
a=10
b=20
if [ $a == $b ];then
echo "a is equal to b"
else
echo "a is not equal to b"
fi
```
## if..then..elif..fi
if..then..elif..fi的語法:
**方式1**
```
if [ 條件語句 ];then
執行內容
elif [ 條件語句 ];then
執行內容
else
執行內容
fi
```
**方式2**
```
if [ 條件語句 ]
then
執行內容
elif [ 條件語句 ]
then
執行內容
else
執行內容
fi
```
if..then..elif..fi的案例。
**案例1**
```
#!/bin/bash
a=10
b=20
if [ $a -eq $b ] ;then
echo "a is equal to b"
elif [ $a -gt $b ] ;then # a 大于 b 見表1
echo "a is greater than b"
elif [ $a -lt $b ];then # a 小于 b 見表1
echo "a is less than b"
else
echo "None of the condition met"
fi
```
文件比較符。
| \[ \]括號 | (())擴容 | 含義 |
| :--- | :--- | :--- |
| -eq | == | 等于 |
| -ne | != | 不等于 |
| -gt | > | 大于 |
| -ge | >= | 大于等于 |
| -lt | < | 小于 |
| -le | <= | 小于等于 |
**案例2**
通過條件語句實現的計算器。
```
#!/bin/bash
# author:djangwoang
# filename:jisuanqi.sh
echo "please input your first number"
read a
echo "please input + - * /"
read b
echo "please input your second number"
read c
if [ "x$b" == "x" ];then
echo "please input + - * /"
elif [ "$b" == "+" ];then
tmp=$((a+c))
elif [ "$b" == "-" ];then
tmp=$((a-c))
elif [ "$b" == "*" ];then
tmp=$((a*c))
elif [ "$b" == "/" ];then
tmp=$((a/c))
else
echo "please input + - * /"
fi
echo "result is:${tmp}"
```
# 循環語句
Shell支持五中循環方式:
* while循環
* for循環
* for..in循環
* until循環
* select循環
## while循環
首先來看一下while循環的語法。
**方式1**
```
while [ 條件表達式 ];do # 推薦
執行內容
done
```
**方式2**
```
while [ 條件表達式 ]
do
執行內容
done
```
**方式3**
```
while [ 條件表達式 ];do 執行內容 ;done # Bash語句大都可以寫作一行,只不過可讀性差
```
**方式4**
```
while command;do
執行內容
done
```
再來看一下while循環的案例。
1.打印1-100的數字。
```
#!/bin/bash
i=1
while [ $i -le "100" ];do
echo $i
i=$((i+1))
done
```
2.打印1-100間的偶數。
```
#!/bin/bash
i=1
while [ $i -le "100" ];do
tmp=$((i%2))
if [ $tmp -eq 0 ];then
echo $i
fi
i=$((i+1))
done
```
3.打印/etc/passwd信息。
```
#!/bin/bash
while read line # 推薦
do
echo $line
done < /etc/passwd
# 或
#!/bin/bash
cat /etc/passwd | while read line
do
echo $line
done
```
4.死循環
死循環中條件表達式永遠為真,如果要退出死循環可以用ctrl+c方式。
```
#!/bin/bash
while true ;do
echo "hello world"
done
```
## for循環
for循環的語法。
**方式1**
```
for (( ; ; ));do # 推薦
執行內容
done
```
**方式2**
```
for (( ; ;))
do
執行內容
done
```
**方式3**
```
for ((;;));do 執行內容 ;done
```
for循環的案例。
1.打印1-100的數字。
```
for ((i=1; i<=100; i ++)); do
echo $i
done
```
2.打印1-100間的奇數。
```
#!/bin/bash
for((i=1;i<=100;i++));do
tmp=$((i%2))
if [ $tmp -ne 0 ];then
echo $i
fi
done
```
3.死循環。死循環中條件表達式永遠為真,如果要退出死循環可以用ctrl+c方式.
```
#!/bin/bash
for((;;));do
echo "hello world"
done
```
## for..in 循環
for..in循環的語法。
**方式一**
```
for 變量 in 條件語句;do # 推薦
執行語句
done
```
**方式二**
```
for 變量 in 條件語句
do
執行語句
done
```
**方式三**
```
for 變量 in 條件語句;do 執行語句 ;done
```
案例
1.打印1-5的數字。
```
for loop in 1 2 3 4 5 ;do
echo $loop
done
```
2.打印1-100間的數字。
```
for loop in `seq 1 100`;do
echo $loop
done
```
3.創建1-100的文件夾。
```
for loop in `seq 1 100`;do
mkdir $loop
done
```
## until循環
until 循環執行一系列命令直至條件為 true 時停止。until 循環與 while 循環在處理方式上剛好相反,一般while循環優于until循環,但在某些時候,也只是極少數情況下,until 循環更加有用。首先來看一下until循環的語法。
**方式一**
```
until command;do # 推薦
執行語句
done
```
**方式二**
```
until command
do
執行語句
done
```
## select循環
select 是個無限循環,因此要記住用break命令退出循環或用exit命令終止腳本,也可以按ctrl+c 退出循環。我們首先看select的語法。
**方式一**
```
select name [in list ];do # 推薦
執行語句
done
```
**方式二**
```
select name [in list ]
do
執行語句
done
```
案例,通常我們使用select用來做列表,案例如下。
```
#!/bin/bash
echo "What is your favourite OS?"
select var in "Linux" "Windows" "Free BSD" "Other"; do
break;
done
echo "You have selected $var"
```
# 分支語句
Shell的分支語句其實就是case,我們先來看一下它的語法。
```
case 匹配內容 in
條件1)
執行內容1
執行內容2
;;
條件2)
執行內容1
執行內容2
;;
條件3)
執行內容1
執行內容2
;;
*)
默認執行
;;
esac
```
案例1,計算器。
```
#!/bin/bash
if [ $# -ne 3 ];then
echo "參數個數應該為3,例如:$0 1 + 2"
exit 1;
fi
case $2 in
+)
echo "scale=2;$1+$3" | bc
;;
-)
echo "scale=2;$1-$3" | bc
;;
\*)
echo "scale=2;$1*$3" | bc
;;
/)
echo "scale=2;$1/$3" | bc
;;
*)
echo "$2 不是運算符"
;;
esac
exit 0
```
案例2,位置參數。
```
#!/bin/bash
name=`basename $0 .sh`
case $1 in
START|start)
echo "start..."
;;
STOP|stop)
echo "stop ..."
;;
RELOAD|reload)
echo "reload..."
;;
*)
echo "Usage: $name [start|stop|reload]"
exit 1
;;
esac
exit 0
```
# 本章小結
# 習題