## 10.2 Shell 的變量功能
變量是 bash 環境中非常重要的一個玩意兒,我們知道 Linux 是多用戶多任務的環境,每個人登陸系統都能取得一個 bash shell, 每個人都能夠使用 bash 下達 mail 這個指令來收受“自己”的郵件等等。問題是, bash 是如何得知你的郵件信箱是哪個文件? 這就需要“變量”的幫助啦!所以,你說變量重不重要呢?下面我們將介紹重要的環境變量、變量的取用與設置等數據, 呼呼!動動腦時間又來到啰!^_^
### 10.2.1 什么是變量?
那么,什么是“變量”呢?簡單的說,就是讓某一個特定字串代表不固定的內容就是了。舉個大家在國中都會學到的數學例子, 那就是:“ y = ax + b ”這東西,在等號左邊的(y)就是變量,在等號右邊的(ax+b)就是變量內容。 要注意的是,左邊是未知數,右邊是已知數喔! 講的更簡單一點,我們可以“用一個簡單的 "字眼" 來取代另一個比較復雜或者是容易變動的數據”。這有什么好處啊?最大的好處就是“方便!”。
* 變量的可變性與方便性
舉例來說,我們每個帳號的郵件信箱默認是以 MAIL 這個變量來進行存取的, 當 dmtsai 這個使用者登陸時,他便會取得 MAIL 這個變量,而這個變量的內容其實就是 /var/spool/mail/dmtsai, 那如果 vbird 登陸呢?他取得的 MAIL 這個變量的內容其實就是 /var/spool/mail/vbird 。 而我們使用信件讀取指令 mail 來讀取自己的郵件信箱時,嘿嘿,這支程序可以直接讀取 MAIL 這個變量的內容, 就能夠自動的分辨出屬于自己的信箱信件啰!這樣一來,設計程序的設計師就真的很方便的啦!
圖10.2.1、程序、變量與不同使用者的關系
如上圖所示,由于系統已經幫我們規劃好 MAIL 這個變量,所以使用者只要知道 mail 這個指令如何使用即可, mail 會主動的取用 MAIL 這個變量,就能夠如上圖所示的取得自己的郵件信箱了!(注意大小寫,小寫的 mail 是指令, 大寫的 MAIL 則是變量名稱喔!)
那么使用變量真的比較好嗎?這是當然的!想像一個例子,如果 mail 這個指令將 root 收信的郵件信箱 (mailbox) 文件名為 /var/spool/mail/root 直接寫入程序碼中。那么當 dmtsai 要使用 mail 時,將會取得 /var/spool/mail/root 這個文件的內容! 不合理吧!所以你就需要幫 dmtsai 也設計一個 mail 的程序,將 /var/spool/mail/dmtsai 寫死到 mail 的程序碼當中! 天吶!那系統要有多少個 mail 指令啊?反過來說,使用變量就變的很簡單了!因為你不需要更動到程序碼啊! 只要將 MAIL 這個變量帶入不同的內容即可讓所有使用者通過 mail 取得自己的信件!當然簡單多了!
* 影響 bash 環境操作的變量
某些特定變量會影響到 bash 的環境喔!舉例來說,我們前面已經提到過很多次的那個 PATH 變量! 你能不能在任何目錄下執行某個指令,與 PATH 這個變量有很大的關系。例如你下達 ls 這個指令時,系統就是通過 PATH 這個變量里面的內容所記錄的路徑順序來搜尋指令的呢!如果在搜尋完 PATH 變量內的路徑還找不到 ls 這個指令時, 就會在屏幕上顯示“ command not found ”的錯誤訊息了。
如果說的學理一點,那么由于在 Linux System 下面,所有的線程都是需要一個執行碼, 而就如同上面提到的,你“真正以 shell 來跟 Linux 溝通,是在正確的登陸 Linux 之后!”這個時候你就有一個 bash 的執行程序,也才可以真正的經由 bash 來跟系統溝通啰!而在進入 shell 之前,也正如同上面提到的,由于系統需要一些變量來提供他數據的存取 (或者是一些環境的設置參數值, 例如是否要顯示彩色等等的) ,所以就有一些所謂的“環境變量” 需要來讀入系統中了!這些環境變量例如 PATH、HOME、MAIL、SHELL 等等,都是很重要的, 為了區別與自訂變量的不同,環境變量通常以大寫字符來表示呢!
* 腳本程序設計 (shell script) 的好幫手
這些還都只是系統默認的變量的目的,如果是個人的設置方面的應用呢:例如你要寫一個大型的 script 時,有些數據因為可能由于使用者習慣的不同而有差異,比如說路徑好了,由于該路徑在 script 被使用在相當多的地方,如果下次換了一部主機,都要修改 script 里面的所有路徑,那么我一定會瘋掉! 這個時候如果使用變量,而將該變量的定義寫在最前面,后面相關的路徑名稱都以變量來取代, 嘿嘿!那么你只要修改一行就等于修改整篇 script 了!方便的很!所以,良好的程序設計師都會善用變量的定義!
圖10.2.2、變量應用于 shell script 的示意圖
最后我們就簡單的對“什么是變量”作個簡單定義好了: “變量就是以一組文字或符號等,來取代一些設置或者是一串保留的數據!”, 例如:我設置了“myname”就是“VBird”,所以當你讀取 myname 這個變量的時候,系統自然就會知道!哈!那就是 VBird 啦! 那么如何“顯示變量”呢?這就需要使用到 echo 這個指令啦!
### 10.2.2 變量的取用與設置:echo, 變量設置規則, unset
說的口沫橫飛的,也不知道“變量”與“變量代表的內容”有啥關系? 那我們就將“變量”的“內容”拿出來給您瞧瞧好了。你可以利用 echo 這個指令來取用變量, 但是,變量在被取用時,前面必須要加上錢字號“ $ ”才行,舉例來說,要知道 PATH 的內容,該如何是好?
* 變量的取用: echo
```
[dmtsai@study ~]$ echo $variable
[dmtsai@study ~]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
[dmtsai@study ~]$ echo ${PATH} # 近年來,鳥哥比較偏向使用這種格式喔!
```
變量的取用就如同上面的范例,利用 echo 就能夠讀出,只是需要在變量名稱前面加上 $ , 或者是以 ${變量} 的方式來取用都可以!當然啦,那個 echo 的功能可是很多的, 我們這里單純是拿 echo 來讀出變量的內容而已,更多的 echo 使用,請自行給他 man echo 吧! ^_^
例題:請在屏幕上面顯示出您的環境變量 HOME 與 MAIL:答:`echo $HOME` 或者是 `echo ${HOME}`,`echo $MAIL` 或者是 `echo ${MAIL}`
現在我們知道了變量與變量內容之間的相關性了,好了,那么我要如何“設置”或者是“修改” 某個變量的內容啊?很簡單啦!用“等號(=)”連接變量與他的內容就好啦!舉例來說: 我要將 myname 這個變量名稱的內容設置為 VBird ,那么:
```
[dmtsai@study ~]$ echo ${myname}
<==這里并沒有任何數據~因為這個變量尚未被設置!是空的!
[dmtsai@study ~]$ myname=VBird
[dmtsai@study ~]$ echo ${myname}
VBird <==出現了!因為這個變量已經被設置了!
```
瞧!如此一來,這個變量名稱 myname 的內容就帶有 VBird 這個數據啰~ 而由上面的例子當中,我們也可以知道: 在 bash 當中,當一個變量名稱尚未被設置時,默認的內容是“空”的。 另外,變量在設置時,還是需要符合某些規定的,否則會設置失敗喔!這些規則如下所示啊!

