# 4.4 特殊的變量類型
### 局部變量
僅在代碼塊或函數中才可見的變量(參考函數章節的局部變量部分)。
### 環境變量
會影響用戶及shell行為的變量。
>  一般情況下,每一個進程都有自己的“環境”(environment),也就是一組該進程可以訪問到的變量。從這個意義上來說,shell表現出與其他進程一樣的行為。
>
> 每當shell啟動時,都會創建出與其環境對應的shell環境變量。改變或增加shell環境變量會使shell更新其自身的環境。*子進程*(由父進程執行產生)會繼承*父進程*的環境變量。
>
>  分配給環境變量的空間是有限的。創建過多環境變量或占用空間過大的環境變量有可能會造成問題。
>
```
bash$ eval "`seq 10000 | sed -e 's/.*/export var&=ZZZZZZZZZZZZZZ/'`"
>
bash$ du
bash: /usr/bin/du: Argument list too long
```
> 注意,上面的"錯誤"已經在Linux內核版本號為2.6.23的系統中修復了。
>
> (感謝 Stéphane Chazelas 對此問題的解釋并提供了上面的例子。)
如果在腳本中設置了環境變量,那么這些環境變量需要被“導出”,也就是通知腳本所在的*環境*做出相應的更新。這個“導出”操作就是 `export` 命令。
>  腳本只能將變量導出到子進程,即在這個腳本中所調用的命令或程序。在命令行中調用的腳本不能夠將變量回傳給命令行環境,即*子進程不能將變量回傳給父進程*。
>
> **定義:** 子進程(child process)是由另一個進程,即其父進程(parent process)所啟動的子程序。
### 位置參數
從命令行中傳遞給腳本的參數[^1]:`$0, $1, $2, $3 ...`
即**命令行參數**。
`$0` 代表腳本名稱,`$1` 代表第一個參數,`$2` 代表第二個,`$3` 代表第三個,以此類推[^2]。在 `$9` 之后的參數必須被包含在大括號中,如 `${10}, ${11}, ${12}`。
特殊變量 `$*` 與 `$@` 代表所有位置參數。
樣例 4-5. 位置參數
```bash
#!/bin/bash
# 調用腳本時使用至少10個參數,例如
# ./scriptname 1 2 3 4 5 6 7 8 9 10
MINPARAMS=10
echo
echo "The name of this script is \"$0\"."
# 附帶 ./ 代表當前目錄
echo "The name of this script is \"`basename $0`\"."
# 除去路徑信息(查看 'basename')
echo
if [ -n "$1" ] # 測試變量是否存在
then
echo "Parameter #1 is $1" # 使用引號轉義#
fi
if [ -n "$2" ]
then
echo "Parameter #2 is $2"
fi
if [ -n "$3" ]
then
echo "Parameter #3 is $3"
fi
# ...
if [ -n "${10}" ] # 大于 $9 的參數必須被放在大括號中
then
echo "Parameter #10 is ${10}"
fi
echo "-----------------------------------"
echo "All the command-line parameters are: "$*""
if [ $# -lt "$MINPARAMS" ]
then
echo
echo "This script needs at least $MINPARAMS command-line arguments!"
fi
echo
exit 0
```
在位置參數中使用大括號助記符提供了一種非常簡單的方式來訪問傳入腳本的最后一個參數。在其中會使用到間接引用。
```bash
args=$# # 傳入參數的個數
lastarg=${!args}
# 這是 $args 的一種間接引用方式
# 也可以使用: lastarg=${!#} (感謝 Chris Monson.)
# 這是 $# 的一種間接引用方式。
# 注意 lastarg=${!$#} 是無效的。
```
一些腳本能夠根據調用時文件名的不同來執行不同的操作。要達到這樣的效果,腳本需要檢測 `$0`,也就是調用時的文件名[^3]。同時,也必須存在指向這個腳本所有別名的符號鏈接文件(symbolic links)。詳情查看樣例 16-2。
>  如果一個腳本需要一個命令行參數但是在調用的時候卻沒有傳入,那么這將會造成一個空變量賦值。這通常不是我們想要的。一種避免的方法是,在使用期望的位置參數時候,在賦值語句兩側添加一個額外的字符。
```bash
variable1_=$1_ # 而不是 variable1=$1
# 使用這種方法可以在沒有位置參數的情況下避免產生錯誤。
critical_argument01=$variable1_
# 多余的字符可以被去掉,就像下面這樣:
variable1=${variable1_/_/}
# 僅僅當 $variable1_ 是以下劃線開頭時候才會有一些副作用。
# 這里使用了我們稍后會介紹的參數替換模板中的一種。
# (將替換模式設為空等價于刪除。)
# 更直接的處理方法就是先檢測預期的位置參數是否被傳入。
if [ -z $1 ]
then
exit $E_MISSING_POS_PARAM
fi
# 但是,正如 Fabin Kreutz 指出的,
#+ 上面的方法會有一些意想不到的副作用。
# 更好的方法是使用參數替換:
# ${1:-$DefaultVal}
# 詳情查看第十章“操作變量”的第二節“變量替換”。
```
樣例 4-6. *wh*, *whois* 域名查詢
```bash
#!/bin/bash
# ex18.sh
# 在下面三個可選的服務器中進行 whois 域名查詢:
# ripe.net, cw.net, radb.net
# 將這個腳本重命名為 'wh' 后放在 /usr/local/bin 目錄下
# 這個腳本需要進行符號鏈接:
# ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe
# ln -s /usr/local/bin/wh /usr/local/bin/wh-apnic
# ln -s /usr/local/bin/wh /usr/local/bin/wh-tucows
E_NOARGS=75
if [ -z "$1" ]
then
echo "Usage: `basename $0` [domain-name]"
exit $E_NOARGS
fi
# 檢查腳本名,訪問對應服務器進行查詢。
case `basename $0` in # 也可以寫: case ${0##*/} in
"wh" ) whois $1@whois.tucows.com;;
"wh-ripe" ) whois $1@whois.ripe.net;;
"wh-apnic" ) whois $1@whois.apnic.net;;
"wh-cw" ) whois $1@whois.cw.net;;
* ) echo "Usage: `basename $0` [domain-name]";;
esac
exit $?
```
使用 `shift` 命令可以將全體位置參數向左移一位, 重新賦值。
`$1 <--- $2`, `$2 <--- $3`, `$3 <--- $4`,以此類推。
原先的 `$1` 將會消失,而 `$0`(腳本名稱)不會有任何改變。如果你在腳本中使用了大量的位置參數,`shift` 可以讓你不使用{大括號}助記法也可以訪問超過10個的位置參數。
樣例 4-7. 使用 `shift` 命令
```bash
#!/bin/bash
# shft.sh: 使用 `shift` 命令步進訪問所有的位置參數。
# 將這個腳本命名為 shft.sh,然后在調用時跟上一些參數。
# 例如:
# sh shft.sh a b c def 83 barndoor
until [ -z "$1" ] # 直到訪問完所有的參數
do
echo -n "$1 "
shift
done
echo # 換行。
# 那些被訪問完的參數又會怎樣呢?
echo "$2"
# 什么都不會被打印出來。
# 當 $2 被移動到 $1 且沒有 $3 時,$2 將會保持空。
# 因此 shift 是移動參數而非復制參數。
exit
# 可以參考 echo-params.sh 腳本,在不使用 shift 命令的情況下,
#+ 步進訪問所有位置參數。
```
`shift` 命令也可以帶一個參數來指明一次移動多少位。
```bash
#!/bin/bash
# shift-past.sh
shift 3 # 移動3位。
# 與 n=3; shift $n 效果相同。
echo "$1"
exit 0
# ======================== #
$ sh shift-past.sh 1 2 3 4 5
4
# 但是就像 Eleni Fragkiadaki 指出的那樣,
# 如果嘗試將位置參數($#)傳給 'shift',
# 將會導致腳本錯誤的結束,同時位置參數也不會發送改變。
# 這也許是因為陷入了一個死循環...
# 比如:
# until [ -z "$1" ]
# do
# echo -n "$1 "
# shift 20 # 如果少于20個位置參數,
# done #+ 那么循環將永遠不會結束。
#
# 當你不確定是否有這么多的參數時,你可以加入一個測試:
# shift 20 || break
# ^^^^^^^^
```
>  使用 `shift` 命令同給函數傳參相類似。詳情查看樣例 36-18。
[^1]: 函數同樣也可以接受與使用位置參數。
[^2]: 是調用腳本的進程設置了 $0 參數。就是腳本的文件名。詳情可以查看 `execv` 的使用手冊。<br>在命令行中,$0 是shell的名稱。<pre>bash$ echo $0<br>bash<br><br>tcsh% echo $0<br>tcsh</pre>
[^3]: 如果腳本被引用(sourced)執行或者被鏈接(symlinked)執行時會失效。安全的方法是檢測變量 `$BASH_Source`。
- 第一部分 初見shell
- 1. 為什么使用shell編程
- 2. 和Sha-Bang(#!)一起出發
- 2.1 調用一個腳本
- 2.2 牛刀小試
- 第二部分 shell基礎
- 3. 特殊字符
- 4. 變量與參數
- 4.1 變量替換
- 4.2 變量賦值
- 4.3 Bash弱類型變量
- 4.4 特殊變量類型
- 5. 引用
- 5.1 引用變量
- 5.2 轉義
- 6. 退出與退出狀態
- 7. 測試
- 7.1 測試結構
- 7.2 文件測試操作
- 7.3 其他比較操作
- 7.4 嵌套 if/then 條件測試
- 7.5 牛刀小試
- 8. 運算符相關話題
- 8.1 運算符
- 8.2 數字常量
- 8.3 雙圓括號結構
- 8.4 運算符優先級
- 第三部分 shell進階
- 10. 變量處理
- 10.1 字符串處理
- 10.1.1 使用 awk 處理字符串
- 10.1.2 參考資料
- 10.2 參數替換
- 11. 循環與分支
- 11.1 循環
- 11.2 嵌套循環
- 11.3 循環控制
- 11.4 測試與分支
- 12. 命令替換
- 13. 算術擴展
- 14. 休息時間
- 第五部分 進階話題
- 19. 嵌入文檔
- 20. I/O 重定向
- 20.1 使用 exec
- 20.2 重定向代碼塊
- 20.3 應用程序
- 22. 限制模式的Shell
- 23. 進程替換
- 26. 列表結構
- 25. 別名