0%

Linux (十一) - Shell 的變數設定

上一篇我們介紹了 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

設定變數內容

變數的內容設定非常直覺就是用等號 = 就可以了,但是內容上有一些規則限制。

1
<variable name>=<data>

範例
tmp 這個變數還沒設定過所以直接用 echo 顯示出來是沒有值的,接著再設定 tmp 的值之後就可以順利用 echo 指令取到值。

1
2
3
4
5
$ echo $tmp

$ tmp=TestData
$ echo $tmp
TestData

變數設定規則

  • 等號兩邊不能接空白,例如 :
1
2
tmp = Test ❌
tmp=Test Data ❌
  • 變數名稱只能是英文和數字,且 數字不能在開頭,例如 :
1
1tt=tt1 ❌
  • 變數內容有空白可以放在雙引號 "" 或是單引號 '' 裡面。
    • 雙引號中若有特殊的字元,例如 $,會保有原本的特性。
    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 指令來將自訂的變數轉成環境變數讓子程序使用。

1
export <variable name>

範例
這個範例簡單示範了父程序先自訂了一個變數,接著使用 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

declaretypeset 指令可以用於宣告變數的類型,如果沒有特別指定就會是字串。因此如果沒有指定成整數就無法進行運算。

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
  • 宣告為陣列類型,和 read 指令不同的是 read 直接輸入陣列內容可以不須加其他符號只要透過空白隔開。而 declare 如果在宣告時要先給值則必須使用小括號 () 將內容括起來。

    另外也可以直接指定值要寫進哪一個 index。

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

# 以此種格式宣告不加 -a 也可以
$ 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