**Tips** 要請各位讀者注意喔,每一種 shell 的語法都不相同~在變量的使用上,bash 在你沒有設置的變量中強迫去 echo 時,它會顯示出空的值。 在其他某些 shell 中,隨便去 echo 一個不存在的變量,它是會出現錯誤訊息的喔!要注意!要注意!
* 變量的設置規則
1. 變量與變量內容以一個等號“=”來鏈接,如下所示:
“myname=VBird”
2. 等號兩邊不能直接接空白字符,如下所示為錯誤:
“myname = VBird”或“myname=VBird Tsai”
3. 變量名稱只能是英文字母與數字,但是開頭字符不能是數字,如下為錯誤:
“2myname=VBird”
4. 變量內容若有空白字符可使用雙引號“"”或單引號“'”將變量內容結合起來,但
* 雙引號內的特殊字符如 $ 等,可以保有原本的特性,如下所示:
“var="lang is $LANG"”則“echo $var”可得“lang is zh_TW.UTF-8”
* 單引號內的特殊字符則僅為一般字符 (純文本),如下所示:
“var='lang is $LANG'”則“echo $var”可得“lang is $LANG”
5. 可用跳脫字符“ \ ”將特殊符號(如 [Enter], $, \, 空白字符, '等)變成一般字符,如:
“myname=VBird\ Tsai”
6. 在一串指令的執行中,還需要借由其他額外的指令所提供的信息時,可以使用反單引號“`指令`”或 “$(指令)”。特別注意,那個 ` 是鍵盤上方的數字鍵 1 左邊那個按鍵,而不是單引號! 例如想要取得核心版本的設置:
“version=$(uname -r)”再“echo $version”可得“3.10.0-229.el7.x86_64”
7. 若該變量為擴增變量內容時,則可用 "$變量名稱" 或 ${變量} 累加內容,如下所示:
“PATH="$PATH":/home/bin”或“PATH=${PATH}:/home/bin”
8. 若該變量需要在其他子程序執行,則需要以 export 來使變量變成環境變量:
“export PATH”
9. 通常大寫字符為系統默認變量,自行設置變量可以使用小寫字符,方便判斷 (純粹依照使用者興趣與嗜好) ;
10. 取消變量的方法為使用 unset :“unset 變量名稱”例如取消 myname 的設置:
“unset myname”
下面讓鳥哥舉幾個例子來讓你試看看,就知道怎么設置好你的變量啰!
```
范例一:設置一變量 name ,且內容為 VBird
[dmtsai@study ~]$ 12name=VBird
bash: 12name=VBird: command not found... <==屏幕會顯示錯誤!因為不能以數字開頭!
[dmtsai@study ~]$ name = VBird <==還是錯誤!因為有空白!
[dmtsai@study ~]$ name=VBird <==OK 的啦!
范例二:承上題,若變量內容為 VBird's name 呢,就是變量內容含有特殊符號時:
[dmtsai@study ~]$ name=VBird's name
# 單引號與雙引號必須要成對,在上面的設置中僅有一個單引號,因此當你按下 enter 后,
# 你還可以繼續輸入變量內容。這與我們所需要的功能不同,失敗啦!
# 記得,失敗后要復原請按下 [ctrl]-c 結束!
[dmtsai@study ~]$ name="VBird's name" <==OK 的啦!
# 指令是由左邊向右找→,先遇到的引號先有用,因此如上所示, 單引號變成一般字符!
[dmtsai@study ~]$ name='VBird's name' <==失敗的啦!
# 因為前兩個單引號已成對,后面就多了一個不成對的單引號了!因此也就失敗了!
[dmtsai@study ~]$ name=VBird\'s\ name <==OK 的啦!
# 利用反斜線 (\) 跳脫特殊字符,例如單引號與空白鍵,這也是 OK 的啦!
范例三:我要在 PATH 這個變量當中“累加”:/home/dmtsai/bin 這個目錄
[dmtsai@study ~]$ PATH=$PATH:/home/dmtsai/bin
[dmtsai@study ~]$ PATH="$PATH":/home/dmtsai/bin
[dmtsai@study ~]$ PATH=${PATH}:/home/dmtsai/bin
# 上面這三種格式在 PATH 里頭的設置都是 OK 的!但是下面的例子就不見得啰!
范例四:承范例三,我要將 name 的內容多出 "yes" 呢?
[dmtsai@study ~]$ name=$nameyes
# 知道了吧?如果沒有雙引號,那么變量成了啥?name 的內容是 $nameyes 這個變量!
# 呵呵!我們可沒有設置過 nameyes 這個變量吶!所以,應該是下面這樣才對!
[dmtsai@study ~]$ name="$name"yes
[dmtsai@study ~]$ name=${name}yes <==以此例較佳!
范例五:如何讓我剛剛設置的 name=VBird 可以用在下個 shell 的程序?
[dmtsai@study ~]$ name=VBird
[dmtsai@study ~]$ bash <==進入到所謂的子程序
[dmtsai@study ~]$ echo $name <==子程序:再次的 echo 一下;
<==嘿嘿!并沒有剛剛設置的內容喔!
[dmtsai@study ~]$ exit <==子程序:離開這個子程序
[dmtsai@study ~]$ export name
[dmtsai@study ~]$ bash <==進入到所謂的子程序
[dmtsai@study ~]$ echo $name <==子程序:在此執行!
VBird <==看吧!出現設置值了!
[dmtsai@study ~]$ exit <==子程序:離開這個子程序
```
什么是“子程序”呢?就是說,在我目前這個 shell 的情況下,去啟用另一個新的 shell ,新的那個 shell 就是子程序啦!在一般的狀態下,父程序的自訂變量是無法在子程序內使用的。但是通過 export 將變量變成環境變量后,就能夠在子程序下面應用了!很不賴吧!至于程序的相關概念, 我們會在[第十六章程序管理](../Text/index.html)當中提到的喔!
```
范例六:如何進入到您目前核心的模塊目錄?
[dmtsai@study ~]$ cd /lib/modules/`uname -r`/kernel
[dmtsai@study ~]$ cd /lib/modules/$(uname -r)/kernel # 以此例較佳!
```
每個 Linux 都能夠擁有多個核心版本,且幾乎 distribution 的核心版本都不相同。以 CentOS 7.1 (未更新前) 為例,他的默認核心版本是 3.10.0-229.el7.x86_64 ,所以核心模塊目錄在 /lib/modules/3.10.0-229.el7.x86_64/kernel/ 內。 也由于每個 distributions 的這個值都不相同,但是我們卻可以利用 uname -r 這個指令先取得版本信息。所以啰,就可以通過上面指令當中的內含指令 $(uname -r) 先取得版本輸出到 cd ... 那個指令當中,就能夠順利的進入目前核心的驅動程序所放置的目錄啰!很方便吧!
其實上面的指令可以說是作了兩次動作,亦即是:
1. 先進行反單引號內的動作“uname -r”并得到核心版本為 3.10.0-229.el7.x86_64
2. 將上述的結果帶入原指令,故得指令為:“cd /lib/modules/3.10.0-229.el7.x86_64/kernel/”

**Tips** 為什么鳥哥比較建議記憶 $( command ) 呢?還記得小時候學數學的加減乘除,我們都知道得要先乘除后加減。那如果硬要先加減再乘除呢? 當然就是加上括號 () 來處理即可啊!所以啰,這個指令的處理方式也差不多,只是括號左邊得要加個錢字號就是了!
```
范例七:取消剛剛設置的 name 這個變量內容
[dmtsai@study ~]$ unset name
```
根據上面的案例你可以試試看!就可以了解變量的設置啰!這個是很重要的呦!請勤加練習! 其中,較為重要的一些特殊符號的使用啰!例如單引號、雙引號、跳脫字符、錢字號、反單引號等等,下面的例題想一想吧!
例題:在變量的設置當中,單引號與雙引號的用途有何不同?
答:單引號與雙引號的最大不同在于雙引號仍然可以保有變量的內容,但單引號內僅能是一般字符 ,而不會有特殊符號。我們以下面的例子做說明:假設您定義了一個變量, name=VBird ,現在想以 name 這個變量的內容定義出 myname 顯示 VBird its me 這個內容,要如何訂定呢?
> [dmtsai@study ~]$ name=VBird
> [dmtsai@study ~]$ echo $name
> VBird
> [dmtsai@study ~]$ myname="$name its me"
> [dmtsai@study ~]$ echo $myname
> VBird its me
> [dmtsai@study ~]$ myname='$name its me'
> [dmtsai@study ~]$ echo $myname
> $name its me
發現了嗎?沒錯!使用了單引號的時候,那么 $name 將失去原有的變量內容,僅為一般字符的顯示型態而已!這里必需要特別小心在意!
例題:在指令下達的過程中,反單引號( ` )這個符號代表的意義為何?答:在一串指令中,在 ` 之內的指令將會被先執行,而其執行出來的結果將做為外部的輸入信息!例如 uname -r 會顯示出目前的核心版本,而我們的核心版本在 /lib/modules 里面,因此,你可以先執行 uname -r 找出核心版本,然后再以“ cd 目錄”到該目錄下,當然也可以執行如同上面范例六的執行內容啰。
另外再舉個例子,我們也知道, [locate](../Text/index.html#locate) 指令可以列出所有的相關文件文件名,但是,如果我想要知道各個文件的權限呢?舉例來說,我想要知道每個 crontab 相關文件名的權限:
> [dmtsai@study ~]$ ls -ld `locate crontab`
> [dmtsai@study ~]$ ls -ld $(locate crontab)
如此一來,先以 locate 將文件名數據都列出來,再以 ls 指令來處理的意思啦!瞭了嗎? ^_^
例題:若你有一個常去的工作目錄名稱為:“/cluster/server/work/taiwan_2015/003/”,如何進行該目錄的簡化?答:在一般的情況下,如果你想要進入上述的目錄得要“cd /cluster/server/work/taiwan_2015/003/”, 以鳥哥自己的案例來說,鳥哥跑數值模式常常會設置很長的目錄名稱(避免忘記),但如此一來變換目錄就很麻煩。 此時,鳥哥習慣利用下面的方式來降低指令下達錯誤的問題:
> [dmtsai@study ~]$ work="/cluster/server/work/taiwan_2015/003/"
> [dmtsai@study ~]$ cd $work
未來我想要使用其他目錄作為我的模式工作目錄時,只要變更 work 這個變量即可!而這個變量又可以在 [bash 的配置文件](../Text/index.html#settings_bashrc)(~/.bashrc)中直接指定,那我每次登陸只要執行“ cd $work ”就能夠去到數值模式仿真的工作目錄了!是否很方便呢? ^_^
### 10.2.3 環境變量的功能
環境變量可以幫我們達到很多功能~包括主文件夾的變換啊、提示字符的顯示啊、可執行文件搜尋的路徑啊等等的, 還有很多很多啦!那么,既然環境變量有那么多的功能,問一下,目前我的 shell 環境中, 有多少默認的環境變量啊?我們可以利用兩個指令來查閱,分別是 env 與 export 呢!
* 用 env 觀察環境變量與常見環境變量說明
```
范例一:列出目前的 shell 環境下的所有環境變量與其內容。
[dmtsai@study ~]$ env
HOSTNAME=study.centos.vbird <== 這部主機的主機名稱
TERM=xterm <== 這個終端機使用的環境是什么類型
SHELL=/bin/bash <== 目前這個環境下,使用的 Shell 是哪一個程序?
HISTSIZE=1000 <== “記錄指令的筆數”在 CentOS 默認可記錄 1000 筆
OLDPWD=/home/dmtsai <== 上一個工作目錄的所在
LC_ALL=en_US.utf8 <== 由于語系的關系,鳥哥偷偷丟上來的一個設置
USER=dmtsai <== 使用者的名稱啊!
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:
or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:
*.tar=01... <== 一些顏色顯示
MAIL=/var/spool/mail/dmtsai <== 這個使用者所取用的 mailbox 位置
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
PWD=/home/dmtsai <== 目前使用者所在的工作目錄 (利用 pwd 取出!)
LANG=zh_TW.UTF-8 <== 這個與語系有關,下面會再介紹!
HOME=/home/dmtsai <== 這個使用者的主文件夾啊!
LOGNAME=dmtsai <== 登陸者用來登陸的帳號名稱
_=/usr/bin/env <== 上一次使用的指令的最后一個參數(或指令本身)
```
env 是 environment (環境) 的簡寫啊,上面的例子當中,是列出來所有的環境變量。當然,如果使用 export 也會是一樣的內容~ 只不過, export 還有其他額外的功能就是了,我們等一下再提這個 export 指令。 那么上面這些變量有些什么功用呢?下面我們就一個一個來分析分析!
* HOME
代表使用者的主文件夾。還記得我們可以使用 cd ~ 去到自己的主文件夾嗎?或者利用 cd 就可以直接回到使用者主文件夾了。那就是取用這個變量啦~ 有很多程序都可能會取用到這個變量的值!
* SHELL
告知我們,目前這個環境使用的 SHELL 是哪支程序? Linux 默認使用 /bin/bash 的啦!
* HISTSIZE
這個與“歷史命令”有關,亦即是, 我們曾經下達過的指令可以被系統記錄下來,而記錄的“筆數”則是由這個值來設置的。
* MAIL
當我們使用 mail 這個指令在收信時,系統會去讀取的郵件信箱文件 (mailbox)。
* PATH
就是可執行文件搜尋的路徑啦~目錄與目錄中間以冒號(:)分隔, 由于文件的搜尋是依序由 PATH 的變量內的目錄來查詢,所以,目錄的順序也是重要的喔。
* LANG
這個重要!就是語系數據啰~很多訊息都會用到他, 舉例來說,當我們在啟動某些 perl 的程序語言文件時,他會主動的去分析語系數據文件, 如果發現有他無法解析的編碼語系,可能會產生錯誤喔!一般來說,我們中文編碼通常是 zh_TW.Big5 或者是 zh_TW.UTF-8,這兩個編碼偏偏不容易被解譯出來,所以,有的時候,可能需要修訂一下語系數據。 這部分我們會在下個小節做介紹的!
* RANDOM
這個玩意兒就是“隨機亂數”的變量啦!目前大多數的 distributions 都會有亂數產生器,那就是 /dev/random 這個文件。 我們可以通過這個亂數文件相關的變量 ($RANDOM) 來隨機取得亂數值喔。在 BASH 的環境下,這個 RANDOM 變量的內容,介于 0~32767 之間,所以,你只要 echo $RANDOM 時,系統就會主動的隨機取出一個介于 0~32767 的數值。萬一我想要使用 0~9 之間的數值呢?呵呵~利用 declare 宣告數值類型, 然后這樣做就可以了:
```
[dmtsai@study ~]$ declare -i number=$RANDOM*10/32768 ; echo $number
8 <== 此時會隨機取出 0~9 之間的數值喔!
```
大致上是有這些環境變量啦~里面有些比較重要的參數,在下面我們都會另外進行一些說明的~
* 用 set 觀察所有變量 (含環境變量與自訂變量)
bash 可不只有環境變量喔,還有一些與 bash 操作接口有關的變量,以及使用者自己定義的變量存在的。 那么這些變量如何觀察呢?這個時候就得要使用 set 這個指令了。 set 除了環境變量之外, 還會將其他在 bash 內的變量通通顯示出來哩!信息很多,下面鳥哥僅列出幾個重要的內容:
```
[dmtsai@study ~]$ set
BASH=/bin/bash <== bash 的主程序放置路徑
BASH_VERSINFO=([0]="4" [1]="2" [2]="46" [3]="1" [4]="release" [5]="x86_64-redhat-linux-gnu")
BASH_VERSION='4.2.46(1)-release' <== 這兩行是 bash 的版本啊!
COLUMNS=90 <== 在目前的終端機環境下,使用的字段有幾個字符長度
HISTFILE=/home/dmtsai/.bash_history <== 歷史命令記錄的放置文件,隱藏文件
HISTFILESIZE=1000 <== 存起來(與上個變量有關)的文件之指令的最大紀錄筆數。
HISTSIZE=1000 <== 目前環境下,內存中記錄的歷史命令最大筆數。
IFS=$' \t\n' <== 默認的分隔符號
LINES=20 <== 目前的終端機下的最大行數
MACHTYPE=x86_64-redhat-linux-gnu <== 安裝的機器類型
OSTYPE=linux-gnu <== 操作系統的類型!
PS1='[\u@\h \W]\$ ' <== PS1 就厲害了。這個是命令提示字符,也就是我們常見的
[root@www ~]# 或 [dmtsai ~]$ 的設置值啦!可以更動的!
PS2='> ' <== 如果你使用跳脫符號 (\) 第二行以后的提示字符也
$ <== 目前這個 shell 所使用的 PID
? <== 剛剛執行完指令的回傳值。
...
# 有許多可以使用的函數庫功能被鳥哥取消啰!請自行查閱!
```
一般來說,不論是否為環境變量,只要跟我們目前這個 shell 的操作接口有關的變量, 通常都會被設置為大寫字符,也就是說,“基本上,在 Linux 默認的情況中,使用{大寫的字母}來設置的變量一般為系統內定需要的變量”。 OK!OK!那么上頭那些變量當中,有哪些是比較重要的?大概有這幾個吧!
* PS1:(提示字符的設置)
這是 PS1 (數字的 1 不是英文字母),這個東西就是我們的“命令提示字符”喔! 當我們每次按下 [Enter] 按鍵去執行某個指令后,最后要再次出現提示字符時, 就會主動去讀取這個變量值了。上頭 PS1 內顯示的是一些特殊符號,這些特殊符號可以顯示不同的信息, 每個 distributions 的 bash 默認的 PS1 變量內容可能有些許的差異,不要緊,“習慣你自己的習慣”就好了。 你可以用 man bash [[3]](#ps3)去查詢一下 PS1 的相關說明,以理解下面的一些符號意義。
* \d :可顯示出“星期 月 日”的日期格式,如:"Mon Feb 2"
* \H :完整的主機名稱。舉例來說,鳥哥的練習機為“study.centos.vbird”
* \h :僅取主機名稱在第一個小數點之前的名字,如鳥哥主機則為“study”后面省略
* \t :顯示時間,為 24 小時格式的“HH:MM:SS”
* \T :顯示時間,為 12 小時格式的“HH:MM:SS”
* \A :顯示時間,為 24 小時格式的“HH:MM”
* \@ :顯示時間,為 12 小時格式的“am/pm”樣式
* \u :目前使用者的帳號名稱,如“dmtsai”;
* \v :BASH 的版本信息,如鳥哥的測試主機版本為 4.2.46(1)-release,僅取“4.2”顯示
* \w :完整的工作目錄名稱,由根目錄寫起的目錄名稱。但主文件夾會以 ~ 取代;
* \W :利用 basename 函數取得工作目錄名稱,所以僅會列出最后一個目錄名。
* \# :下達的第幾個指令。
* \$ :提示字符,如果是 root 時,提示字符為 # ,否則就是 $ 啰~
好了,讓我們來看看 CentOS 默認的 PS1 內容吧:“[\u@\h \W]\$ ”,現在你知道那些反斜線后的數據意義了吧? 要注意喔!那個反斜線后的數據為 PS1 的特殊功能,與 bash 的變量設置沒關系啦!不要搞混了喔! 那你現在知道為何你的命令提示字符是:“ [dmtsai@study ~]$ ”了吧?好了,那么假設我想要有類似下面的提示字符:
> [dmtsai@study /home/dmtsai 16:50 #12]$
那個 # 代表第 12 次下達的指令。那么應該如何設置 PS1 呢?可以這樣啊:
```
[dmtsai@study ~]$ cd /home
[dmtsai@study home]$ PS1='[\u@\h \w \A #\#]\$ '
[dmtsai@study /home 17:02 #85]$
# 看到了嗎?提示字符變了!變的很有趣吧!其中,那個 #85 比較有趣,
# 如果您再隨便輸入幾次 ls 后,該數字就會增加喔!為啥?上面有說明滴!
```
* $:(關于本 shell 的 PID)
錢字號本身也是個變量喔!這個咚咚代表的是“目前這個 Shell 的線程代號”,亦即是所謂的 PID (Process ID)。 更多的程序觀念,我們會在第四篇的時候提及。想要知道我們的 shell 的 PID ,就可以用:“ echo $$ ”即可!出現的數字就是你的 PID 號碼。
* ?:(關于上個執行指令的回傳值)
蝦密?問號也是一個特殊的變量?沒錯!在 bash 里面這個變量可重要的很! 這個變量是:“上一個執行的指令所回傳的值”, 上面這句話的重點是“上一個指令”與“回傳值”兩個地方。當我們執行某些指令時, 這些指令都會回傳一個執行后的代碼。一般來說,如果成功的執行該指令, 則會回傳一個 0 值,如果執行過程發生錯誤,就會回傳“錯誤代碼”才對!一般就是以非為 0 的數值來取代。 我們以下面的例子來看看:
```
[dmtsai@study ~]$ echo $SHELL
/bin/bash <==可順利顯示!沒有錯誤!
[dmtsai@study ~]$ echo $?
0 <==因為沒問題,所以回傳值為 0
[dmtsai@study ~]$ 12name=VBird
bash: 12name=VBird: command not found... <==發生錯誤了!bash回報有問題
[dmtsai@study ~]$ echo $?
127 <==因為有問題,回傳錯誤代碼(非為0)
# 錯誤代碼回傳值依據軟件而有不同,我們可以利用這個代碼來搜尋錯誤的原因喔!
[dmtsai@study ~]$ echo $?
0
# 咦!怎么又變成正確了?這是因為 "?" 只與“上一個執行指令”有關,
# 所以,我們上一個指令是執行“ echo $? ”,當然沒有錯誤,所以是 0 沒錯!
```
* OSTYPE, HOSTTYPE, MACHTYPE:(主機硬件與核心的等級)
我們在[第零章、計算機概論內的 CPU 等級](../Text/index.html#pc_cpu)說明中談過 CPU , 目前個人計算機的 CPU 主要分為 32/64 位,其中 32 位又可分為 i386, i586, i686,而 64 位則稱為 x86_64。 由于不同等級的 CPU 指令集不太相同,因此你的軟件可能會針對某些 CPU 進行最優化,以求取較佳的軟件性能。 所以軟件就有 i386, i686 及 x86_64 之分。以目前 (2015) 的主流硬件來說,幾乎都是 x86_64 的天下! 因此 CentOS 7 開始,已經不支持 i386 相容模式的安裝光盤了~哇嗚!進步的太快了!
要留意的是,較高階的硬件通常會向下相容舊有的軟件,但較高階的軟件可能無法在舊機器上面安裝! 我們在[第二章](../Text/index.html#beforeinstall_distro)就曾說明過, 這里再強調一次,你可以在 x86_64 的硬件上安裝 i386 的 Linux 操作系統,但是你無法在 i686 的硬件上安裝 x86_64 的 Linux 操作系統!這點得要牢記在心!
* export: 自訂變量轉成環境變量
談了 env 與 set 現在知道有所謂的環境變量與自訂變量,那么這兩者之間有啥差異呢?其實這兩者的差異在于“ 該變量是否會被子程序所繼續引用”啦!唔!那么啥是父程序?子程序? 這就得要了解一下指令的下達行為了。
當你登陸 Linux 并取得一個 bash 之后,你的 bash 就是一個獨立的程序,這個程序的識別使用的是一個稱為程序識別碼,被稱為 PID 的就是。 接下來你在這個 bash 下面所下達的任何指令都是由這個 bash 所衍生出來的,那些被下達的指令就被稱為子程序了。 我們可以用下面的圖示來簡單的說明一下父程序與子程序的概念:
圖10.2.3、程序相關性示意圖
如上所示,我們在原本的 bash 下面執行另一個 bash ,結果操作的環境接口會跑到第二個 bash 去(就是子程序), 那原本的 bash 就會在暫停的情況 (睡著了,就是 sleep)。整個指令運行的環境是實線的部分!若要回到原本的 bash 去, 就只有將第二個 bash 結束掉 (下達 exit 或 logout) 才行。更多的程序概念我們會在第四篇談及,這里只要有這個概念即可。
這個程序概念與變量有啥關系啊?關系可大了!因為子程序僅會繼承父程序的環境變量, 子程序不會繼承父程序的自訂變量啦!所以你在原本 bash 的自訂變量在進入了子程序后就會消失不見, 一直到你離開子程序并回到原本的父程序后,這個變量才會又出現!
換個角度來想,也就是說,如果我能將自訂變量變成環境變量的話,那不就可以讓該變量值繼續存在于子程序了? 呵呵!沒錯!此時,那個 export 指令就很有用啦!如你想要讓該變量內容繼續的在子程序中使用,那么就請執行:
```
[dmtsai@study ~]$ export 變量名稱
```
這東西用在“分享自己的變量設置給后來調用的文件或其他程序”啦! 像鳥哥常常在自己的主文件后面調用其他附屬文件(類似函數的功能),但是主文件與附屬文件內都有相同的變量名稱, 若一再重復設置時,要修改也很麻煩,此時只要在原本的第一個文件內設置好“ export 變量 ”, 后面所調用的文件就能夠使用這個變量設置了!而不需要重復設置,這非常實用于 shell script 當中喔! 如果僅下達 export 而沒有接變量時,那么此時將會把所有的“環境變量”秀出來喔!例如:
```
[dmtsai@study ~]$ export
declare -x HISTSIZE="1000"
declare -x HOME="/home/dmtsai"
declare -x HOSTNAME="study.centos.vbird"
declare -x LANG="zh_TW.UTF-8"
declare -x LC_ALL="en_US.utf8"
# 后面的鳥哥就都直接省略了!不然....浪費版面~ ^_^
```
那如何將環境變量轉成自訂變量呢?可以使用本章后續介紹的 [declare](../Text/index.html#declare) 呢!
### 10.2.4 影響顯示結果的語系變量 (locale)
還記得我們在[第四章里面提到的語系問題](../Text/index.html#cmd_cmd_lang)嗎? 就是當我們使用 man command 的方式去查詢某個數據的說明文檔時,該說明文檔的內容可能會因為我們使用的語系不同而產生亂碼。 另外,利用 ls 查詢文件的時間時,也可能會有亂碼出現在時間的部分。那個問題其實就是語系的問題啦。
目前大多數的 Linux distributions 已經都是支持日漸流行的萬國碼了,也都支持大部分的國家語系。 那么我們的 Linux 到底支持了多少的語系呢?這可以由 locale 這個指令來查詢到喔!
```
[dmtsai@study ~]$ locale -a
....(前面省略)....
zh_TW
zh_TW.big5 <==大五碼的中文編碼
zh_TW.euctw
zh_TW.utf8 <==萬國碼的中文編碼
zu_ZA
zu_ZA.iso88591
zu_ZA.utf8
```
正體中文語系至少支持了兩種以上的編碼,一種是目前還是很常見的 big5 ,另一種則是越來越熱門的 utf-8 編碼。 那么我們如何修訂這些編碼呢?其實可以通過下面這些變量的說:
```
[dmtsai@study ~]$ locale <==后面不加任何選項與參數即可!
LANG=en_US <==主語言的環境
LC_CTYPE="en_US" <==字符(文字)辨識的編碼
LC_NUMERIC="en_US" <==數字系統的顯示訊息
LC_TIME="en_US" <==時間系統的顯示數據
LC_COLLATE="en_US" <==字串的比較與排序等
LC_MONETARY="en_US" <==幣值格式的顯示等
LC_MESSAGES="en_US" <==訊息顯示的內容,如功能表、錯誤訊息等
LC_ALL= <==整體語系的環境
....(后面省略)....
```
基本上,你可以逐一設置每個與語系有關的變量數據,但事實上,如果其他的語系變量都未設置, 且你有設置 LANG 或者是 LC_ALL 時,則其他的語系變量就會被這兩個變量所取代! 這也是為什么我們在 Linux 當中,通常說明僅設置 LANG 或 LC_ALL 這兩個變量而已,因為他是最主要的設置變量! 好了,那么你應該要覺得奇怪的是,為什么在 Linux 主機的終端機接口 (tty1 ~ tty6) 的環境下,如果設置“ LANG=zh_TW.utf8 ”這個設置值生效后,使用 man 或者其他訊息輸出時, 都會有一堆亂碼,尤其是使用 ls -l 這個參數時?
因為在 Linux 主機的終端機接口環境下是無法顯示像中文這么復雜的編碼文字, 所以就會產生亂碼了。也就是如此,我們才會必須要在 tty1 ~ tty6 的環境下, 加裝一些中文化接口的軟件,才能夠看到中文啊!不過,如果你是在 MS Windows 主機以遠端連線服務器的軟件連線到主機的話,那么,嘿嘿!其實命令行確實是可以看到中文的。 此時反而你得要在 LC_ALL 設置中文編碼才好呢!

**Tips** 無論如何,如果發生一些亂碼的問題,那么設置系統里面保有的語系編碼, 例如: en_US 或 en_US.utf8 等等的設置,應該就 OK 的啦!好了,那么系統默認支持多少種語系呢? 當我們使用 locale 時,系統是列出目前 Linux 主機內保有的語系文件, 這些語系文件都放置在: /usr/lib/locale/ 這個目錄中。
你當然可以讓每個使用者自己去調整自己喜好的語系,但是整體系統默認的語系定義在哪里呢? 其實就是在 /etc/locale.conf 啰!這個文件在 CentOS 7.x 的內容有點像這樣:
```
[dmtsai@study ~]$ cat /etc/locale.conf
LANG=zh_TW.utf8
LC_NUMERIC=zh_TW.UTF-8
LC_TIME=zh_TW.UTF-8
LC_MONETARY=zh_TW.UTF-8
LC_PAPER=zh_TW.UTF-8
LC_MEASUREMENT=zh_TW.UTF-8
```
因為鳥哥在[第三章的安裝時](../Text/index.html)選擇的是中文語系安裝畫面, 所以這個文件默認就會使用中文編碼啦!你也可以自行將他改成你想要的語系編碼即可。

**Tips** 假設你有一個純文本原本是在 Windows 下面創建的,那么這個文件默認可能是 big5 的編碼格式。 在你將這個文件上傳到 Linux 主機后,在 X window 下面打開時,咦!怎么中文字通通變成亂碼了? 別擔心!因為如上所示, Linux 目前大多默認是萬國碼顯示嘛!你只要將打開該文件的軟件編碼由 utf8 改成 big5 就能夠看到正確的中文了!
例題:鳥哥原本是中文語系,所有顯示的數據通通是中文。但為了網頁顯示的關系,需要將輸出轉成英文 (en_US.utf8) 的語系來展示才行。 但鳥哥又不想要寫入配置文件!畢竟是暫時顯示用的~那該如何處理?答:其實不很難,重點是 LANG 及 LC_ALL 而已!但在 CentOS 7 當中,你要讓 LC_ALL 生效時,得要使用 export 轉成環境變量才行耶! 所以就是這樣搞:
```
[dmtsai@study ~]$ locale
LANG=zh_TW.UTF-8
LC_CTYPE="zh_TW.UTF-8"
LC_NUMERIC="zh_TW.UTF-8"
LC_TIME="zh_TW.UTF-8"
[dmtsai@study ~]$ LANG=en_US.utf8; locale
[dmtsai@study ~]$ export LC_ALL=en_US.utf8; locale # 你就會看到與上頭有不同的語系啰!
```
### 10.2.5 變量的有效范圍
蝦密?變量也有使用的“范圍”?沒錯啊~我們在上頭的 [export](../Text/index.html#export) 指令說明中,就提到了這個概念了。如果在跑程序的時候,有父程序與子程序的不同程序關系時, 則“變量”可否被引用與 export 有關。被 export 后的變量,我們可以稱他為“環境變量”! 環境變量可以被子程序所引用,但是其他的自訂變量內容就不會存在于子程序中。

**Tips** 在某些不同的書籍會談到“全域變量, global variable”與“區域變量, local variable”。 在鳥哥的這個章節中,基本上你可以這樣看待:
環境變量=全域變量
自訂變量=區域變量
在學理方面,為什么環境變量的數據可以被子程序所引用呢?這是因為內存配置的關系!理論上是這樣的:
* 當啟動一個 shell,操作系統會分配一記憶區塊給 shell 使用,此內存內之變量可讓子程序取用
* 若在父程序利用 export 功能,可以讓自訂變量的內容寫到上述的記憶區塊當中(環境變量);
* 當載入另一個 shell 時 (亦即啟動子程序,而離開原本的父程序了),子 shell 可以將父 shell 的環境變量所在的記憶區塊導入自己的環境變量區塊當中。
通過這樣的關系,我們就可以讓某些變量在相關的程序之間存在,以幫助自己更方便的操作環境喔! 不過要提醒的是,這個“環境變量”與“bash 的操作環境”意思不太一樣,舉例來說, PS1 并不是環境變量, 但是這個 PS1 會影響到 bash 的接口 (提示字符嘛)!相關性要厘清喔!^_^
### 10.2.6 變量鍵盤讀取、陣列與宣告: read, array, declare
我們上面提到的變量設置功能,都是由命令行直接設置的,那么,可不可以讓使用者能夠經由鍵盤輸入? 什么意思呢?是否記得某些程序執行的過程當中,會等待使用者輸入 "yes/no" 之類的訊息啊? 在 bash 里面也有相對應的功能喔!此外,我們還可以宣告這個變量的屬性,例如:陣列或者是數字等等的。下面就來看看吧!
* read
要讀取來自鍵盤輸入的變量,就是用 read 這個指令了。這個指令最常被用在 shell script 的撰寫當中, 想要跟使用者對談?用這個指令就對了。關于 script 的寫法,我們會在第十三章介紹,下面先來瞧一瞧 read 的相關語法吧!
```
[dmtsai@study ~]$ read [-pt] variable
選項與參數:
-p :后面可以接提示字符!
-t :后面可以接等待的“秒數!”這個比較有趣~不會一直等待使用者啦!
范例一:讓使用者由鍵盤輸入一內容,將該內容變成名為 atest 的變量
[dmtsai@study ~]$ read atest
This is a test <==此時光標會等待你輸入!請輸入左側文字看看
[dmtsai@study ~]$ echo ${atest}
This is a test <==你剛剛輸入的數據已經變成一個變量內容!
范例二:提示使用者 30 秒內輸入自己的大名,將該輸入字串作為名為 named 的變量內容
[dmtsai@study ~]$ read -p "Please keyin your name: " -t 30 named
Please keyin your name: VBird Tsai <==注意看,會有提示字符喔!
[dmtsai@study ~]$ echo ${named}
VBird Tsai <==輸入的數據又變成一個變量的內容了!
```
read 之后不加任何參數,直接加上變量名稱,那么下面就會主動出現一個空白行等待你的輸入(如范例一)。 如果加上 -t 后面接秒數,例如上面的范例二,那么 30 秒之內沒有任何動作時, 該指令就會自動略過了~如果是加上 -p ,嘿嘿!在輸入的光標前就會有比較多可以用的提示字符給我們參考! 在指令的下達里面,比較美觀啦! ^_^
* declare / typeset
declare 或 typeset 是一樣的功能,就是在“宣告變量的類型”。如果使用 declare 后面并沒有接任何參數,那么 bash 就會主動的將所有的變量名稱與內容通通叫出來,就好像使用 set 一樣啦! 那么 declare 還有什么語法呢?看看先:
```
[dmtsai@study ~]$ declare [-aixr] variable
選項與參數:
-a :將后面名為 variable 的變量定義成為陣列 (array) 類型
-i :將后面名為 variable 的變量定義成為整數數字 (integer) 類型
-x :用法與 export 一樣,就是將后面的 variable 變成環境變量;
-r :將變量設置成為 readonly 類型,該變量不可被更改內容,也不能 unset
范例一:讓變量 sum 進行 100+300+50 的加總結果
[dmtsai@study ~]$ sum=100+300+50
[dmtsai@study ~]$ echo ${sum}
100+300+50 <==咦!怎么沒有幫我計算加總?因為這是文字體態的變量屬性啊!
[dmtsai@study ~]$ declare -i sum=100+300+50
[dmtsai@study ~]$ echo ${sum}
450 <==瞭乎??
```
由于在默認的情況下面, bash 對于變量有幾個基本的定義:
* 變量類型默認為“字串”,所以若不指定變量類型,則 1+2 為一個“字串”而不是“計算式”。 所以上述第一個執行的結果才會出現那個情況的;
* bash 環境中的數值運算,默認最多僅能到達整數形態,所以 1/3 結果是 0;
現在你曉得為啥你需要進行變量宣告了吧?如果需要非字串類型的變量,那就得要進行變量的宣告才行啦! 下面繼續來玩些其他的 declare 功能。
```
范例二:將 sum 變成環境變量
[dmtsai@study ~]$ declare -x sum
[dmtsai@study ~]$ export | grep sum
declare -ix sum="450" <==果然出現了!包括有 i 與 x 的宣告!
范例三:讓 sum 變成只讀屬性,不可更動!
[dmtsai@study ~]$ declare -r sum
[dmtsai@study ~]$ sum=tesgting
-bash: sum: readonly variable <==老天爺~不能改這個變量了!
范例四:讓 sum 變成非環境變量的自訂變量吧!
[dmtsai@study ~]$ declare +x sum <== 將 - 變成 + 可以進行“取消”動作
[dmtsai@study ~]$ declare -p sum <== -p 可以單獨列出變量的類型
declare -ir sum="450" <== 看吧!只剩下 i, r 的類型,不具有 x 啰!
```
declare 也是個很有用的功能~尤其是當我們需要使用到下面的陣列功能時, 他也可以幫我們宣告陣列的屬性喔!不過,老話一句,陣列也是在 shell script 比較常用的啦! 比較有趣的是,如果你不小心將變量設置為“只讀”,通常得要登出再登陸才能復原該變量的類型了! @_@
* 陣列 (array) 變量類型
某些時候,我們必須使用陣列來宣告一些變量,這有什么好處啊?在一般人的使用上, 果然是看不出來有什么好處的!不過,如果您曾經寫過程序的話,那才會比較了解陣列的意義~ 陣列對寫數值程序的設計師來說,可是不能錯過學習的重點之一哩!好!不啰唆~ 那么要如何設置陣列的變量與內容呢?在 bash 里頭,陣列的設置方式是:
> var[index]=content
意思是說,我有一個陣列名稱為 var ,而這個陣列的內容為 var[1]=小明, var[2]=大明, var[3]=好明 .... 等等,那個 index 就是一些數字啦,重點是用中刮號 ([ ]) 來設置的。 目前我們 bash 提供的是一維陣列。老實說,如果您不必寫一些復雜的程序, 那么這個陣列的地方,可以先略過,等到有需要再來學習即可!因為要制作出陣列, 通常與循環或者其他判斷式交互使用才有比較高的存在意義!
```
范例:設置上面提到的 var[1] ~ var[3] 的變量。
[dmtsai@study ~]$ var[1]="small min"
[dmtsai@study ~]$ var[2]="big min"
[dmtsai@study ~]$ var[3]="nice min"
[dmtsai@study ~]$ echo "${var[1]}, ${var[2]}, ${var[3]}"
small min, big min, nice min
```
陣列的變量類型比較有趣的地方在于“讀取”,一般來說,建議直接以 ${陣列} 的方式來讀取,比較正確無誤的啦!這也是為啥鳥哥一開始就建議你使用 ${變量} 來記憶的原因!
### 10.2.7 與文件系統及程序的限制關系: ulimit
想像一個狀況:我的 Linux 主機里面同時登陸了十個人,這十個人不知怎么搞的, 同時打開了 100 個文件,每個文件的大小約 10MBytes ,請問一下, 我的 Linux 主機的內存要有多大才夠? 10*100*10 = 10000 MBytes = 10GBytes ... 老天爺,這樣,系統不掛點才有鬼哩!為了要預防這個情況的發生,所以我們的 bash 是可以“限制使用者的某些系統資源”的,包括可以打開的文件數量, 可以使用的 CPU 時間,可以使用的內存總量等等。如何設置?用 ulimit 吧!
```
[dmtsai@study ~]$ ulimit [-SHacdfltu] [配額]
選項與參數:
-H :hard limit ,嚴格的設置,必定不能超過這個設置的數值;
-S :soft limit ,警告的設置,可以超過這個設置值,但是若超過則有警告訊息。
在設置上,通常 soft 會比 hard 小,舉例來說,soft 可設置為 80 而 hard
設置為 100,那么你可以使用到 90 (因為沒有超過 100),但介于 80~100 之間時,
系統會有警告訊息通知你!
-a :后面不接任何選項與參數,可列出所有的限制額度;
-c :當某些程序發生錯誤時,系統可能會將該程序在內存中的信息寫成文件(除錯用),
這種文件就被稱為核心文件(core file)。此為限制每個核心文件的最大容量。
-f :此 shell 可以創建的最大文件大小(一般可能設置為 2GB)單位為 KBytes
-d :程序可使用的最大斷裂內存(segment)容量;
-l :可用于鎖定 (lock) 的內存量
-t :可使用的最大 CPU 時間 (單位為秒)
-u :單一使用者可以使用的最大程序(process)數量。
范例一:列出你目前身份(假設為一般帳號)的所有限制數據數值
[dmtsai@study ~]$ ulimit -a
core file size (blocks, -c) 0 <==只要是 0 就代表沒限制
data seg size (kBytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited <==可創建的單一文件的大小
pending signals (-i) 4903
max locked memory (kBytes, -l) 64
max memory size (kBytes, -m) unlimited
open files (-n) 1024 <==同時可打開的文件數量
pipe size (512 Bytes, -p) 8
POSIX message queues (Bytes, -q) 819200
real-time priority (-r) 0
stack size (kBytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kBytes, -v) unlimited
file locks (-x) unlimited
范例二:限制使用者僅能創建 10MBytes 以下的容量的文件
[dmtsai@study ~]$ ulimit -f 10240
[dmtsai@study ~]$ ulimit -a | grep 'file size'
core file size (blocks, -c) 0
file size (blocks, -f) 10240 <==最大量為10240Kbyes,相當10MBytes
[dmtsai@study ~]$ dd if=/dev/zero of=123 bs=1M count=20
File size limit exceeded (core dumped) <==嘗試創建 20MB 的文件,結果失敗了!
[dmtsai@study ~]$ rm 123 <==趕快將這個文件刪除啰!同時你得要登出再次的登陸才能解開 10M 的限制
```
還記得我們在[第七章 Linux 磁盤文件系統](../Text/index.html)里面提到過,單一 filesystem 能夠支持的單一文件大小與 block 的大小有關。但是文件系統的限制容量都允許的太大了!如果想要讓使用者創建的文件不要太大時, 我們是可以考慮用 ulimit 來限制使用者可以創建的文件大小喔!利用 ulimit -f 就可以來設置了!例如上面的范例二,要注意單位喔!單位是 KBytes。 若改天你一直無法創建一個大容量的文件,記得瞧一瞧 ulimit 的信息喔!

**Tips** 想要復原 ulimit 的設置最簡單的方法就是登出再登陸,否則就是得要重新以 ulimit 設置才行! 不過,要注意的是,一般身份使用者如果以 ulimit 設置了 -f 的文件大小, 那么他“只能繼續減小文件大小,不能增加文件大小喔!”另外,若想要管控使用者的 ulimit 限值, 可以參考[第十三章的 pam](../Text/index.html#limits) 的介紹。
### 10.2.8 變量內容的刪除、取代與替換 (Optional)
變量除了可以直接設置來修改原本的內容之外,有沒有辦法通過簡單的動作來將變量的內容進行微調呢? 舉例來說,進行變量內容的刪除、取代與替換等!是可以的!我們可以通過幾個簡單的小步驟來進行變量內容的微調喔! 下面就來試試看!
* 變量內容的刪除與取代
變量的內容可以很簡單的通過幾個咚咚來進行刪除喔!我們使用 PATH 這個變量的內容來做測試好了。 請你依序進行下面的幾個例子來玩玩,比較容易感受的到鳥哥在這里想要表達的意義:
```
范例一:先讓小寫的 path 自訂變量設置的與 PATH 內容相同
[dmtsai@study ~]$ path=${PATH}
[dmtsai@study ~]$ echo ${path}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
范例二:假設我不喜歡 local/bin,所以要將前 1 個目錄刪除掉,如何顯示?
[dmtsai@study ~]$ echo ${path#/*local/bin:}
/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
```
上面這個范例很有趣的!他的重點可以用下面這張表格來說明:
```
${variable#/*local/bin:}
上面的特殊字體部分是關鍵字!用在這種刪除模式所必須存在的
${variable#/*local/bin:}
這就是原本的變量名稱,以上面范例二來說,這里就填寫 path 這個“變量名稱”啦!
${variable#/*local/bin:}
這是重點!代表“從變量內容的最前面開始向右刪除”,且僅刪除最短的那個
${variable#/*local/bin:}
代表要被刪除的部分,由于 # 代表由前面開始刪除,所以這里便由開始的 / 寫起。
需要注意的是,我們還可以通過萬用字符 * 來取代 0 到無窮多個任意字符
以上面范例二的結果來看, path 這個變量被刪除的內容如下所示:
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
```
很有趣吧!這樣了解了 # 的功能了嗎?接下來讓我們來看看下面的范例三!
```
范例三:我想要刪除前面所有的目錄,僅保留最后一個目錄
[dmtsai@study ~]$ echo ${path#/*:}
/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
# 由于一個 # 僅刪除掉最短的那個,因此他刪除的情況可以用下面的刪除線來看:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
[dmtsai@study ~]$ echo ${path##/*:}
/home/dmtsai/bin
# 嘿!多加了一個 # 變成 ## 之后,他變成“刪除掉最長的那個數據”!亦即是:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
```
非常有趣!不是嗎?因為在 PATH 這個變量的內容中,每個目錄都是以冒號“:”隔開的, 所以要從頭刪除掉目錄就是介于斜線 (/) 到冒號 (:) 之間的數據!但是 PATH 中不止一個冒號 (:) 啊! 所以 # 與 ## 就分別代表:
* # :符合取代文字的“最短的”那一個;
* ##:符合取代文字的“最長的”那一個
上面談到的是“從前面開始刪除變量內容”,那么如果想要“從后面向前刪除變量內容”呢? 這個時候就得使用百分比 (%) 符號了!來看看范例四怎么做吧!
```
范例四:我想要刪除最后面那個目錄,亦即從 : 到 bin 為止的字串
[dmtsai@study ~]$ echo ${path%:*bin}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin
# 注意啊!最后面一個目錄不見去!
# 這個 % 符號代表由最后面開始向前刪除!所以上面得到的結果其實是來自如下:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
范例五:那如果我只想要保留第一個目錄呢?
[dmtsai@study ~]$ echo ${path%%:*bin}
/usr/local/bin
# 同樣的, %% 代表的則是最長的符合字串,所以結果其實是來自如下:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
```
由于我是想要由變量內容的后面向前面刪除,而我這個變量內容最后面的結尾是“/home/dmtsai/bin”, 所以你可以看到上面我刪除的數據最終一定是“bin”,亦即是“:*bin”那個 * 代表萬用字符! 至于 % 與 %% 的意義其實與 # 及 ## 類似!這樣理解否?
例題:假設你是 dmtsai ,那你的 MAIL 變量應該是 /var/spool/mail/dmtsai 。假設你只想要保留最后面那個文件名 (dmtsai), 前面的目錄名稱都不要了,如何利用 $MAIL 變量來達成?答:題意其實是這樣“/var/spool/mail/dmtsai”,亦即刪除掉兩條斜線間的所有數據(最長符合)。 這個時候你就可以這樣做即可:
```
[dmtsai@study ~]$ echo ${MAIL##/*/}
```
相反的,如果你只想要拿掉文件名,保留目錄的名稱,亦即是“/var/spool/mail/dmtsai” (最短符合)。但假設你并不知道結尾的字母為何,此時你可以利用萬用字符來處理即可,如下所示:
```
[dmtsai@study ~]$ echo ${MAIL%/*}
```
了解了刪除功能后,接下來談談取代吧!繼續玩玩范例六啰!
```
范例六:將 path 的變量內容內的 sbin 取代成大寫 SBIN:
[dmtsai@study ~]$ echo ${path/sbin/SBIN}
/usr/local/bin:/usr/bin:/usr/local/SBIN:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
# 這個部分就容易理解的多了!關鍵字在于那兩個斜線,兩斜線中間的是舊字串
# 后面的是新字串,所以結果就會出現如上述的特殊字體部分啰!
[dmtsai@study ~]$ echo ${path//sbin/SBIN}
/usr/local/bin:/usr/bin:/usr/local/SBIN:/usr/SBIN:/home/dmtsai/.local/bin:/home/dmtsai/bin
# 如果是兩條斜線,那么就變成所有符合的內容都會被取代喔!
```
我們將這部份作個總結說明一下:
| 變量設置方式 | 說明 |
| --- | --- | --- |
| `${變量#關鍵字}` `${變量##關鍵字}` | 若變量內容從頭開始的數據符合“關鍵字”,則將符合的最短數據刪除 若變量內容從頭開始的數據符合“關鍵字”,則將符合的最長數據刪除 |
| `${變量%關鍵字}` `${變量%%關鍵字}` | 若變量內容從尾向前的數據符合“關鍵字”,則將符合的最短數據刪除 若變量內容從尾向前的數據符合“關鍵字”,則將符合的最長數據刪除 |
| `${變量/舊字串/新字串}` `${變量//舊字串/新字串}` | 若變量內容符合“舊字串”則“第一個舊字串會被新字串取代” 若變量內容符合“舊字串”則“全部的舊字串會被新字串取代” |
* 變量的測試與內容替換
在某些時刻我們常常需要“判斷”某個變量是否存在,若變量存在則使用既有的設置,若變量不存在則給予一個常用的設置。 我們舉下面的例子來說明好了,看看能不能較容易被你所理解呢!
```
范例一:測試一下是否存在 username 這個變量,若不存在則給予 username 內容為 root
[dmtsai@study ~]$ echo ${username}
<==由于出現空白,所以 username 可能不存在,也可能是空字串
[dmtsai@study ~]$ username=${username-root}
[dmtsai@study ~]$ echo ${username}
root <==因為 username 沒有設置,所以主動給予名為 root 的內容。
[dmtsai@study ~]$ username="vbird tsai" <==主動設置 username 的內容
[dmtsai@study ~]$ username=${username-root}
[dmtsai@study ~]$ echo ${username}
vbird tsai <==因為 username 已經設置了,所以使用舊有的設置而不以 root 取代
```
在上面的范例中,重點在于減號“ - ”后面接的關鍵字!基本上你可以這樣理解:
```
new_var=${old_var-content}
新的變量,主要用來取代舊變量。新舊變量名稱其實常常是一樣的
new_var=${old_var-content}
這是本范例中的關鍵字部分!必須要存在的哩!
new_var=${old_var-content}
舊的變量,被測試的項目!
new_var=${old_var-content}
變量的“內容”,在本范例中,這個部分是在“給予未設置變量的內容”
```
不過這還是有點問題!因為 username 可能已經被設置為“空字串”了!果真如此的話,那你還可以使用下面的范例來給予 username 的內容成為 root 喔!
```
范例二:若 username 未設置或為空字串,則將 username 內容設置為 root
[dmtsai@study ~]$ username=""
[dmtsai@study ~]$ username=${username-root}
[dmtsai@study ~]$ echo ${username}
<==因為 username 被設置為空字串了!所以當然還是保留為空字串!
[dmtsai@study ~]$ username=${username:-root}
[dmtsai@study ~]$ echo ${username}
root <==加上“ : ”后若變量內容為空或者是未設置,都能夠以后面的內容替換!
```
在大括號內有沒有冒號“ : ”的差別是很大的!加上冒號后,被測試的變量未被設置或者是已被設置為空字串時, 都能夠用后面的內容 (本例中是使用 root 為內容) 來替換與設置!這樣可以了解了嗎?除了這樣的測試之外, 還有其他的測試方法喔!鳥哥將他整理如下:

**Tips** 下面的例子當中,那個 var 與 str 為變量,我們想要針對 str 是否有設置來決定 var 的值喔! 一般來說, str: 代表“str 沒設置或為空的字串時”;至于 str 則僅為“沒有該變量”。
| 變量設置方式 | str 沒有設置 | str 為空字串 | str 已設置非為空字串 |
| --- | --- | --- |
| var=${str-expr} | var=expr | var= | var=$str |
| var=${str:-expr} | var=expr | var=expr | var=$str |
| var=${str+expr} | var= | var=expr | var=expr |
| var=${str:+expr} | var= | var= | var=expr |
| var=${str=expr} | str=expr var=expr | str 不變 var= | str 不變 var=$str |
| var=${str:=expr} | str=expr var=expr | str=expr var=expr | str 不變 var=$str |
| var=${str?expr} | expr 輸出至 stderr | var= | var=$str |
| var=${str:?expr} | expr 輸出至 stderr | expr 輸出至 stderr | var=$str |
根據上面這張表,我們來進行幾個范例的練習吧! ^_^!首先讓我們來測試一下,如果舊變量 (str) 不存在時, 我們要給予新變量一個內容,若舊變量存在則新變量內容以舊變量來替換,結果如下:
```
測試:先假設 str 不存在 (用 unset) ,然后測試一下減號 (-) 的用法:
[dmtsai@study ~]$ unset str; var=${str-newvar}
[dmtsai@study ~]$ echo "var=${var}, str=${str}"
var=newvar, str= <==因為 str 不存在,所以 var 為 newvar
測試:若 str 已存在,測試一下 var 會變怎樣?:
[dmtsai@study ~]$ str="oldvar"; var=${str-newvar}
[dmtsai@study ~]$ echo "var=${var}, str=${str}"
var=oldvar, str=oldvar <==因為 str 存在,所以 var 等于 str 的內容
```
關于減號 (-) 其實上面我們談過了!這里的測試只是要讓你更加了解,這個減號的測試并不會影響到舊變量的內容。 如果你想要將舊變量內容也一起替換掉的話,那么就使用等號 (=) 吧!
```
測試:先假設 str 不存在 (用 unset) ,然后測試一下等號 (=) 的用法:
[dmtsai@study ~]$ unset str; var=${str=newvar}
[dmtsai@study ~]$ echo "var=${var}, str=${str}"
var=newvar, str=newvar <==因為 str 不存在,所以 var/str 均為 newvar
測試:如果 str 已存在了,測試一下 var 會變怎樣?
[dmtsai@study ~]$ str="oldvar"; var=${str=newvar}
[dmtsai@study ~]$ echo "var=${var}, str=${str}"
var=oldvar, str=oldvar <==因為 str 存在,所以 var 等于 str 的內容
```
那如果我只是想知道,如果舊變量不存在時,整個測試就告知我“有錯誤”,此時就能夠使用問號“ ? ”的幫忙啦! 下面這個測試練習一下先!
```
測試:若 str 不存在時,則 var 的測試結果直接顯示 "無此變量"
[dmtsai@study ~]$ unset str; var=${str?無此變量}
-bash: str: 無此變量 <==因為 str 不存在,所以輸出錯誤訊息
測試:若 str 存在時,則 var 的內容會與 str 相同!
[dmtsai@study ~]$ str="oldvar"; var=${str?novar}
[dmtsai@study ~]$ echo "var=${var}, str=${str}"
var=oldvar, str=oldvar <==因為 str 存在,所以 var 等于 str 的內容
```
基本上這種變量的測試也能夠通過 shell script 內的 if...then... 來處理, 不過既然 bash 有提供這么簡單的方法來測試變量,那我們也可以多學一些嘛! 不過這種變量測試通常是在程序設計當中比較容易出現,如果這里看不懂就先略過,未來有用到判斷變量值時,再回來看看吧! ^_^
- 鳥哥的Linux私房菜:基礎學習篇 第四版
- 目錄及概述
- 第零章、計算機概論
- 0.1 電腦:輔助人腦的好工具
- 0.2 個人電腦架構與相關設備元件
- 0.3 數據表示方式
- 0.4 軟件程序運行
- 0.5 重點回顧
- 0.6 本章習題
- 0.7 參考資料與延伸閱讀
- 第一章、Linux是什么與如何學習
- 1.1 Linux是什么
- 1.2 Torvalds的Linux發展
- 1.3 Linux當前應用的角色
- 1.4 Linux 該如何學習
- 1.5 重點回顧
- 1.6 本章習題
- 1.7 參考資料與延伸閱讀
- 第二章、主機規劃與磁盤分區
- 2.1 Linux與硬件的搭配
- 2.2 磁盤分區
- 2.3 安裝Linux前的規劃
- 2.4 重點回顧
- 2.5 本章習題
- 2.6 參考資料與延伸閱讀
- 第三章、安裝 CentOS7.x
- 3.1 本練習機的規劃--尤其是分區參數
- 3.2 開始安裝CentOS 7
- 3.3 多重開機安裝流程與管理(Option)
- 3.4 重點回顧
- 3.5 本章習題
- 3.6 參考資料與延伸閱讀
- 第四章、首次登陸與線上求助
- 4.1 首次登陸系統
- 4.2 文字模式下指令的下達
- 4.3 Linux系統的線上求助man page與info page
- 4.4 超簡單文書編輯器: nano
- 4.5 正確的關機方法
- 4.6 重點回顧
- 4.7 本章習題
- 4.8 參考資料與延伸閱讀
- 第五章、Linux 的文件權限與目錄配置
- 5.1 使用者與群組
- 5.2 Linux 文件權限概念
- 5.3 Linux目錄配置
- 5.4 重點回顧
- 5.5 本章練習
- 5.6 參考資料與延伸閱讀
- 第六章、Linux 文件與目錄管理
- 6.1 目錄與路徑
- 6.2 文件與目錄管理
- 6.3 文件內容查閱
- 6.4 文件與目錄的默認權限與隱藏權限
- 6.5 指令與文件的搜尋
- 6.6 極重要的復習!權限與指令間的關系
- 6.7 重點回顧
- 6.8 本章習題:
- 6.9 參考資料與延伸閱讀
- 第七章、Linux 磁盤與文件系統管理
- 7.1 認識 Linux 文件系統
- 7.2 文件系統的簡單操作
- 7.3 磁盤的分區、格式化、檢驗與掛載
- 7.4 設置開機掛載
- 7.5 內存交換空間(swap)之創建
- 7.6 文件系統的特殊觀察與操作
- 7.7 重點回顧
- 7.8 本章習題 - 第一題一定要做
- 7.9 參考資料與延伸閱讀
- 第八章、文件與文件系統的壓縮,打包與備份
- 8.1 壓縮文件的用途與技術
- 8.2 Linux 系統常見的壓縮指令
- 8.3 打包指令: tar
- 8.4 XFS 文件系統的備份與還原
- 8.5 光盤寫入工具
- 8.6 其他常見的壓縮與備份工具
- 8.7 重點回顧
- 8.8 本章習題
- 8.9 參考資料與延伸閱讀
- 第九章、vim 程序編輯器
- 9.1 vi 與 vim
- 9.2 vi 的使用
- 9.3 vim 的額外功能
- 9.4 其他 vim 使用注意事項
- 9.5 重點回顧
- 9.6 本章練習
- 9.7 參考資料與延伸閱讀
- 第十章、認識與學習BASH
- 10.1 認識 BASH 這個 Shell
- 10.2 Shell 的變量功能
- 10.3 命令別名與歷史命令
- 10.4 Bash Shell 的操作環境:
- 10.5 數據流重導向
- 10.6 管線命令 (pipe)
- 10.7 重點回顧
- 10.8 本章習題
- 10.9 參考資料與延伸閱讀
- 第十一章、正則表達式與文件格式化處理
- 11.1 開始之前:什么是正則表達式
- 11.2 基礎正則表達式
- 11.3 延伸正則表達式
- 11.4 文件的格式化與相關處理
- 11.5 重點回顧
- 11.6 本章習題
- 11.7 參考資料與延伸閱讀
- 第十二章、學習 Shell Scripts
- 12.1 什么是 Shell scripts
- 12.2 簡單的 shell script 練習
- 12.3 善用判斷式
- 12.4 條件判斷式
- 12.5 循環 (loop)
- 12.6 shell script 的追蹤與 debug
- 12.7 重點回顧
- 12.8 本章習題
- 第十三章、Linux 帳號管理與 ACL 權限設置
- 13.1 Linux 的帳號與群組
- 13.2 帳號管理
- 13.3 主機的細部權限規劃:ACL 的使用
- 13.4 使用者身份切換
- 13.5 使用者的特殊 shell 與 PAM 模塊
- 13.6 Linux 主機上的使用者訊息傳遞
- 13.7 CentOS 7 環境下大量創建帳號的方法
- 13.8 重點回顧
- 13.9 本章習題
- 13.10 參考資料與延伸閱讀
- 第十四章、磁盤配額(Quota)與進階文件系統管理
- 14.1 磁盤配額 (Quota) 的應用與實作
- 14.2 軟件磁盤陣列 (Software RAID)
- 14.3 邏輯卷軸管理員 (Logical Volume Manager)
- 14.4 重點回顧
- 14.5 本章習題
- 14.6 參考資料與延伸閱讀
- 第十五章、例行性工作調度(crontab)
- 15.1 什么是例行性工作調度
- 15.2 僅執行一次的工作調度
- 15.3 循環執行的例行性工作調度
- 15.4 可喚醒停機期間的工作任務
- 15.5 重點回顧
- 15.6 本章習題
- 第十六章、程序管理與 SELinux 初探
- 16.1 什么是程序 (process)
- 16.2 工作管理 (job control)
- 16.3 程序管理
- 16.4 特殊文件與程序
- 16.5 SELinux 初探
- 16.6 重點回顧
- 16.7 本章習題
- 16.8 參考資料與延伸閱讀
- 第十七章、認識系統服務 (daemons)
- 17.1 什么是 daemon 與服務 (service)
- 17.2 通過 systemctl 管理服務
- 17.3 systemctl 針對 service 類型的配置文件
- 17.4 systemctl 針對 timer 的配置文件
- 17.5 CentOS 7.x 默認啟動的服務簡易說明
- 17.6 重點回顧
- 17.7 本章習題
- 17.8 參考資料與延伸閱讀
- 第十八章、認識與分析登錄文件
- 18.1 什么是登錄文件
- 18.2 rsyslog.service :記錄登錄文件的服務
- 18.3 登錄文件的輪替(logrotate)
- 18.4 systemd-journald.service 簡介
- 18.5 分析登錄文件
- 18.6 重點回顧
- 18.7 本章習題
- 18.8 參考資料與延伸閱讀
- 第十九章、開機流程、模塊管理與 Loader
- 19.1 Linux 的開機流程分析
- 19.2 核心與核心模塊
- 19.3 Boot Loader: Grub2
- 19.4 開機過程的問題解決
- 19.5 重點回顧
- 19.6 本章習題
- 19.7 參考資料與延伸閱讀
- 第二十章、基礎系統設置與備份策略
- 20.1 系統基本設置
- 20.2 服務器硬件數據的收集
- 20.3 備份要點
- 20.4 備份的種類、頻率與工具的選擇
- 20.5 鳥哥的備份策略
- 20.6 災難復原的考慮
- 20.7 重點回顧
- 20.8 本章習題
- 20.9 參考資料與延伸閱讀
- 第二十一章、軟件安裝:源代碼與 Tarball
- 20.1 開放源碼的軟件安裝與升級簡介
- 21.2 使用傳統程序語言進行編譯的簡單范例
- 21.3 用 make 進行宏編譯
- 21.4 Tarball 的管理與建議
- 21.5 函數庫管理
- 21.6 檢驗軟件正確性
- 21.7 重點回顧
- 21.8 本章習題
- 21.9 參考資料與延伸閱讀
- 第二十二章、軟件安裝 RPM, SRPM 與 YUM
- 22.1 軟件管理員簡介
- 22.2 RPM 軟件管理程序: rpm
- 22.3 YUM 線上升級機制
- 22.4 SRPM 的使用 : rpmbuild (Optional)
- 22.5 重點回顧
- 22.6 本章習題
- 22.7 參考資料與延伸閱讀
- 第二十三章、X Window 設置介紹
- 23.1 什么是 X Window System
- 23.2 X Server 配置文件解析與設置
- 23.3 顯卡驅動程序安裝范例
- 23.4 重點回顧
- 23.5 本章習題
- 23.6 參考資料與延伸閱讀
- 第二十四章、Linux 核心編譯與管理
- 24.1 編譯前的任務:認識核心與取得核心源代碼
- 24.2 核心編譯的前處理與核心功能選擇
- 24.3 核心的編譯與安裝
- 24.4 額外(單一)核心模塊編譯
- 24.5 以最新核心版本編譯 CentOS 7.x 的核心
- 24.6 重點回顧
- 24.7 本章習題
- 24.8 參考資料與延伸閱讀