# 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`
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.