# Linux (十一) - Shell 的變數設定 上一篇我們介紹了 Shell 的基本概念,了解了什麼是 Shell 之後,接下來就要來了解什麼是變數。 <!-- more --> 變數是 Bash 中非常重要的一個設計,而概念和程式語言所宣告的變數類似。例如 : 不同的使用者會對應到不同的設定,系統只要在使用者登入時去取得對應的值放到變數裡,當要用到時可以直接從變數取值,就不用把值寫死在程式碼裡面。 ## 取得變數內容 (echo) 要取得變數的內容可以使用 `echo` 指令來達成,在取得變數內容前要在變數前面加上 `$` 的符號才可以。變數名稱也可以放在 `{}` 中。 ```bash= echo $<variable name> echo ${<variable name>} ``` **範例** 下面以 `PATH` 這個環境變數來作為範例,可以看到不論是有沒有加 `{}` 輸出的結果都是一樣的。 ```bash= $ 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 ``` ## 設定變數內容 變數的內容設定非常直覺就是用等號 `=` 就可以了,但是內容上有一些規則限制。 ```bash= <variable name>=<data> ``` **範例** tmp 這個變數還沒設定過所以直接用 `echo` 顯示出來是沒有值的,接著再設定 tmp 的值之後就可以順利用 `echo` 指令取到值。 ```bash= $ echo $tmp $ tmp=TestData $ echo $tmp TestData ``` ### 變數設定規則 * 等號兩邊不能接空白,例如 : ```bash= tmp = Test ❌ tmp=Test Data ❌ ``` * 變數名稱只能是英文和數字,且 `數字不能在開頭`,例如 : ```bash= 1tt=tt1 ❌ ``` * 變數內容有空白可以放在雙引號 `""` 或是單引號 `''` 裡面。 * 雙引號中若有特殊的字元,例如 `$`,會保有原本的特性。 ```bash= $ name=simon $ s1="This is $name" $ echo $s1 This is simon ``` * 單引號中所有的字元即為一般字元,沒有任何功能。 ```bash= $ s2='This is $name' $ echo $s2 This is $name ``` * 使用反斜線 `\` 可以跳脫其後接的第一個字元本身的功能,這可以用來跳脫一些特殊字元,例如 : `$`、`Enter`、`空白` 等等,讓這些字元變成單純的字元沒有功能。在上一篇我們也有介紹過用反斜線來跳脫 `Enter` 可以讓很長的指令變成多行以利閱讀。 ```bash= $ name=simon\ test $ echo $name simon test $ echo It costs $65 It costs 5 $ echo It costs \$65 It costs $65 ``` * 在指令中如果還要從其他指令取得內容,可以使用 $(command) 或是 \`command\` 取得其他指令的值。 ```bash= $ username=$(whoami) $ echo $username simon $ echo $(whoami) simon $ username=`id simon` $ echo $username uid=0(simon) gid=0(simon) groups=0(simon) ``` * 擴增變數內容可以直接取現有的值再接上想要擴增的內容。 ```bash= $ echo $name simon test $ name=$name\ tina $ echo $name simon test tina ``` * 要取消變數的設定可以使用 `unset` 指令來解除變數。 ```bash= $ 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。從這裡就可以看出來 `第二個程序是第一個程序的子程序`。 ```bash= $ 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` 指令來列出所有的環境變數。 ```bash= $ 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` 指令來將自訂的變數轉成環境變數讓子程序使用。 ```bash= export <variable name> ``` **範例** 這個範例簡單示範了父程序先自訂了一個變數,接著使用 export 指令開放成環境變數。再打開一個 bash 作為子程序後,可以正確的讀取到父程序所開放的環境變數。 ```bash= $ test_env=12345 $ export test_env $ bash $ echo $test_env 12345 ``` 如果想查看開放了哪些環境變數可以直接使用 `export` 指令不加變數名稱即可,如下 : ```bash= $ 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` 的變數一同列出來。 ```bash= $ 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` 指令來查看變數的設定。 ```bash= $ 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`。 ```bash= $ locale -a ... zh_TW zh_TW.big5 # 大五碼中文編碼 zh_TW.euctw zh_TW.utf8 # 萬國瑪中文編碼 ... ``` 如果想要 `暫時` 修改語系的變數,可以直接像給變數一樣給值,並且搭配 `export` 轉成環境變數這樣才會生效。如下 : ```bash= $ 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` 這個檔案去修改,改完後重啟系統即可完成。 ```bash= $ cat `/etc/locale.conf` LANG="en_US.utf-8" ``` ## 從鍵盤讀取變數內容 上述介紹的變數設定都是直接使用指令來設定的,不過也可以讓使用者來輸入內容再存到變數裡面。例如登入會等待使用者輸入帳號、密碼,或是安裝程式會等待使用者輸入 yes/no 等等。 ### read read 指令可以用來讀取鍵盤輸入的內容並存到變數裡,而這個指令經常被用在 Shell Script。 ```bash= read <option> <variable name> ``` option : * -a : array, 變數為陣列格式,輸入的內容以空白隔開依序存入陣列中,陣列索引從 0 開始。 * -p : prompt, 跳出提示字串。 * -t : timeout, 設定秒數內沒有完成則結束。 variable name : 變數的名稱,鍵盤輸入的內容會寫進此變數。 **範例** 下面的範例分別以不加任何選項參數、變數設為陣列、設定提示訊息及時間限制來展示使用 `read` 指令的結果。 ```bash= $ 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` 指令可以用於宣告變數的類型,如果沒有特別指定就會是字串。因此如果沒有指定成整數就無法進行運算。 ```bash= declare <option> <variable name> ``` option : * -a : 變數為陣列格式,輸入的內容以空白隔開依序存入陣列中,陣列索引從 0 開始。 * -i : 整數類型,可以進行數值運算,但是預設只能做到整數型態。 * -l : 將變數內容轉換為小寫。 * -r : 變數僅為 readonly,不可被更改也不可以被 unset。 * -u : 將變數內容轉換為大寫。 * -x : 開放變數轉換成環境變數,等同使用 `export`。 variable name : 變數名稱。 **範例** * 基本宣告變數,沒有特別指定就會是字串,如果是不指定其實也不需要使用 `declare` 指令,可以和前面提過的變數給值方式使用就好。 ```bash= $ declare td1=test $ echo $td1 test ``` * 宣告為陣列類型,和 `read` 指令不同的是 `read` 直接輸入陣列內容可以不須加其他符號只要透過空白隔開。而 `declare` 如果在宣告時要先給值則必須使用小括號 `()` 將內容括起來。 另外也可以直接指定值要寫進哪一個 index。 ```bash= $ 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 ``` * 變數內容強制轉成小寫 : ```bash= $ declare -l td3="ABCDE" $ echo $td3 abcde ``` * 變數內容設為 `readonly`,不可修改。可以看到下面的範例想要嘗試修改結果失敗。 ```bash= $ declare -r td4="aabbcc" $ td4="abcd" bash: td4: readonly variable ``` * 變數內容強制轉成大寫 : ```bash= $ declare -u td5="aabbcc" $ echo $td5 AABBCC ``` * 變數直接宣告成環境變數,省去了要再使用 `export` 開放成環境變數。 ```bash= $ declare -x td6="aabbcc" $ env ... td6=aabbcc ... ``` * 下面這個範例是結合陣列和整數運算的應用,可以看到雖然建立陣列時沒有定義變數裡面的值是甚麼類型,但是只要把最終輸出的變數設定成整數,在運算時就會自動變成整數運算。 ```bash= $ declare -a td7=(1 2 3) $ declare -i tmp $ tmp=${td7[0]}+${td7[1]}+${td7[2]} $ echo $tmp 6 ``` ### 陣列 前面其實已經有介紹到了陣列的宣告,這裡再補充一下陣列也可以直接像變數一樣宣告而不一定要用 `declare`。如下 : ```bash= $ 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](http://linux.vbird.org/linux_basic/0320bash.php#variable) [2] [shell script 教學 變數的宣告](https://crmne0707.pixnet.net/blog/post/315013570-shell-script-%E6%95%99%E5%AD%B8-%E8%AE%8A%E6%95%B8%E7%9A%84%E5%AE%A3%E5%91%8A) [3] [How to use arrays in bash script](https://linuxconfig.org/how-to-use-arrays-in-bash-script) ###### tags: `Linux` `Shell` `Bash`