# 第七章:shell相關
## 區分登錄/非登錄shell,以及交互式/非交互式shell
### 登錄/非登錄shell
- 登錄shell(login shell)指的是需要用戶輸入用戶名和密碼才能進去的shell,這是比較常用的。
- 非登錄shell(non-login shell)則相反,不需要用戶輸入用戶名和密碼,例如:
- 直接命令`bash`(不帶`--login`參數)就是打開一個新的非登錄shell。
- 在圖形界面(如Gnome或KDE)中打開一個“終端”(terminal)窗口程序也是屬于打開了一個非登錄shell。
### 交互式/非交互式shell
- 交互式shell(interactive shell)指的是shell等待自然人用戶在終端上的輸入,并且按照用戶輸入的命令立即執行且返回執行結果,這種操作模式被稱為“交互式模式”,是我們最直觀、最常用的操作方式。
- 非交互式shell(non-interactive shell)針對的并非自然人用戶,它通常是以 shell script (即以 shell 的語法所寫成的程序代碼段)的方式執行。
- 運行shell腳本程序時,系統將創建一個子shell。此時,系統中將有兩個shell,一個是登錄時系統啟動的shell,另一個是系統為運行腳本程序創建的shell。
- 當一個腳本程序運行完畢,它的腳本shell將終止,可以返回到執行該腳本之前的shell。
- 從這種意義上來說,用戶可以有許多 shell,每個shell都是由某個shell(稱為父shell)派生的。
### 不同類型shell初始化時所執行的startup腳本不一樣
> 對于Bash來說,登錄shell(包括交互式登錄shell和使用`–-login`選項的非交互shell),它會首先讀取和執行`/etc/profile`全局配置文件中的命令,然后依次查找`~/.bash_profile`、`~/.bash_login` 和 `~/.profile`這三個配置文件,讀取和執行這三個中的第一個存在且可讀的文件中命令。除非被`–noprofile`選項禁止了。另外,由于`~/.bash_profile`一般也會帶有執行`~/.bashrc`的代碼段,因此`~/.bashrc`的內容也會被執行。
> 在非登錄shell里,只讀取 `~/.bashrc` (和 `/etc/bash.bashrc`、`/etc/bashrc` )文件,不同的發行版里面可能有所不同。
從上可得,無論是登錄shell還是非登錄shell,最終都會執行`~/.bashrc`。
## 如何讓變量突破shell腳本程序的上下文限制:`export`與`source`
在子 shell 中定義的變量只在該子 shell 內有效。如果在一個 shell 腳本程序中定義了一個變量,當該腳本程序運行時,這個定義的變量只是該腳本程序內的一個局部變量,其他的 shell 不能引用它。
有兩種方法可以使子 shell 中的變量突破上下文限制,在其它 shell 中也能使用:
- 在腳本代碼中使用`export`語句可以對已定義的變量進行輸出,如下:
```
a=2333
export a
```
`export`命令將使系統在創建每一個新的子 shell 時定義這個變量的一個拷貝。這個過程稱之為變量輸出。
- 在交互式 shell 中使用`source`命令來執行腳本程序,系統將不會生成新的子 shell 來執行腳本程序,而是直接在當前的交互式 shell 中執行;那么,腳本程序中所有定義(或改變)的變量,都將在當前 shell 中起效。
## 如何配置Linux用戶的工作環境,并在下次登錄依然保持有效
當我們直接在命令行里執行配置工作環境的命令,如`alias`,賦值自定義變量等操作時,這些配置僅適用于當前登錄的會話,若當前用戶退出系統,則這些配置均失效。
首先需要明確的是,既然針對的是“Linux用戶的工作環境”,那么實際上我們執行的是**登錄交互式shell**;因此,若想要這些工作環境的配置在下次登錄依然保持有效,可以這樣做:
- “全局配置”,指的是為當前系統的每一個用戶都設置一致的工作環境配置。全局配置的方式是:在`/etc/profile`文件中進行編輯或添加命令語句。
- “每個用戶私有配置”,指的是針對Linux中具體某個用戶進行特定的工作環境配置,其方式為:在`~/.bash_profile`中進行編輯或添加命令語句。
## shell變量規則
- 賦值變量的格式為`a=b`,其中a為變量名,b為賦給變量的值,需要注意的是,等號兩邊不能有空格。
- 變量名只能由字母、數字以及下劃線組成,而且不能以數字開頭。
- 當變量內容帶有特殊字符(如空格)時,需要加上單引號,如`myname='array huang'`;如果變量內容中本來就含有單引號,此時就需要加雙引號了,如`myname="'array huang'"`。
- 如變量中需要用到linux命令的執行結果,可以使用反引號,如
```
pwdResult=`pwd`
```
- 拼接變量的方法如下:`myname="$firstName"Huang`
## `grep`命令和`egrep`命令不一樣的地方
`egrep`是`grep`的擴展版本,因此在功能上前者比后者更為強大,下面介紹它們不同的地方:
- `egrep`支持更多的正則表達式用法:
- `?`(表示0個或1個指定的字符);
- `+`(表示至少1個指定的字符);
- `|`(邏輯或連接符),如`'a|b'`,指的是匹配含有 a **或** b 的字符串;
- `()`表示把多個字符合為1個整體來進行表達,通常搭配其它正則表達式符號來使用,如`'r(oo|at)o'`,指的是匹配含有 rooo 或 rato 的字符串;
- 在`{}`的用法上,`grep`的使用比較麻煩,需要加上轉義字符`\`,如`'o\{2\}'`(表示匹配含有 oo 的字符串);而`egrep`的使用則自然得多,同樣的正則表達式,直接使用`'o{2}'`即可。
## shell腳本
### shell腳本的創建和執行
- shell腳本應以`#! /bin/bash`開頭來表明該腳本使用的是 bash 語法,如果不寫,也能正常運行,但就不符合編碼規范了。
- shell腳本的執行有兩種方法:
- 執行命令`sh ./file.sh`,并且可以考慮加上`-x`參數,這樣就能查看腳本每一步執行的命令與結果了,非常利于調試程序。
- 直接執行`./file.sh`,這種方法使用的前提是用戶擁有腳本的執行權限(即**x**權限),需要注意的是新建腳本文件時一般是沒有執行權限的,需要通過`chmod`進行修改。
因此一般我們直接用第一種方法即可。
### 腳本常用命令及語法
在 shell 腳本中,命令可看作是一般程序語言中的預設函數。
#### 時間
`date`,時間格式化和簡單的計算(如“一天前”、“一小時后”等)。
- 命令(函數)的結果可以通過反引號來賦值給變量,如
```
nowDate=`date +"%Y-%m-%d"`
```
#### 數學運算
- 數學運算有特別的語法:
- 用`$[]`給括起來(只支持整數運算),如:
```
a=1
b=2
c=$[$a+$b+3]
echo $c // 結果是6
```
-
- 使用`let`(只支持整數運算):
```
var=1
let "var+=1"
echo $var
```
-
- 使用`(())`(只支持整數運算):
```
var=1
((var+=1))
echo $var
```
-
- 使用bc(可以進行浮點數計算):
```
var=1
var=`echo "$var+1"|bc`
echo $var
```
其原理是:bc是linux下的一個簡單計算器,支持浮點數計算,在命令行下輸入bc即進入計算器程序,而我們想在程序中直接進行浮點數計算時,利用一個簡單的管道即可解決問題。
#### 和用戶交互
使用`read`命令可達到與用戶交互的目的,它會把用戶輸入的字符串作為變量值,如:
```
read -p "please input a number:" x # 執行后等待用戶輸入并按回車確認
echo "$x"
```
#### 執行shell腳本時傳參
在執行腳本時,我們可以通過對腳本傳參(當然前提是腳本被設計成可以接受參數),來改變腳本的具體行為,以及得到以此計算出來的結果。
腳本內獲取參數的格式為:$n。(n代表一個數字,,1 為執行腳本的第一個參數,2 為執行腳本的第二個參數,以此類推……)。
舉例說明,當前有一個名為**test.sh**的腳本:
```
#!/bin/bash
echo "Shell 輸出腳本名稱及參數";
echo "執行的腳本名:$0";
echo "第一個參數為:$1";
echo "第二個參數為:$2";
echo "第三個參數為:$3";
```
運行輸出:
```
$ ./test.sh 1 2 3
Shell 傳遞參數實例!
執行的文件名:./test.sh
第一個參數為:1
第二個參數為:2
第三個參數為:3
```
#### 邏輯判斷
共有以下3種語法:
- 不帶`else`:
```
if [判斷語句]; then
command
fi
```
- 帶`else`:
```
if [判斷語句]; then
command
else
command
fi
```
- 帶`elif`:
```
if [判斷語句]; then
command
elif [判斷語句]; then
command
else
command
fi
```
##### if語句
###### 字符串判斷
- str1 = str2 當兩個串有相同內容、長度時為真
- str1 != str2 當串str1和str2不等時為真
- -n str1 當串的長度大于0時為真(串非空)
- -z str1 當串的長度為0時為真(空串)
- str1 當串str1為非空時為真
###### 數字的判斷
- int1 -eq int2 兩數相等為真
- int1 -ne int2 兩數不等為真
- int1 -gt int2 int1大于int2為真
- int1 -ge int2 int1大于等于int2為真
- int1 -lt int2 int1小于int2為真
- int1 -le int2 int1小于等于int2為真
###### 文件的判斷
- -r file 用戶可讀為真
- -w file 用戶可寫為真
- -x file 用戶可執行為真
- -f file 文件為正規文件為真
- -d file 文件為目錄為真
- -c file 文件為字符特殊文件為真
- -b file 文件為塊特殊文件為真
- -s file 文件大小非0時為真
- -t file 當文件描述符(默認為1)指定的設備為終端時為真
###### 復雜邏輯判斷
- && 與
- || 或
- ! 非
舉例:
```
a=10
if [$a -lt 1] || [$a -gt 5]; then
echo ok; # 輸出ok
fi
```
#### case語句
語法如下:
```bash
case 變量 in
value1)
command
;;
value2)
command
;;
*)
command
;;
esac
```
結合執行shell腳本時傳入的參數,可以實現功能的切換:
```bash
#!/bin/bash
case $1 in
start|s) ## |表示or,在這里表示匹配start或s均可
echo service is running
;;
stop)
echo service is stoped
;;
reload)
echo service is reload
;;
*)
echo xxxxx
;;
esac
```
#### for循環
語法如下:
```
for 變量名 in 循環的條件; do
command
done
```
##### 循環條件
- 數字遞進:`((i=1;i<=10;i++))`
- 列舉一組字符串或數字,以空格分隔:`1 2 3 aaa bbb`
- 命令執行的結果:
```bash
for i in `ls`; do
echo $i is file name\!
done
```
- 變量的值:
```bash
list="rootfs usr data data2"
for i in $list; do
echo $i is appoint
done
```
- 執行腳本時傳入的參數:
```bash
#!/bin/bash
echo "number of arguments is $#" # $#表示參數的個數
echo "What you input is: "
for argument in "$@"; do # $@表示參數列表,此處也可以使用$*代替,$*則把所有的參數當作一個字符串
echo "$argument"
done
```
#### while循環
語法:
```
while [循環(判斷)的條件]; do
command
done
```
注意此處的***循環(判斷)的條件***與`if`語句中的判斷條件是一樣的。
#### 函數
##### 函數定義語法:
```bash
function 函數名() {
command
[return] # 可return也可不return
}
```
##### 函數的調用方法是:
```bash
#!/bin/bash
function show() {
echo "hello , you are calling the function"
}
echo "first time call the function"
show
echo "second time call the function"
show
```
##### 如函數需要傳入參數,則使用$1、$2……等取用:
```bash
#!/bin/bash
function show() {
echo "hello , you are calling the function $1"
}
echo "first time call the function"
show first # 輸出hello , you are calling the function first
echo "second time call the function"
show second # 輸出hello , you are calling the function second
```
##### 函數中的關鍵字“return”可以放到函數體的任意位置,通常用于返回某些值,Shell在執行到return之后,就停止往下執行,返回到主程序的調用行,return的返回值只能是0~256之間的一個整數,返回值將保存到變量“$?”中。
```
#!/bin/bash
function test() {
return 0
}
test
echo "$?" # 輸出0
```
##### 如果函數在另外一個文件中,我們該怎么調用它呢?
我們可以使用`source`命令,比如 show 函數寫在了function.sh里面了:
```bash
source function.sh
show
```
##### 函數的變量作用域
默認情況下,變量具有全局作用域,如果想把它設置為局部作用域,可以在其前加入local
例如:
```bash
local a=hello
```
使用局部變量,使得函數在執行完畢后,自動釋放變量所占用的內存空間,從而減少系統資源的消耗,在運行大型的程序時,定義和使用局部變量尤為重要。
##### 函數的嵌套
函數可以進行嵌套,實例:
```bash
#!/bin/bash
function first() {
function second() {
function third() {
echo "------this is third"
}
echo "this is the second"
third
}
echo "this is the first"
second
}
echo "start..."
first
```
#### shell腳本中的中斷和繼續
##### break
與其它程序語言一樣,`break`是用來跳出當前的循環的,`break`后可加數字,如`break 2`表示跳出兩層的循環。
##### continue
與其它程序語言一樣。
##### exit
退出腳本,或者更準確地說,是退出當前運行腳本的shell(既然運行環境關閉了,那么腳本當然也不能繼續執行下去了)。
在函數中使用`exit`,是會退出整個腳本的,因此若只是想退出函數,請使用`return`。
`exit`后可接數字作為狀態碼,如`exit 0`;一般來說,**0**表示腳本執行成功,其它狀態碼則表示各種可能的異常情況
## 別名
我們可以通過`alias`命令把一個常用的并且很長的指令另取名為一個簡單易記的指令;如果不想用了,還可以使用`unalias`來取消。
- 使用`alias`命令,可以列出系統當前所有的別名。
- 使用`alias [命令別名]=['具體的命令']`可以自定義別名,例如:`alias aming='pwd'`。
- 使用`unalias`可以取消別名,例如`unalias aming`。
## 重定向
重定向分為“輸入重定向”和“輸出重定向”,重定向一般通過在命令間插入特定的符號來實現。
- `command1 > file1`,這個命令執行command1然后將執行后輸出的內容存入file1。注意任何file1內的已經存在的內容將被新內容替代。如果要將新內容添加在文件末尾,請使用>>操作符。
- `command1 < file1`,執行command1,使用file1作為用來替代鍵盤的輸入源。
- `command1 < infile > outfile`,同時替換輸入和輸出,執行command1,從文件infile讀取內容,然后將輸出寫入到outfile中。
另外,上述的“輸出內容”僅指命令執行的結果,若命令執行中出現錯誤,則錯誤信息需要另行處理:
- `command1 2> file1`,執行command1,然后將標準錯誤輸出重定向到文件file1。
- 另外一個很有用的功能是將標準錯誤輸出融合到標準輸出中去,這樣錯誤信息可以和其他普通的輸出信息一起處理。例如:`command > file 2>&1`,這表示將command執行后的結果與錯誤信息均寫入到file里。
## 管道符
管道符的標識為`|`,它用于將前一個指令的輸出作為后一個指令的輸入,格式如下:`command1 | command2`,舉例說明:`yum list | grep zip | head -n 5`。