上一篇我們介紹了 Shell 的基本概念,了解了什麼是 Shell 之後,接下來就要來了解什麼是變數。
變數是 Bash 中非常重要的一個設計,而概念和程式語言所宣告的變數類似。例如 : 不同的使用者會對應到不同的設定,系統只要在使用者登入時去取得對應的值放到變數裡,當要用到時可以直接從變數取值,就不用把值寫死在程式碼裡面。
取得變數內容 (echo)
要取得變數的內容可以使用 echo
指令來達成,在取得變數內容前要在變數前面加上 $
的符號才可以。變數名稱也可以放在 {}
中。
1 2
| echo $<variable name> echo ${<variable name>}
|
範例
下面以 PATH
這個環境變數來作為範例,可以看到不論是有沒有加 {}
輸出的結果都是一樣的。
1 2 3 4 5
| $ echo $PATH /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/opc/.local/bin:/home/opc/bin
$ echo ${PATH} /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/opc/.local/bin:/home/opc/bin
|
設定變數內容
變數的內容設定非常直覺就是用等號 =
就可以了,但是內容上有一些規則限制。
範例
tmp 這個變數還沒設定過所以直接用 echo
顯示出來是沒有值的,接著再設定 tmp 的值之後就可以順利用 echo
指令取到值。
1 2 3 4 5
| $ echo $tmp
$ tmp=TestData $ echo $tmp TestData
|
變數設定規則
1 2
| tmp = Test ❌ tmp=Test Data ❌
|
- 變數名稱只能是英文和數字,且
數字不能在開頭
,例如 :
- 變數內容有空白可以放在雙引號
""
或是單引號 ''
裡面。
- 雙引號中若有特殊的字元,例如
$
,會保有原本的特性。
1 2 3 4
| $ name=simon $ s1="This is $name" $ echo $s1 This is simon
|
1 2 3
| $ s2='This is $name' $ echo $s2 This is $name
|
- 使用反斜線
\
可以跳脫其後接的第一個字元本身的功能,這可以用來跳脫一些特殊字元,例如 : $
、Enter
、空白
等等,讓這些字元變成單純的字元沒有功能。在上一篇我們也有介紹過用反斜線來跳脫 Enter
可以讓很長的指令變成多行以利閱讀。
1 2 3 4 5 6 7 8
| $ name=simon\ test $ echo $name simon test
$ echo It costs $65 It costs 5 $ echo It costs \$65 It costs $65
|
- 在指令中如果還要從其他指令取得內容,可以使用 $(command) 或是 `command` 取得其他指令的值。
1 2 3 4 5 6 7 8 9 10
| $ username=$(whoami) $ echo $username simon
$ echo $(whoami) simon
$ username=`id simon` $ echo $username uid=0(simon) gid=0(simon) groups=0(simon)
|
- 擴增變數內容可以直接取現有的值再接上想要擴增的內容。
1 2 3 4 5 6
| $ echo $name simon test
$ name=$name\ tina $ echo $name simon test tina
|
- 要取消變數的設定可以使用
unset
指令來解除變數。
1 2
| $ unset name $ echo $name
|
環境變數
環境變數包含了使用者登入後的一些資訊,大多都是系統的設定,例如 : 系統語言、執行檔的路徑、家目錄路徑等等。和一般變數一樣這些環境變數都可以設定,但是不太會經常去改動環境變數的內容。
程序 (Process)
環境變數的用途是讓父程序下的所有子程序都可以使用這個變數。所謂的程序 (Process) 是指運作起來的程式 (Program),所以在一個運作中的程式裡面再啟動一個程式讓他運作,這就是父程序和子程序的概念。
舉例來說,Bash 的執行檔是在 /bin/bash
,而這個執行檔是一個 binary file
,也就是一個程式。所以當我們透過 /bin/bash
登入時,就是啟動了 /bin/bash
這個程式讓他變成 程序
。接著我們在 Bash 中執行 cd
指令來切換當前的路徑,而 cd
也是一個 binary file
的執行檔,路徑是 /bin/cd
,所以下了 cd
就等於是啟動了 /bin/cd
這個程式,也就會產生出一個 cd
的 程序
。這時候父程序就是 bash 程序
,而子程序就是 cd 程序
。
範例
下面這個範例我們在已經開啟的 bash 上再開一個 bash,此時再使用 ps (process status)
指令來看一下運行中的 process 狀態。
可以看到第一個程序的 PID (程序的 ID) 是 17,而 CMD (觸發程序的指令) 是 bash;第二個程序的 PPID (程序的父程序 ID) 也是 17,而 CMD (觸發程序的指令) 是 bash。從這裡就可以看出來 第二個程序是第一個程序的子程序
。
1 2 3 4 5 6
| $ bash $ ps -l F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 4 S 0 17 0 0 80 0 - 2960 - pts/1 00:00:00 bash 4 S 0 35 17 0 80 0 - 2960 - pts/1 00:00:00 bash 0 R 0 62 35 0 80 0 - 12405 - pts/1 00:00:00 ps
|
列出環境變數 (env)
上面簡單介紹了環境變數的用途,現在要再回來看環境變數的一些操作和設定。首先可以先用 env
指令來列出所有的環境變數。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| $ env HOSTNAME=myserver TERM=xterm SHELL=/bin/bash HISTSIZE=1000 USER=myuser 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;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36: MAIL=/var/spool/mail/myuser PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/myuser/.local/bin:/home/myuser/bin PWD=/home/myuser LANG=en_US.UTF-8 HOME=/home/myuser LOGNAME=myuser _=/usr/bin/env
|
- HOSTNAME : 主機名稱。
- SHELL : 目前使用哪個 Shell。
- TERM : 終端機使用的環境類型,例如 : xterm、GNOME、Terminal、PuTTY。
- HITSIZE : 命令記錄的最大數量,上一篇有提到命令紀錄只會有一定的數量,就是從這裡設定的。
- USER : 使用者的名稱。
- LS_COLORS : Shell 顯示檔案、資料夾等等的顏色設定。
- MAIL : 如果使用
mail
指令收信,系統會去讀取信的檔案路徑。
- PATH : 執行檔的搜尋路徑,不同的路徑用
:
隔開。當下達了指令時就會 依序
到這些路徑看有沒有這個指令的執行檔。
- PWD : 目前使用者所在的工作目錄。
- LANG : 語系的設定。
- HOME : 目前使用者的家目錄路徑。
- LOGNAME : 登入者用來登入的帳號名稱。
- _ : 上一次使用的指令最後一個參數或是指令本身。
可以看到上面列出的環境變數都是大寫,LINUX 預設會使用大寫字母來設定系統的變數。
自訂變數轉成環境變數 (export)
前面我們有介紹過了環境變數的用途就是用讓 子程序可以使用到父程序所定義的變數
,但是這裡要特別注意 子程序只能使用父程序設定的環境變數,而不是父程序所有自訂的變數
。因此父程序就可以透過 export
指令來將自訂的變數轉成環境變數讓子程序使用。
範例
這個範例簡單示範了父程序先自訂了一個變數,接著使用 export 指令開放成環境變數。再打開一個 bash 作為子程序後,可以正確的讀取到父程序所開放的環境變數。
1 2 3 4 5
| $ test_env=12345 $ export test_env $ bash $ echo $test_env 12345
|
如果想查看開放了哪些環境變數可以直接使用 export
指令不加變數名稱即可,如下 :
1 2 3 4 5 6 7 8 9 10
| $ export declare -x HOME="/myuser" declare -x HOSTNAME="myserver" ... declare -x OLDPWD declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" declare -x PWD="/" declare -x SHLVL="1" declare -x TERM="xterm" declare -x test_env="12345"
|
列出所有的變數 (set)
如果想要查看所有的變數,包含自訂變數和環境變數,可以使用 set
指令來查看。除了自訂變數和環境變數,bash 本身也會有一些變數,而 set
指令也會將 bash
的變數一同列出來。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| $ set BASH=/bin/bash BASHOPTS=checkwinsize:cmdhist:expand_aliases:extquote:force_fignore:histappend:hostcomplete:interactive_comments:progcomp:promptvars:sourcepath BASH_ALIASES=() BASH_ARGC=() BASH_ARGV=() BASH_CMDS=() BASH_LINENO=() BASH_SOURCE=() BASH_VERSINFO=([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu") BASH_VERSION='4.2.46(2)-release' ... HOME=/home/myuser HOSTNAME=myserver HOSTTYPE=x86_64 ... PPID=0 PROMPT_COMMAND='printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"' PS1='[\u@\h \W]\$ ' PS2='> ' PS4='+ ' PWD=/ SHELL=/bin/bash SHELLOPTS=braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor SHLVL=1 TERM=xterm UID=0 _=set colors=/root/.dircolors test_env=12345 username='uid=0(root) gid=0(root) groups=0(root)'
|
語系變數 (locale)
語系的設定也是使用變數來記錄和設定的,可以透過 locale
指令來查看變數的設定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| $ locale LANG=en_US.UTF-8 LC_CTYPE="en_US.UTF-8" LC_NUMERIC="en_US.UTF-8" LC_TIME="en_US.UTF-8" LC_COLLATE="en_US.UTF-8" LC_MONETARY="en_US.UTF-8" LC_MESSAGES="en_US.UTF-8" LC_PAPER="en_US.UTF-8" LC_NAME="en_US.UTF-8" LC_ADDRESS="en_US.UTF-8" LC_TELEPHONE="en_US.UTF-8" LC_MEASUREMENT="en_US.UTF-8" LC_IDENTIFICATION="en_US.UTF-8" LC_ALL=
|
這些語系的變數都可以依照自己的需求調整,不過其實只要有設定 LANG
或是 LC_ALL
,則其他的變數就會被這兩個變數自動取代。
設定前可以使用 locale -a
來查看支援哪些語系,下面列出了幾個中文的編碼,最常用的就是 zh_TW.big5
或是 zh_TW.utf8
。
1 2 3 4 5 6 7
| $ locale -a ... zh_TW zh_TW.big5 zh_TW.euctw zh_TW.utf8 ...
|
如果想要 暫時
修改語系的變數,可以直接像給變數一樣給值,並且搭配 export
轉成環境變數這樣才會生效。如下 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| $ locale LANG=en_US.utf8 LC_CTYPE="en_US.utf8" LC_NUMERIC="en_US.utf8" LC_TIME="en_US.utf8" LC_COLLATE="en_US.utf8" LC_MONETARY="en_US.utf8" LC_MESSAGES="en_US.utf8" LC_PAPER="en_US.utf8" LC_NAME="en_US.utf8" LC_ADDRESS="en_US.utf8" LC_TELEPHONE="en_US.utf8" LC_MEASUREMENT="en_US.utf8" LC_IDENTIFICATION="en_US.utf8" LC_ALL=
$ export LANG=zh_TW.utf8 $ locale LANG=zh_TW.utf8 LC_CTYPE="zh_TW.utf8" LC_NUMERIC="zh_TW.utf8" LC_TIME="zh_TW.utf8" LC_COLLATE="zh_TW.utf8" LC_MONETARY="zh_TW.utf8" LC_MESSAGES="zh_TW.utf8" LC_PAPER="zh_TW.utf8" LC_NAME="zh_TW.utf8" LC_ADDRESS="zh_TW.utf8" LC_TELEPHONE="zh_TW.utf8" LC_MEASUREMENT="zh_TW.utf8" LC_IDENTIFICATION="zh_TW.utf8" LC_ALL=
|
如果是想要永久修改語系的變數,可以打開 /etc/locale.conf
這個檔案去修改,改完後重啟系統即可完成。
1 2
| $ cat `/etc/locale.conf` LANG="en_US.utf-8"
|
從鍵盤讀取變數內容
上述介紹的變數設定都是直接使用指令來設定的,不過也可以讓使用者來輸入內容再存到變數裡面。例如登入會等待使用者輸入帳號、密碼,或是安裝程式會等待使用者輸入 yes/no 等等。
read
read 指令可以用來讀取鍵盤輸入的內容並存到變數裡,而這個指令經常被用在 Shell Script。
1
| read <option> <variable name>
|
option :
- -a : array, 變數為陣列格式,輸入的內容以空白隔開依序存入陣列中,陣列索引從 0 開始。
- -p : prompt, 跳出提示字串。
- -t : timeout, 設定秒數內沒有完成則結束。
variable name : 變數的名稱,鍵盤輸入的內容會寫進此變數。
範例
下面的範例分別以不加任何選項參數、變數設為陣列、設定提示訊息及時間限制來展示使用 read
指令的結果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| $ read t1 12345 $ echo $t1 12345
$ read -a t2 123 456 789 $ echo ${t2[0]} 123 $ echo ${t2[1]} 456 $ echo ${t2[2]} 789
$ read -p "user name : " -t 20 t3 user name : sam $ echo $t3 sam
|
變數類型宣告
變數也可以透過宣告指定想要的類型,例如 : 陣列、整數等等。
declare/typeset
declare
和 typeset
指令可以用於宣告變數的類型,如果沒有特別指定就會是字串。因此如果沒有指定成整數就無法進行運算。
1
| declare <option> <variable name>
|
option :
- -a : 變數為陣列格式,輸入的內容以空白隔開依序存入陣列中,陣列索引從 0 開始。
- -i : 整數類型,可以進行數值運算,但是預設只能做到整數型態。
- -l : 將變數內容轉換為小寫。
- -r : 變數僅為 readonly,不可被更改也不可以被 unset。
- -u : 將變數內容轉換為大寫。
- -x : 開放變數轉換成環境變數,等同使用
export
。
variable name : 變數名稱。
範例
- 基本宣告變數,沒有特別指定就會是字串,如果是不指定其實也不需要使用
declare
指令,可以和前面提過的變數給值方式使用就好。
1 2 3
| $ declare td1=test $ echo $td1 test
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| $ declare -a td2=(aaa bbb ccc) $ echo ${td2[0]} aaa $ echo ${td2[1]} bbb $ echo ${td2[2]} ccc
$ declare td2=(aaa bbb ccc) $ echo ${td2[0]} aaa $ echo ${td2[1]} bbb $ echo ${td2[2]} ccc
$ declare -a td2 $ td2[0]=test1 $ td2[1]=test2
|
1 2 3
| $ declare -l td3="ABCDE" $ echo $td3 abcde
|
- 變數內容設為
readonly
,不可修改。可以看到下面的範例想要嘗試修改結果失敗。
1 2 3
| $ declare -r td4="aabbcc" $ td4="abcd" bash: td4: readonly variable
|
1 2 3
| $ declare -u td5="aabbcc" $ echo $td5 AABBCC
|
- 變數直接宣告成環境變數,省去了要再使用
export
開放成環境變數。
1 2 3 4 5
| $ declare -x td6="aabbcc" $ env ... td6=aabbcc ...
|
- 下面這個範例是結合陣列和整數運算的應用,可以看到雖然建立陣列時沒有定義變數裡面的值是甚麼類型,但是只要把最終輸出的變數設定成整數,在運算時就會自動變成整數運算。
1 2 3 4 5
| $ declare -a td7=(1 2 3) $ declare -i tmp $ tmp=${td7[0]}+${td7[1]}+${td7[2]} $ echo $tmp 6
|
陣列
前面其實已經有介紹到了陣列的宣告,這裡再補充一下陣列也可以直接像變數一樣宣告而不一定要用 declare
。如下 :
1 2 3 4 5 6 7 8 9 10 11
| $ arr[0]="ttt" $ echo ${arr[0]} ttt
$ arr=(1 2 3) $ echo ${arr[0]} 1 $ echo ${arr[1]} 2 $ echo ${arr[2]} 3
|
Summary
本篇針對 Shell 的變數做了進一步的介紹,而變數大多是在撰寫 Shell Script 時才會用到,平時會用到的大概就是環境變數和語系的變數了。所以接著下一篇我們就來介紹什麼是 Shell Script 以及如何撰寫。
參考
[1] 認識與學習BASH
[2] shell script 教學 變數的宣告
[3] How to use arrays in bash script