文字界面下指令的連續下達方式、資料流重新導向以及大量的應用題型!
前一節課針對 bash 做簡單的變數與環境操作之介紹,本節將針對 bash 環境中常用的連續指令下達方式, 以及資料處理常用的資料流重導向與管線命令進行介紹。這些資料處理的技術對於管理員來說是相當重要的, 尤其在自撰腳本程式分析登錄檔時,這就顯的非常的重要。
某些情況下,使用者可能會連續的進行某些指令的下達。但這些指令之間可能會有關連性,例如前一個指令成功後才可進行下一個指令等。 這些情況就需要使用到特殊的字符來處理。
前面的章節我們一直使用到 bash 終端機環境下的指令操作,而且一直接觸到很多的單引號、雙引號、錢字號、反單引號、大括號、小括號等等, 其實在該成對的符號內,隱藏了:指令、變數、計算式等的功能!再次了解特殊的符號的用途,請完成底下的練習:
接下來,直接應用一下上面的功能:
指令的執行正確與否與後續的處理有關。Linux 環境下預設的指令正常結束回傳值為 0 ,呼叫的方式為使用『 echo $? 』即可, 亦即找出 ? 這個變數的內容即可。
上述的練習在讓使用者了解到,指令回傳值是每個指令自己指定的,只要符合 bash 的基本規範即可。
指令是可以連續輸入的,直接透過分號 (;) 隔開每個指令即可。在沒有相依性的指令環境中,可以直接進行如下的行為:
[student@localhost ~]$ date; uptime; uname -r
Sun Apr 30 03:17:19 PM CST 2023
15:17:19 up 7 min, 2 users, load average: 0.00, 0.04, 0.03
5.14.0-162.12.1.el9_1.0.2.x86_64
如此一口氣就可以直接將所有的指令執行完畢,無須考量其他問題。當使用者有多個指令需要下達,每個指令又需要比較長的等待時間時, 可以使用這種方式來處理即可。但是,如果想要將這些資訊同步輸出到同一個檔案時,應該如何處理?參考底下兩個範例後,說明其差異為何?
請使用底下的兩個指令處理資料流,探討其原因
[student@localhost ~]$ date; uptime; uname -r > myfile.txt [student@localhost ~]$ (date; uptime; uname -r ) > myfile.txt
分號 (;) 是直接連續下達指令,指令間不必有一定程度的相依性。但當指令之間有相依性時,就能夠使用 && 或 || 來處理。 這兩個處理的方式如下:
[student@localhost ~]$ ls -ld /dev/shm/check ls: 無法存取 '/dev/shm/check': 沒有此一檔案或目錄 # 確認該檔名是不存在的! [student@localhost ~]$ ls -ld /dev/shm/check || mkdir /dev/shm/check ls: 無法存取 '/dev/shm/check': 沒有此一檔案或目錄 [student@localhost ~]$ ls -ld /dev/shm/check drwxr-xr-x. 2 student student 40 4月 30 15:21 /dev/shm/check # 檔案出現了!這是因為 mkdir 的緣故! [student@localhost ~]$ ls -ld /dev/shm/check || mkdir /dev/shm/check drwxr-xr-x. 2 student student 40 4月 30 15:21 /dev/shm/check # 再次運作也不會出現 mkdir 的錯誤! || 會略過正確訊息的動作!第一次執行時,由於尚未有該目錄,所以顯示找不到,但是第二次執行時,就會有該目錄,因此 mkdir 的動作就沒有進行。 若將中間分隔改為分號 (;) 時,就會產生重複 mkdir 的問題了。因此,比較好的執行方式,還是需要使用 || 較佳!
[student@localhost ~]$ ls -ld /dev/shm/check && rmdir /dev/shm/check drwxr-xr-x. 2 student student 40 4月 30 15:21 /dev/shm/check # 兩個指令都分別順利運作完畢 [student@localhost ~]$ ls -ld /dev/shm/check && rmdir /dev/shm/check ls: 無法存取 '/dev/shm/check': 沒有此一檔案或目錄 # 第一個 ls 的指令失敗,因此並沒有執行 rmdir 喔!若不信,使用底下指令看看: [student@localhost ~]$ ls -ld /dev/shm/check ; rmdir /dev/shm/check ls: 無法存取 '/dev/shm/check': 沒有此一檔案或目錄 rmdir: 刪除 '/dev/shm/check' 失敗: 沒有此一檔案或目錄與前一個例題相似,透過 ls 簡易的進行搜尋的任務,讀者也同樣會發現兩次執行的結果並不相同。 此外,最後一個指令也能夠讓讀者知道, && 與 ; 的差異!
假設我們需要一個指令來說明某個檔名是否存在,可以這樣處理:
[student@localhost ~]$ ls -d /etc && echo exist || echo non-exist /etc exist [student@localhost ~]$ ls -d /vbird && echo exist || echo non-exist ls: 無法存取 /vbird: 沒有此一檔案或目錄 non-exist
由於我們只是想要知道該檔案是否存在,因此不需要如上表所示,連 ls 的結果也輸出。此時可以使用 &> 的方式來將結果輸出到垃圾桶,如下所示:
[student@localhost ~]$ ls -d /etc &> /dev/null && echo exist || echo non-exist exist [student@localhost ~]$ ls -d /vbird &> /dev/null && echo exist || echo non-exist non-exist
由於上述指令要修改很麻煩,假設我們需要使用『 checkfile filename 』來處理,此時可以撰寫一隻小腳本來進行此任務。 若該指令可以讓所有用戶執行,則可將指令寫入 /usr/local/bin 目錄內。
[root@localhost ~]# vim /usr/local/bin/checkfile #!/bin/bash ls -d ${1} &> /dev/null && echo "${1} exist" || echo "${1} non-exist" [root@localhost ~]# chmod a+x /usr/local/bin/checkfile [root@localhost ~]# checkfile /etc /etc exist [root@localhost ~]# checkfile /vbird /vbird non-exist
在 checkfile 檔案中,第一行 (#!/bin/bash) 代表使用 bash 來執行底下的語法,第二行當中的變數 ${1} 代表在本檔案後面所接的第一個參數,因此執行時,就能夠直接將要判斷的檔名接在 checkfile 後面即可。
事實上,前一小節使用 ls 進行確認檔名時,僅需要確認回傳值是否為 0 而已。Linux 提供一個名為 test 的指令可以確認許多檔名參數, 常見的參數有:
測試的標誌 | 代表意義 |
1. 關於某個檔名的『檔案類型』判斷,如 test -e filename 表示存在否 | |
-e | 該『檔名』是否存在?(常用) |
-f | 該『檔名』是否存在且為檔案(file)?(常用) |
-d | 該『檔名』是否存在且為目錄(directory)?(常用) |
-b | 該『檔名』是否存在且為一個 block device 裝置? |
-c | 該『檔名』是否存在且為一個 character device 裝置? |
-S | 該『檔名』是否存在且為一個 Socket 檔案? |
-p | 該『檔名』是否存在且為一個 FIFO (pipe) 檔案? |
-L | 該『檔名』是否存在且為一個連結檔? |
2. 關於檔案的權限偵測,如 test -r filename 表示可讀否 (但 root 權限常有例外) | |
-r | 偵測該檔名是否存在且具有『可讀』的權限? |
-w | 偵測該檔名是否存在且具有『可寫』的權限? |
-x | 偵測該檔名是否存在且具有『可執行』的權限? |
-u | 偵測該檔名是否存在且具有『SUID』的屬性? |
-g | 偵測該檔名是否存在且具有『SGID』的屬性? |
-k | 偵測該檔名是否存在且具有『Sticky bit』的屬性? |
-s | 偵測該檔名是否存在且為『非空白檔案』? |
3. 兩個檔案之間的比較,如: test file1 -nt file2 | |
-nt | (newer than)判斷 file1 是否比 file2 新 |
-ot | (older than)判斷 file1 是否比 file2 舊 |
-ef | 判斷 file1 與 file2 是否為同一檔案,可用在判斷 hard link 的判定上。 主要意義在判定,兩個檔案是否均指向同一個 inode 哩! |
4. 關於兩個整數之間的判定,例如 test n1 -eq n2 | |
-eq | 兩數值相等 (equal) |
-ne | 兩數值不等 (not equal) |
-gt | n1 大於 n2 (greater than) |
-lt | n1 小於 n2 (less than) |
-ge | n1 大於等於 n2 (greater than or equal) |
-le | n1 小於等於 n2 (less than or equal) |
5. 判定字串的資料 | |
test -z string | 判定字串是否為 0 ?若 string 為空字串,則為 true |
test -n string | 判定字串是否非為 0 ?若 string 為空字串,則為 false。 註: -n 亦可省略 |
test str1 == str2 | 判定 str1 是否等於 str2 ,若相等,則回傳 true |
test str1 != str2 | 判定 str1 是否不等於 str2 ,若相等,則回傳 false |
6. 多重條件判定,例如: test -r filename -a -x filename | |
-a | (and)兩狀況同時成立!例如 test -r file -a -x file,則 file 同時具有 r 與 x 權限時,才回傳 true。 |
-o | (or)兩狀況任何一個成立!例如 test -r file -o -x file,則 file 具有 r 或 x 權限時,就可回傳 true。 |
! | 反相狀態,如 test ! -x file ,當 file 不具有 x 時,回傳 true |
test 僅會回傳 $? 而已,螢幕上不會出現任何的變化,因此如果需要取得回應,就需要使用 echo $? 的方式來查詢, 或者使用 && 及 || 來處理。
由於 test 是直接加在變數判斷之前,讀者可能偶而會覺得怪異。此時可以使用中括號 [ ] 來取代 test 的語法。 同樣以 checkfile 來處理時,該檔案的內容應該需要改寫成如下:
[root@localhost ~]# vim /usr/local/bin/checkfile #!/bin/bash [ -e "${1}" ] && echo "${1} exist" || echo "${1} non-exist"
由於中括號的意義非常多,包括第三堂課萬用字元當中,中括號代表的是『具有一個指定的任意字元』,未來第九堂課的正規表示法當中, 中括號也具有特殊的字符意義。而分辨是否為『判別式』的部份,就是其語法的差別。請注意,在 bash 環境下,使用中括號替代 test 指令時, 中括號的內部需要留白一個以上的空白字元!如下圖示:
[ "$HOME" == "$MAIL" ] [□"$HOME"□==□"$MAIL"□] ↑ ↑ ↑ ↑
如果使用者想要使用簡易的 shell script 創立一個指令,也能夠自己設定回傳值的意義。
[root@localhost ~]# vim /usr/local/bin/myls.sh #!/bin/bash ls ${@} && exit 100 || exit 10 [root@localhost ~]# chmod a+x /usr/local/bin/myls.sh [student@localhost ~]$ myls.sh /vbird; echo $? ls: 無法存取 '/vbird': 沒有此一檔案或目錄 10
上述的 ${@} 代表指令後面接的任何參數,因此你可以執行 myls.sh 後面接多個參數都沒問題。 由於 exit 可以回傳訊息,因此可以讓使用者簡易的設定好所需要的回傳訊息規範。
第一堂課開始讀者應該就接到到 ls 與 ll 這兩個指令,剛開始介紹時,讀者們應該知道 ll 是 long list 的縮寫。 若將 ll 這個指令用來取代 checkfile 這個腳本,是否可以處理?
[root@localhost ~]# vim /usr/local/bin/checkfile #!/bin/bash #[ -e "${1}" ] && echo "${1} exist" || echo "${1} non-exist" ll -d ${1} && echo "${1} exist" || echo "${1} non-exist" # 特別注意,不要加上 &> /dev/null 喔!不然會看不到 ll -d 的錯誤! [root@localhost ~]# checkfile /etc /usr/local/bin/checkfile: 列 5: ll:命令找不到 /etc non-exist
但是,當你執行 checkfile /etc 時,竟然出現 command not found 的問題!這是為什麼?因為系統上真的沒有 ll 這個指令, 該指令為使用命令別名暫時創造出來的一個命令的別稱 (別名) 而已。要查看有多少命令別名,可以使用 alias 來查看:
[root@localhost ~]# alias alias cp='cp -i' alias egrep='egrep --color=auto' alias fgrep='fgrep --color=auto' alias grep='grep --color=auto' alias l.='ls -d .* --color=auto' alias ll='ls -l --color=auto' <==指令別名! alias ls='ls --color=auto' alias mv='mv -i' alias rm='rm -i' ......
因此讀者應該能知道為何 /bin/ls -d /etc 與 ls -d /etc 輸出的結果會有顏色差異的問題了。此外,管理員執行 mv, cp, rm 等管理檔案的指令時, 為了避免不小心導致的檔案覆蓋等問題,於是僅有 root 的身份會加上 -i 的選項,提示管理員相關的檔案覆蓋問題。 我們的 student 因為在 .bashrc 裡面也額外加入 alias 了!這才會有相關的命令別名!
讓指令不以命令別名的方式執行是相當值得注意的喔!很有趣!
某些時刻管理員可能需要進行一串指令後,再將這串指令進行資料的處理,而非每個指令獨自運作。如下列指令的說明:
[student@localhost ~]$ date; cal -3; echo "The following is log" [student@localhost ~]$ date; cal -3; echo "The following is log" > mylog.txt [student@localhost ~]$ cat mylog.txt
讀者會發現到,原本想要紀錄的資訊當中,僅有最後一個指令才可以被處理了。若需要每個指令都進行紀錄,依據前面的介紹,則必須要如此處理:
[student@localhost ~]$ date > mylog.txt; cal -3 >> mylog.txt; echo "The following is log" >> mylog.txt
指令會變得相當複雜。此時,可以透過資料統整的方式,亦即將所有的指令包含在小括號內,就能夠將訊息統一輸出了。
[student@localhost ~]$ (date; cal -3; echo "The following is log") > mylog.txt
某些時刻使用者可能需要將螢幕的資訊轉存成為檔案以方便紀錄,就是前幾堂課就已經談到過得 > 這個符號的功能。 事實上,這個功能就是資料流重導向。
從前一小節的說明,讀者可以知道指令執行後,至少可以輸出正確與錯誤的資料 ($? 是否為 0), 某些指令執行時,則會從檔案取得資料來處理,例如 cat, more, less 等指令。因此,將指令對於資料的載入與輸出彙整如下圖:
簡單的說,標準輸出指的是『指令執行所回傳的正確的訊息』,而標準錯誤輸出可理解為『 指令執行失敗後,所回傳的錯誤訊息』。 不管正確或錯誤的資料都是預設輸出到螢幕上,我們可以透過特殊的字符來進行資料的重新導向!
上述的例題練習完畢後,可以將特殊的字符歸類成:
一般來說,檔案無法讓兩個程序同時打開還同時進行讀寫!因為這樣資料內容會反覆的被改寫掉,所以你不應該使用如下的方式來下達指令:
command > file.txt 2> file.txt
如果你需要將正確資料與錯誤資料同步寫入同一個檔案內,那就應該要這樣思考:
若同樣使用『 find /etc -name '*passwd*' 』這個指令來處理,則讀者可以嘗試使用底下的方案來執行上述三個資料轉管道的動作:
[student@localhost ~]$ find /etc -name '*passwd*' > ~/find_passwd2.txt 2>&1 [student@localhost ~]$ find /etc -name '*passwd*' 2> ~/find_passwd2.txt 1>&2 [student@localhost ~]$ find /etc -name '*passwd*' &> ~/find_passwd2.txt
但請注意指令的輸入順序, 2>&1 與 1>&2 必須要在指令的後面輸入才行。
有些指令在執行時,你需要敲擊鍵盤才行,這個 standard input 就可以由檔案內容來取代鍵盤輸入的意思。 舉例來說, cat 這個指令就是直接讓你敲擊鍵盤來由螢幕輸出資訊。
上面的例題中,我們可以透過 [ctrl]+d 的方式來結束輸入,但是一般用戶可能會看不懂 [ctrl]+d 代表的意義是什麼。 如果能夠使用 end 或 eof 等特殊關鍵字來結束輸入,那似乎更為人性化一些。你可以使用底下的指令來處理:
[student@localhost ~]$ cat > yourtype.txt << eof
> here is GoGo!
> eof
最後一行一定要完整的輸入 eof (上面的範例),這樣就能夠結束 cat 的輸入,這就是 << 的意義~
讀者在前幾堂課曾經看過類似『 ll /etc | more 』的指令,較特別的是管線 (pipe, |) 的功能。管線的意思是, 將前一個指令的標準輸出作為下一個指令的標準輸入來處理!流程有點像底下這樣的圖示:
另外,這個管線命令『 | 』僅能處理經由前面一個指令傳來的正確資訊,也就是 standard output 的資訊,對於 stdandard error 並沒有直接處理的能力。如果你需要處理 standard error output ,就得要搭配 2>&1 這種方式來處理才行了。
常見的管線命令有:
建議使用者可以 man wc 看一下該指令的選項功能喔!
作業硬碟一般操作說明:
作業當中,某些部份可能為簡答題~若為簡答題時,請將答案寫入 /home/student/ans.txt 當中,並寫好正確題號,方便老師訂正答案。 請注意,檔名寫錯將無法上傳!
請使用 root 的身份進行如下實做的任務。直接在系統上面操作,操作成功即可,上傳結果的程式會主動找到你的實做結果。
1 r-s--x--- 1 rw-r--r-- 10 rwsr-xr-x 3 rws--x--x 3 rwx------ 4 rwxr-sr-x 241 rwxrwxrwx 8 rwxr-x--- 1633 rwxr-xr-x ...
[student@localhost ~]$ mymsg.sh
Hollo!!
My name is 'Internet Lover'...
My server's kernel version is $kver
I'm a student
bye bye!!
作業結果傳輸:請以 root 的身分執行 vbird_book_check_unit 指令上傳作業結果。 正常執行完畢的結果應會出現【XXXXXX_aa:bb:cc:dd:ee:ff_unitNN】字樣。若需要查閱自己上傳資料的時間, 請在作業系統上面使用瀏覽器查詢: http://192.168.251.254 檢查相對應的課程檔案。 相關流程請參考: vbird_book_check_unit