第 8 堂課:bash 指令連續下達與資料流重導向
前一節課針對 bash 做簡單的變數與環境操作之介紹,本節將針對 bash 環境中常用的連續指令下達方式, 以及資料處理常用的資料流重導向與管線命令進行介紹。這些資料處理的技術對於管理員來說是相當重要的, 尤其在自撰腳本程式分析登錄檔時,這就顯的非常的重要。
- 8.1:連續指令的下達
- 8.1.1:指令回傳值
- 8.1.2:連續指令的下達
- 8.1.3:使用 test 及 [ 判斷式 ] 確認回傳值
- 8.1.4:命令別名
- 8.1.5:用 () 進行資料彙整
- 8.2:資料流重導向
- 8.2.1:指令執行資料的流動
- 8.2.2:管線 (pipe) | 的意義
- 8.3:課後練習操作
8.1:連續指令的下達
某些情況下,使用者可能會連續的進行某些指令的下達。但這些指令之間可能會有關連性,例如前一個指令成功後才可進行下一個指令等。 這些情況就需要使用到特殊的字符來處理。
8.1.1:指令回傳值
指令、變數、計算式可以使用特殊的符號來處理,請完成底下的練習:
例題:請將『變數』、『指令』、『數學計算式』、『純文字』、『保持 $ 功能』等寫入下方的空格中
|
指令的執行正確與否與後續的處理有關。Linux 環境下預設的指令正常結束回傳值為 0 ,呼叫的方式為使用『 echo $? 』即可, 亦即找出 ? 這個變數的內容即可。
例題:了解各指令回傳值的意義
|
上述的練習在讓使用者了解到,指令回傳值是每個指令自己指定的,只要符合 bash 的基本規範即可。
8.1.2:連續指令的下達
指令是可以連續輸入的,直接透過分號 (;) 隔開每個指令即可。在沒有相依性的指令環境中,可以直接進行如下的行為:
- 列出目前的日期,直接使用 date 即可;
- 使用 uptime 列出目前的系統資訊;
- 列出核心資訊;
[student@localhost ~]$ date; uptime; uname -r
五 7月 29 22:16:04 CST 2016
22:16:04 up 14 days, 23:35, 2 users, load average: 0.00, 0.01, 0.05
3.10.0-327.el7.x86_64
|
如此一口氣就可以直接將所有的指令執行完畢,無須考量其他問題。當使用者有多個指令需要下達,每個指令又需要比較長的等待時間時, 可以使用這種方式來處理即可。但是,如果想要將這些資訊同步輸出到同一個檔案時,應該如何處理?參考底下兩個範例後,說明其差異為何?
[student@localhost ~]$ date; uptime; uname -r > myfile.txt [student@localhost ~]$ (date; uptime; uname -r ) > myfile.txt |
- 具有指令相依性的 && 與 ||
分號 (;) 是直接連續下達指令,指令間不必有一定程度的相依性。但當指令之間有相依性時,就能夠使用 && 或 || 來處理。 這兩個處理的方式如下:
- command1 && command2
當 command1 執行回傳為 0 時(成功),command2 才會執行,否則就不執行。 - command1 || command2
當 command1 執行回傳為非 0 時(失敗),command2 才會執行,否則就不執行。
例題:當不存在 /dev/shm/check 時,就建立該目錄,若已經建立該目錄,就不做任何動作。
雖然可以使用 mkdir -p /dev/shm/check 來動作,不過我們假設該目錄的檢查為使用 ls 檢測即可。
當 ls /dev/shm/check 顯示錯誤時,表示該檔名不存在,此時才使用 mkdir (不要加上 -p 的選項) 來處理。
例題:當 /dev/shm/check 存在時,就將該目錄刪除,否則就不進行任何動作
|
假設我們需要一個指令來說明某個檔名是否存在,可以這樣處理:
[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 |
例題:
上述的指令能否寫成:『 ls -d /vbird &> /dev/null || echo non-exist && echo exist 』,嘗試說明原因。
|
由於上述指令要修改很麻煩,假設我們需要使用『 checkfile filename 』來處理,此時可以撰寫一隻小腳本來進行此任務。 若該指令可以讓所有用戶執行,則可將指令寫入 /usr/local/bin 目錄內。
[root@localhost ~]# vim /usr/local/bin/checkfile #!/bin/bash ls -d ${1} &> /dev/null && echo exist || echo non-exist [root@localhost ~]# chmod a+x /usr/local/bin/checkfile [root@localhost ~]# checkfile /etc exist [root@localhost ~]# checkfile /vbird non-exist |
在 checkfile 檔案中,第一行 (#!/bin/bash) 代表使用 bash 來執行底下的語法,第二行當中的變數 ${1} 代表在本檔案後面所接的第一個參數,因此執行時,就能夠直接將要判斷的檔名接在 checkfile 後面即可。
8.1.3:使用 test 及 [ 判斷式 ] 確認回傳值
事實上,前一小節使用 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 是直接加在變數判斷之前,讀者可能偶而會覺得怪異。此時可以使用中括號 [ ] 來取代 test 的語法。 同樣以 checkfile 來處理時,該檔案的內容應該需要改寫成如下:
[root@localhost ~]# vim /usr/local/bin/checkfile
#!/bin/bash
[ -e "${1}" ] && echo exist || echo 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 [student@localhost ~]$ echo $? |
上述的 ${@} 代表指令後面接的任何參數,因此你可以執行 myls.sh 後面接多個參數都沒問題。 由於 exit 可以回傳訊息,因此可以讓使用者簡易的設定好所需要的回傳訊息規範。
8.1.4:命令別名
第一堂課開始讀者應該就接到到 ls 與 ll 這兩個指令,剛開始介紹時,讀者們應該知道 ll 是 long list 的縮寫。 若將 ll 這個指令用來取代 checkfile 這個腳本,是否可以處理?
[root@localhost ~]# vim /usr/local/bin/checkfile
#!/bin/bash
#[ -e "${1}" ] && echo exist || echo non-exist
ll -d ${1} && echo exist || echo non-exist
|
但是,當你執行 checkfile /etc 時,竟然出現 command not found 的問題!這是為什麼?因為系統上真的沒有 ll 這個指令, 該指令為使用命令別名暫時創造出來的一個命令的別稱 (別名) 而已。若你在 root 的身份輸入 alias 與在 student 的身份輸入 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' alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' [student@localhost ~]$ alias 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 vi='vim' alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' |
因此讀者應該能知道為何 /bin/ls -d /etc 與 ls -d /etc 輸出的結果會有顏色差異的問題了。此外,管理員執行 mv, cp, rm 等管理檔案的指令時, 為了避免不小心導致的檔案覆蓋等問題,於是僅有 root 的身份會加上 -i 的選項,提示管理員相關的檔案覆蓋問題。
例題:
|
例題:
|
8.1.5:用 () 進行資料彙整
某些時刻管理員可能需要進行一串指令後,再將這串指令進行資料的處理,而非每個指令獨自運作。如下列指令的說明:
[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
|
例題:
|
8.2:資料流重導向
某些時刻使用者可能需要將螢幕的資訊轉存成為檔案以方便紀錄,就是前幾堂課就已經談到過得 > 這個符號的功能。 事實上,這個功能就是資料流重導向。
8.2.1:指令執行資料的流動
從前一小節的說明,讀者可以知道指令執行後,至少可以輸出正確與錯誤的資料 ($? 是否為 0), 某些指令執行時,則會從檔案取得資料來處理,例如 cat, more, less 等指令。因此,將指令對於資料的載入與輸出彙整如下圖:
- standard output 與 standard error output
簡單的說,標準輸出指的是『指令執行所回傳的正確的訊息』,而標準錯誤輸出可理解為『 指令執行失敗後,所回傳的錯誤訊息』。 不管正確或錯誤的資料都是預設輸出到螢幕上,我們可以透過特殊的字符來進行資料的重新導向!
- 標準輸入 (stdin) :代碼為 0 ,使用 < 或 << ;
- 標準輸出 (stdout):代碼為 1 ,使用 > 或 >> ;
- 標準錯誤輸出(stderr):代碼為 2 ,使用 2> 或 2>> ;
例題:
|
上述的例題練習完畢後,可以將特殊的字符歸類成:
- 1> :以覆蓋的方法將『正確的資料』輸出到指定的檔案或裝置上;
- 1>>:以累加的方法將『正確的資料』輸出到指定的檔案或裝置上;
- 2> :以覆蓋的方法將『錯誤的資料』輸出到指定的檔案或裝置上;
- 2>>:以累加的方法將『錯誤的資料』輸出到指定的檔案或裝置上;
一般來說,檔案無法讓兩個程序同時打開還同時進行讀寫!因為這樣資料內容會反覆的被改寫掉,所以你不應該使用如下的方式來下達指令:
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
有些指令在執行時,你需要敲擊鍵盤才行,這個 standard input 就可以由檔案內容來取代鍵盤輸入的意思。 舉例來說, cat 這個指令就是直接讓你敲擊鍵盤來由螢幕輸出資訊。
例題:
|
上面的例題中,我們可以透過 [ctrl]+d 的方式來結束輸入,但是一般用戶可能會看不懂 [ctrl]+d 代表的意義是什麼。 如果能夠使用 end 或 eof 等特殊關鍵字來結束輸入,那似乎更為人性化一些。你可以使用底下的指令來處理:
[student@localhost ~]$ cat > yourtype.txt << eof
> here is GoGo!
> eof
|
最後一行一定要完整的輸入 eof (上面的範例),這樣就能夠結束 cat 的輸入,這就是 << 的意義~
8.2.2:管線 (pipe) | 的意義
讀者在前幾堂課曾經看過類似『 ll /etc | more 』的指令,較特別的是管線 (pipe, |) 的功能。管線的意思是, 將前一個指令的標準輸出作為下一個指令的標準輸入來處理!流程有點像底下這樣的圖示:
另外,這個管線命令『 | 』僅能處理經由前面一個指令傳來的正確資訊,也就是 standard output 的資訊,對於 stdandard error 並沒有直接處理的能力。如果你需要處理 standard error output ,就得要搭配 2>&1 這種方式來處理才行了。
- 管線僅會處理 standard output,對於 standard error output 會予以忽略
- 管線命令必須要能夠接受來自前一個指令的資料成為 standard input 繼續處理才行。
常見的管線命令有:
- cut :裁切資料,包括透過固定符號或者是固定字元位置
- grep :擷取特殊關鍵字的功能
- awk '{print $N}' :以空白為間隔,印出第 N 個欄位的項目
- sort :進行資料排序
- wc :計算資料的行數、字數、字元數
- uniq :資料依行為單位,進行重複資料的計算
- tee :將資料轉存一份到檔案中
- split :將資料依據行數或容量數切割成數份
例題:我想要查看 /etc 底下共有多少檔案檔名結尾為 .conf 的檔案個數,該如何處理?
|
例題:
|
8.3:課後練習操作
前置動作:請使用 unit08 的硬碟進入作業環境,並請先以 root 身分執行 vbird_book_setup_ip 指令設定好你的學號與 IP 之後,再開始底下的作業練習。
請使用 root 的身份進行如下實做的任務。直接在系統上面操作,操作成功即可,上傳結果的程式會主動找到你的實做結果。
- 嘗試以系統上面的實做資料後,回答下列問題,並將答案寫入 /root/ans08.txt 當中:
- 當執行完成 mysha.sh 這個指令之後,該指令的回傳值為多少?
- 在不透過 bc 這個指令的情況下,如何以 bash 的功能,計算一年有幾秒?亦即如何計算出 60*60*24*365 的結果?
- 使用 find / 找出全系統的檔名,然後將所有資料 (包括正確與錯誤) 全部寫入 /root/find_filename.txt , 請寫下達成此目標的完整指令方法。
- 寫下一段指令 (主要以 echo 來達成的),執行該段指令會輸出『 My $HOSTNAME is 'XXX' 』,其中 XXX 為使用 hostname 這個指令所輸出的主機名稱。例如主機名稱為 station1 時,該串指令會輸出『 My $HOSTNAME is 'station1'』。 該串指令在任何主機均可執行,但都會輸出不同的訊息(因為主機名稱不一樣所致)
- 管理員 (root) 執行 mv 時,由於預設 alias 的關係,都會主動的加上 mv -i 這個選項。請寫下兩個方法, 讓 root 執行 mv 時,不會有 -i 的預設選項 (不能 unalias 的情況下)
- 透過『 ll /usr/sbin/* /usr/bin/* 』搭配 cut 與 sort, uniq 等指令來設計一個指令串,執行該指令串之後會輸出如下的畫面,
請寫下該指令串:(底下畫面為示意圖,實際輸出的個數可能會有些許差異)
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 ...
- 製作一個名為 mycmdperm.sh 的腳本指令,放置於 /usr/local/bin 裡面。該腳本的重點是這樣的:
- 執行腳本的方式為『 mycmdperm.sh command 』,其中 command 為你想要取得的指令的名稱
- 在 mycmdperm.sh 裡面,指定一個變數為 cmd ,這個變數的內容為 ${1},其中 ${1} 就是該腳本後面攜帶的第一個參數
- 使用『 ll $( which ${cmd} ) 』來取得這個 cmd 的實際權限。
- 讓 mycmdperm.sh 具有可執行權。
- 最終請執行一次該指令,例如使用『 mycmdperm.sh passwd 』應該會秀出 passwd 的相關權限。不過該指令應該會執行失敗, 因為上述的 (c) 指令怪怪的,似乎是『命令別名無法用在腳本內』的樣子。因此,請將這個腳本的內容修訂成為沒有問題的形式。 (就是將命令別名改成實際的指令操作)
- 製作一個名為 myfileperm.sh 的腳本指令,放置於 /usr/local/bin 裡面。該腳本的重點是這樣的:
- 執行腳本的方式為『 myfileperm.sh filename 』,其中 filename 為你想要取得的檔案名稱 (絕對路徑或相對路徑)
- 在 myfileperm.sh 裡面,指定一個變數為 filename ,這個變數的內容為 ${1},其中 ${1} 就是該腳本後面攜帶的第一個參數
- 判斷 filename 是否不存在,若不存在則回報『filename is non exist』
- 判斷 filename 存在,且為一般檔案,若是則回報『 filename is a reguler file 』
- 判斷 filename 存在,且為一般目錄,若是則回應『 filename is a directory』
- 製作一個 mymsg.sh 的腳本指令,放置於 /usr/local/bin 底下:
- 主要使用 cat 搭配 << eof 這樣的指令語法來處理
- 當執行 mymsg.sh 時,螢幕會輸出底下的字樣,然後結束指令。
[student@localhost ~]$ mymsg.sh Hollo!! My name is 'Internet Lover'... My server's kernel version is $kver I'm a student bye bye!!
- 檔案與檔案內容處理方法:
- 找出 /etc/services 這個檔案內含有 http 的關鍵字那幾行,並將該資料轉存成 /root/myhttpd.txt 檔案
- 找出 examuser 這個帳號在系統所擁有的檔案,並將這些檔案放置到 /root/examuser 目錄中
- root 的 bash 環境操作設定: (主要是修改 .bashrc 喔!且指令內的指令需要用到 $ 時,得輸入 \$ 才可以)
- 做一個命令別名為 myip 的指令,這個指令會透過 ifconfig 的功能,顯示出 eth0 這張網卡的 IP (只要 IP 就好喔!)。例如 IP 為 192.168.251.12 時,則輸入 myip 這個指令,螢幕只會輸出 192.168.251.12 的意思。
- 建立一個命令別名 myerr 這個指令,這個指令會將『 echo "I am error message" 』這個訊息傳輸到 standard error output 去! 亦即當執行『 (myerr) 』時,會在螢幕上出現 I am error message,但是執行『 (myerr) 2> /dev/null 』時,螢幕不會有任何訊息的輸出。
- 建立一個名為 /root/split 的目錄,進行如下的行為:
- 將 /etc/services 複製到本目錄下
- 假設 services 容量太大了,現在請以 100K 為單位,將該檔案拆解成 file_aa, file_ab, file_ac.. 等檔名的檔案, 每個檔案最大為 100K (請自行 man split 去處理)
作業結果傳輸:請以 root 的身分執行 vbird_book_check_unit 指令上傳作業結果。 正常執行完畢的結果應會出現【XXXXXX;aa:bb:cc:dd:ee:ff;unitNN】字樣。若需要查閱自己上傳資料的時間, 請在作業系統上面使用: http://192.168.251.250 檢查相對應的課程檔案。
2017/03/26:加入了作業了!這一章的作業很多其實不需要實際的作業環境就可以先練習囉!
2016/11/17以來統計人數