2022年8月27日 星期六

Linux 指令:sed

動機:我想了解 sed 指令的原因

最早讓我想了解 sed 指令的原因,是在某次的操作過程中,需要查看目前的 $PATH,到底包含了哪些目錄,所以我下了一道指令:

echo $PATH

結果出現了這樣的答案:

面對這麼一長串的文字,我就想如果可以依照字符:來做分割並換行顯示,那我需要的資訊更容易閱讀。於是就 google 了一下:

然後就看到了這個答案:

於是就開始了我對 sed 指令的探索之旅。

sed 指令簡介

sed 全名為 Stream EDitor,是一個使用簡單緊湊的程式語言來解析和轉換文字 Unix 實用程式,是為命令列處理資料檔案而構建的早期 Unix 指令之一。

sed 和 awk 這兩大工具時常被拿來相提並論,因二者一樣強大和對正規表示法有良好的支援。也各自有自己專屬的腳本語言(script language),sed 主要功能為自動化的修改文字檔,而 awk 可想像為超輕量級的 C 直譯語言(Interpreted language)屬通用,偏向統計和需輸出重新排版的應用。

man page 是給已熟悉的人參考用的,用 man page 學 sed 和 awk 就好像用英文字典學英文會話。基本上若直接用 man page 來覺習 sed,這相當有難度,所以若能借用他人的使用心得與範例,相信一定是一個更好的方式。

sed 主要功能:檔案字串修改

grep 雖可利用功能強大的正規表示法搜尋檔案中的字串,但沒辦法對搜尋到的字串進行刪除,取代或插入等編輯動作。補足 grep 編輯功能的工具就是 sed,更甚者 sed 可程式化的特性常用來自動化的修改文字檔。

雖然 vi 也可用來搜尋/修改檔案內容,但要人工把檔案打開,改完再存檔人力要介入很深,如熟悉 sed 的操作這些都可自動化的完成。例如公司搬家,有舊公司地址的表格文件一堆,如善用 sed 就可有效率的自動把所有文件表格上的舊地址改新地址。

sed 用法有點抽象,故對基本用法和每一參數分別說明。

sed 基本用法

sed 和其他功能強大的指令一樣,通常功能愈強語法就愈複雜和抽象,sed 也是如此。如有常自動化的修改文字檔,是值得一學的好工具。

sed 基本的用法如下:

sed [-OPTION] [ADD1][,ADD2] [COMMAND] [/PATTERN][/REPLACEMENT]/[FLAG] [FILE]

但光看其晦澀不明的語法是不太可能會使用的,故先舉個例子,一例解千文。

範例一:

我要把檔案 "MyFile.txt" 的 1~8 行中的 "The" 或 "the" 改為大寫的 "THE" 可如下:

sed -e '1,8 s/[Tt]he/THE/g' MyFile.txt

其中:
  -e:為預設選項,一般的狀況可省略,如沒加任何選項預設是以是 sed -e 選項來執行。
  s/[Tt]he/THE/g:sed 最基本的命令,代表搜尋樣板並取代。
     s:代表搜尋樣板並取代。
     [Tt]he:代表要搜尋樣板文字。
     THE:代表將用來取代搜尋到的樣板文字。
     g:指令最後的 g 代表搜尋全部範圍。

因每一欄參數都可能複雜和抽象,有時 sed 會解讀錯誤,故一般除檔案和選項以外的參數都會用單引號 ' 把其括起來。

範例二:

如欲搜尋和取代的樣板為單字(word),可在樣板和取代字串前加一空隔(因單字間有空隔),例如:有一句子為 "This is a book",我只想把單字 "is" 變大寫,但單字 "This" 也有子串 "is",處理不好輸出會變 "ThIS IS a book"。

echo 'This is a book' | sed 's/is/IS/g'

將輸出文字:

ThIS IS a book

所以正確的指令應該下為:

echo 'This is a book' | sed 's/ is/ IS/g'

sed 進階用法

Delimiter 分隔符號:

sed 每個參數之間預設分隔符號(delimiter)是用 "/" 來區別如 sed 's/OLD/NEW/g' flie,但如要搜尋樣板有 "/" 會和分隔符號混在一起如再加上跳脫字元 "\" 會變得好像火星文看不懂。

例如:
要把 Linux 路徑 "/abc/wxy" 改為 WINDOWS 路徑表示法的 "\abc\wxy",sed 的寫法為

sed 's/\//\\/g'
這一串類火星文,真的不易一眼看出什麼是什麼,所以故 sed 允許我們可以用除了空白 ' '、換行 '\n' 以外的字元(英文字母或數字或符號皆可)來當分隔符號,只要前後一致即可。

例如:用 : 當作分隔字元:

echo "This ia an apple" | sed 's:ap:Ap:g'

例如:Linux 路徑改為 WINDOWS 路徑表示法,用 # 當作分隔字元:

echo "/home/tmps07" | sed 's#/#\\#g'

ADDRESS 位址範圍:

sed 位址的表示法可為行號或合法正規表示法的樣板,可為有起始和結束二個位址的範圍或只有單一位址(如第幾行或指定的樣板)。

有些 COMMAND 一定要配合單一位址,大部分的 COMMAND 位址的表示法為範圍,有些 COOMAND 如省略位址表示是全部(如上例搜尋並取代的 COMMAND "s")。

位址範圍的用法各如下:

位址表示法
為一個起始位址加上一個結束位址,兩者間以 "," 分隔。如只有一個位址則為固定單一位址。

$ sed '1,5 s/ [aA]/ one/g'  file # 將 1~5 行 "a" 或 "A" 改 "one"
$ sed '5 s/ [aA]/ one/g' file # 只將第 5 行 "a" 或 "A" 改 "one"
$ sed 's/ [aA]/ one/g' file # 省略位址, 整個檔案 "a" 或 "A" 改 "one"

檔案的開頭我們都知道是第一行,但檔案的結尾往往不知是第幾行,此時可用 "$" 來代表最後一行(和 vi 用法一樣,"$" 代表最後一行)。

$ sed '3,$ s/^can/CAN/g' file # 將 3 行到最後一行開頭為 "can" 改大寫

樣板表示法
有起始樣板和結束樣板,每一樣板以成對的 "/" 括起來,起始樣板和結束樣板間以 "," 分隔。

例:位址範圍從有 "The" 到 "When" 之間的行,將字串 "can" 改為 "CAN"

$ sed '/The/,/Whe/ s/ can/ CAN/g' < re.txt

例:將符合 "[Cc]an$" pattern 的那行 "a" 改 "A"

$ sed '/[Cc]an$/ s/a/A/g' < re.txt

混搭表示法
sed 的位址表示法和樣板表示法是可混合使用的。

例:第二行開始到符合 "The" 那行之間任何數字改為字串 "#"

$ sed '2,/The/ s/[0-9]/#/g' < re.txt

例:從有 "google" 那行到最後一行的 "a" 改大寫

$ sed '/google/,$ s/a/A/g' < re.txt

OPTION 選項:

了解 OPTION 選項時先要了解 sed 指令的一些術語,才不會不知所云。sed 的動作為一次只讀一行並去掉結尾的換行(EOL)到暫時的緩衝區(buffer)中,此暫時緩衝區稱為 "pattern space",接著處理完成後會把 pattern space 的內容送往螢幕後清空 pattern space 再去處理下一行,這樣不斷重複直到檔案結束。

如有內容符合搜尋的樣板之 pattern space 叫 "current pattern space",而 COMMAND "p" 或 "P" 可令 sed 再輸出 current pattern space 到螢幕,其基本的動作流程如下。

sed 主要的選項如下:

語法:
sed [-OPTION] [ADD1][,ADD2] [COMMAND] [/PATTERN][/REPLACEMENT]/[FLAG] [FILE]]
指令名稱/功能/命令使用者 選項 功能  
sed/(stream editor)檔案字串修改/Any -e 執行 sed 的 script 語法


-f 選用外部的 script 檔來執行  
-n 不輸出 pattern space 到螢幕  
-l # 和 COMMAND l 一起使用時
l 可以指定每一行的長度
#


-r 使用延伸正規表示  
--help 指令自帶說明  

sed 有些選項可獨力運作,但有些選項單獨使用是沒什意義的,如 -n 或 -l 要配合其他 sed COMMAND 才有意義。各選項說明如下:

  • -e:執行 sed 的 script 語法
    sed 本來就是執行自己專屬的 script 腳本語言,故如都沒選項此為預設選項可省略。但如要搜尋和取代的樣板是多重的,或許多 COMMAND 合併使用,就一定要用 "-e" 選項。

    例如:將 "t" 改為 "T" and "pen" 改為 "pencil"
    $ echo 'this is a pen' | sed -e 's/t/T/' -e 's/pen/&cil/'

    例如:許多 sed COMMAND 要使用,就一定要用 "-e" 選項
    $ echo 'this is a pen' | sed -e 's/t/T/' -e 's/pen/&cil/'
  • -f:選用外部的 script 檔來執行
    如 sed 要處理的任務是多重的,此時可把毎一任務一一寫在一起存成一外部 script 檔,再用選項 "-f" 來指定此外部 script 檔即可。這樣做的好處是規則改變時只要改此外部 script 檔即可,或寫許多 script 檔來處理不同的需求。
    例如:有一外部 script 檔 "sed_scr" 功能為把 a~d 改大寫
    $ cat sed_scr
    s/a/A/g
    s/b/B/g
    s/c/C/g
    s/d/D/g
    $ echo 'abcdefg' | sed -f sed_scr
    ABCDefg
  • -n:不輸出 pattern space 到螢幕
    因 sed 處理完成一行後會把 pattern space 的內容送往螢幕後清空,故如單獨使用選項 "-n" 連 pattern space 也不輸往螢幕故輸出什麼也沒有,故選項 "-n" 一般要配合其他的 sed FLAG 使用才有意義。
    一般是配合 sed FLAG "p" 只列出 current pattern space(符合樣板的 pattern),才不會不符合的 pattern 也往螢幕送,此時 sed 的行為可取代 grep。
    例如:選項 "-n" 配合 FLAG "p" 只列出符合樣板的行(類似指令 "grep")
    $ ls -d /etc/* | sed -n '/[A-Z][0-9]/ p'
  • -l:指定每一行的長度
    選項 "-l" (小寫的 L)這選項是配合 COMMAND "l" (小寫的 L)使用,因 sed COMMAND "l" 為把一些 ASCII 控制字元以 "\a"、"\b"、"\t",方式列出,但 ASCII 的控制字元大部分是用來文字定位或排縮,把定位功能取消列印出來可能會影響文字行的長度,故用選項 "-l" 來指定輸出到螢幕的長度。 例如:指定換行的長度=10
    $ echo -e 'This\tIs\tA\tDog' | sed -nl 10 'l'
  • -r: 使用延伸正規表示法解讀
    預設的情形下,sed 的樣板(pattern)只解讀正規表示法,加選項 "-r" 才會去以延伸正規表示法去解讀樣板。此時如樣板內有如 "?","+" 等符號會被認為是延伸正規表示法的 meta-charaters(表示字元)。不同的解讀結果可是差很多。
    例如:正常狀態下,將字串 "99?" 改為 "88"
    $ echo 'Why an apple $9.99?' | sed 's/99?/88/g'
    Why an apple $9.88
    例如:加選項 "-r" 用延伸正規表示法,將字串 "99?" 改為 "88"
    $ echo 'Why an apple $9.99?' | sed -r 's/99?/88/g'
    Why an apple $88.88?

FLAG 旗幟:

sed FLAG 主要為進一步的控制取代樣板(pattern)的行為,前面我們已用過 FLAG 中的 "g" (global replacement)和 "p" (print)。這兩個 FLAG 是最常使用的,其他的 FLAG 並不常用,了解一下即可,全部的 FLAG 用途如下:

Sed Flags
[g][ 數字] 全部取代或指定取代第幾個
I 忽略 pattern 大小寫
p 列印
w 寫入檔案
  • g:全部取代
    預設 sed 只取代搜尋/取代到第一個樣板此行就停止而去處理下一行,如有g FLAG 則全部搜尋和取代。
    例如:"is" → "IS",但預設只取代搜尋/取代到第一個
    $ echo 'this is an issue' | sed 's/is/IS/'
    thIS is an issue
    例如:加 flag "g" 全部取代
    $ echo 'this is an issue' | sed 's/is/IS/g'
    thIS IS an ISsue
    例如:第 3 個符合的樣本才取代
    $ echo 'aaaaaa aaaaaa' | sed 's/a/A/3'
    aaAaa aaaaa
    例如:第 3 個符合的樣本才取代,並重複到結束
    $ echo 'aaaaaa aaaaaa' | sed 's/a/A/3g'
    aaAAA AAAAA
  • I:忽略 pattern 大小寫
    此 FLAG 主要對樣板的大小寫一視同仁
    例如:
    $ echo 'this is an apple' | sed 's/APPLE/banana/I'
    this is an banana
  • p:列印 current pattern space
    如沒此 FLAG,sed 只輸出 pattern space。如加了 "p",FLAG 會再輸出 current pattern space。一般此 FLAG 是和選項 "-n" 合作只輸出符合的 pattern (current pattern space)。
    例如:
    
            
  • w:

    例如:
    
            

Regular Expression -正規表示式的簡易通則

正規表示式 Part 1

正規
表示式
說明及範例 比對不成立
之字串
/a/ 含字母 “a” 的字串。例如 “ab”,“bac”,“cba” “xyz”
/a./ 含字母 “a” 以及其後任一個字元的字串。例如 “ab”,“bac”。若要比對.,請使用 \. “a”,“ba”
/^xy/ 以 “xy” 開始的字串,例如 “xyz”,“xyab”。若要比對 ^,請使用 \^ “axy”,“bxy”
/xy$/ 以 “xy” 結尾的字串,例如 “axy”,“abxy”。若要比對 $,請使用 \$ “a”,“ba”
[13579] 包含 “1” 或 “3” 或 “5” 或 “7” 或 “9” 的字串,例如:”a3b”,“1xy” “y2k”
[0-9] 含數字之字串 不含數字之字串
[a-z0-9] 含數字或小寫字母之字串 不含數字及小寫字母之字串
[a-zA-Z0-9] 含數字或字母之字串 不含數字及字母之字串
b[aeiou]t “bat”,“bet”,“bit”,“bot”,“but” “bxt”,“bzt”
[^0-9] 不含數字之字串。若要比對 ^,請使用 \^ 含數字之字串
[^aeiouAEIOU] 不含母音之字串。若要比對 ^,請使用 \^ 含母音之字串
[^\^] 不含 “^” 之字串,例如 “xyz”,“abc” “xy^”,“a^bc”

正規表示式 Part 2

正規表示式的特定字元 說明 等效的正規表示式
\d 數字 [0-9]
\D 非數字 [^0-9]
\w 數字、字母、底線 [a-zA-Z0-9_]
\W 非 \w [^a-zA-Z0-9_]
\s 空白字元 [ \r\t\n\f]
\S 非空白字元 [^ \r\t\n\f]

正規表示式 Part 3

正規表示式 說明
/a?/ 零或一個 a。若要比對 ? 字元,請使用 \?
/a+/ 一或多個 a。若要比對 + 字元,請使用 \+
/a*/ 零或多個 a。若要比對 * 字元,請使用 \*
/a{4}/ 四個 a
/a{5,10}/ 五至十個 a
/a{5,}/ 至少五個 a
/a{,3}/ 至多三個 a
/a.{5}b/ a 和 b 中間夾五個(非換行)字元

正規表示式 Part 4

字元 說明 簡單範例
\ 避開特殊字元 /A\*/ 可用於比對 “A*”,其中 * 是一個特殊字元,為避開其特殊意義,所以必須加上 “\”
^ 比對輸入列的啟始位置 /^A/ 可比對 “Abcd” 中的 “A”,但不可比對 “aAb”
$ 比對輸入列的結束位置 /A$/ 可比對 “bcdA” 中的 “A”,但不可比對 “aAb”
* 比對前一個字元零次或更多次 /bo*/ 可比對 “Good boook” 中的 “booo”,亦可比對 “Good bk” 中的 “b”
+ 比對前一個字元一次或更多次,等效於 {1,} /a+/ 可比對 “caaandy” 中的 “aaa”,但不可比對 “cndy”
? 比對前一個字元零次或一次 /e?l/ 可比對 “angel” 中的 “el”,也可以比對 “angle” 中的 “l”
. 比對任何一個字元(但換行符號不算) /.n/ 可比對 “nay,an apple is on the tree” 中的 “an” 和 “on”,但不可比對 “nay”
(x) 比對 x 並將符合的部分存入一個變數 /(a*) and (b*)/ 可比對 “aaa and bb” 中的 “aaa” 和 “bb”,並將這兩個比對得到的字串設定至變數 RegExp.$1 和 RegExp.$2。
xy 比對 x 或 y /a*b*/g 可比對 “aaa and bb” 中的 “aaa” 和 “bb”
{n} 比對前一個字元 n 次,n 為一個正整數 /a{3}/ 可比對 “lllaaalaa” 其中的 “aaa”,但不可比對 “aa”
{n,} 比對前一個字元至少 n 次,n 為一個正整數 /a{3,}/ 可比對 “aa aaa aaaa” 其中的 “aaa” 及 “aaaa”,但不可比對 “aa”
{n,m} 比對前一個字元至少 n 次,至多 m 次,m、n 均為正整數 /a{3,4}/ 可比對 “aa aaa aaaa aaaaa” 其中的 “aaa” 及 “aaaa”,但不可比對 “aa” 及 “aaaaa”
[xyz] 比對中括弧內的任一個字元 /[ecm]/ 可比對 “welcome” 中的 “e” 或 “c” 或 “m”
[^xyz] 比對不在中括弧內出現的任一個字元 /[^ecm]/ 可比對 “welcome” 中的 “w”、”l”、”o”,可見出其與 [xyz] 功能相反。(同時請注意 /^/ 與 [^] 之間功能的不同。)
[\b] 比對退位字元(Backspace character) 可以比對一個 backspace ,也請注意 [\b] 與 \b 之間的差別
\b 比對英文字的邊界,例如空格 例如 /\bn\w/ 可以比對 “noonday” 中的 "no":/\wy\b/ 可比對 “possibly yesterday.” 中的 "ly"
\B 比對非「英文字的邊界」 例如,/\w\Bn/ 可以比對 “noonday” 中的 "on",另外 /y\B\w/ 可以比對 “possibly yesterday.” 中的 "ye"
\cX 比對控制字元(Control character),其中 X 是一個控制字元 /\cM/ 可以比對 一個字串中的 control-M
\d 比對任一個數字,等效於 [0-9] /[\d]/ 可比對 由 “0” 至 “9” 的任一數字 但其餘如字母等就不可比對
\D 比對任一個非數字,等效於 [^0-9] /[\D]/ 可比對 “w” “a”… 但不可比對如 “7” “1” 等數字
\f 比對 form-feed 若是在文字中有發生 “換頁” 的行為 則可以比對成功
\n 比對換行符號 若是在文字中有發生 “換行” 的行為 則可以比對成功
\r 比對 carriage return  
\s 比對任一個空白字元(White space character),等效於 [ \f\n\r\t\v] /\s\w*/ 可比對 “A b” 中的 “b”
\S 比對任一個非空白字元,等效於 [^ \f\n\r\t\v] /\S/\w* 可比對 “A b” 中的 “A”
\t 比對定位字元(Tab)  
\v 比對垂直定位字元(Vertical tab)  
\w 比對數字字母字元(Alphanumerical characters)或底線字母(”_”),等效於 [A-Za-z0-9_] /\w/ 可比對 “.A _!9” 中的 “A”、”_”、”9”
\W 比對非「數字字母字元或底線字母」,等效於 [^A-Za-z0-9_] /\W/ 可比對 “.A _!9” 中的 “.”、” “、”!”,可見其功能與 /\w/ 恰好相反
\ooctal 比對八進位,其中octal是八進位數目 /\oocetal123/ 可比對 與 八進位的ASCII中 “123” 所相對應的字元值
\xhex 比對十六進位,其中hex是十六進位數目 /\xhex38/ 可比對 與 16進位的ASCII中 “38” 所相對應的字元

2022年8月23日 星期二

Linux IO 輸入與輸出重新導向

I/O 的重新導向是 Linux 系統中很重要的一個特性,它可以讓我們任意串接各種程式的輸入與輸出、將資料導入檔案或從檔案中導出資料,結合多種 Linux 指令,組成任意的「指令管線」(command pipeline)。

輸入與輸出的重新導向

一般的 Linux 指令在執行時,會有三個輸入與輸出的資料流,分別為:

  1. 標準輸入(standard input,代碼為 0):程式執行所需要的輸入資料。
  2. 標準輸出(standard output,代碼為 1):程式正常執行所產生的輸出資料。
  3. 標準錯誤輸出(standard error output,代碼為 2):程式出錯時通知使用者用的訊息,或是呈現程式狀態用的訊息。

Linux 程式執行時的狀況就像這樣:

而重新導向的作用就是改變這些資料的流向,讓使用者可以非常彈性的組合各種程式,以下我們以範例來說明重新導向的用法。

標準輸出

最典型的程式會將程式執行的結果輸出在螢幕上(也就是說標準輸出預設就是螢幕),而我們可以使用 > 這個重新導向的運算子,將程式的標準輸出導向檔案,這樣輸出的訊息內容就會被儲存在檔案中,其用法為:

N > FILE

其中 N 是要設定重新導向的檔案代碼,若省略的話預設值為標準輸出的檔案代碼(即 1),而 FILE 就是要儲存輸出資料的檔案名稱。

以下是一個簡單的例子:

ls > output.txt

這樣就會把 ls 指令的輸出儲存至 output.txt 檔案中,而執行這行指令時,螢幕上就不會有其他的輸出了。

在執行上面這行指令時,如果 output.txt 這個檔案不存在的話,就會自動建立這個檔案,並把資料寫入其中。但若是這個檔案已經存在了,系統會把它的內容先清空,再將 ls 的輸出儲存進去,所以如果原本 output.txt 檔案中存在有舊的資料,就會全部被清掉。

如果想要以附加的方式把程式的輸出放在原本的檔案內容之後,可以使用 >>,其用法也是非常類似:

N >> FILE

以下為實際範例:

date >> output.txt
這樣一來,他就會在 output.txt 檔案中,於原本的 ls 輸出之後,再加上一行日期資訊。

標準錯誤輸出

若程式發生錯誤時,錯誤訊息預設也是會輸出在螢幕上(標準錯誤輸出預設為螢幕),例如:

ls non_exist > output.txt

這裡我們使用 ls 查看一個不存在的檔案,讓它產生錯誤,執行之後會在螢幕上看到一行錯誤訊息:

ls: non_exist: No such file or directory

這一行就是來自於 ls 標準錯誤輸出的訊息,而 output.txt 這個輸出檔案也會被建立,不過它的內容是空的(因為程式沒有產生任何正常的輸出)。

如果我們想要把程式的錯誤訊息導入檔案,可以使用 > 運算子,把標準錯誤輸出(2)導至指定的檔案:

ls non_exist > output.txt 2 > error.txt

這行指令就會將 ls 的標準錯誤輸出導入 error.txt 檔案,而正常的輸出則一樣導入 output.txt。

這是以附加方式寫入 error.txt 的例子:

ls non_exist > output.txt 2 >> error.txt

如果想要把正常的輸出以及錯誤的輸出都一起導入同一個 output.txt,可以加上 2 > &1

ls non_exist > output.txt 2 > &1

2 > &1 就是把標準錯誤輸出(2)導入標準輸出(1)的意思,然後再靠著 > 把所有的資料全部導入 output.txt,這樣所有的輸出訊息就會一起存入 outpupt.txt 中了。

這類重新導向的語法可以自己變化,例如我們也可以把資料都導向標準錯誤輸出,然後再導向檔案,結果是一樣的:

ls non_exist 2 > output.txt 1 > &2

另外還有一種寫法也可以將標準輸出與標準錯誤輸出都導向至同一個檔案:

ls non_exist &> output.txt

或是這樣也可以:

ls non_exist >& output.txt

標準輸入

一般需要輸入資料的 Linux 程式如果執行時沒有給他資料的話,預設就會從鍵盤讀取資料(也就是標準輸入預設是鍵盤),例如:

cat

直接執行 cat 時,他就會等待使用者從鍵盤輸入資料,並將接收到的資料輸出在螢幕上。

我們可以使用 < 運算子,將指定的檔案設定為程式的標準輸入,這樣他就會從檔案中讀取資料,用法如下:

cat < input.txt

這樣 cat 就可以取得 input.txt 檔案中的資料,並且顯示在螢幕上。

管線

前面介紹過的重新導向都是用於程式與檔案之間的資料流,而如果要把兩個程式的輸入與輸出串接起來,就可以使用管線(pipe)。

管線的寫法是 |,以下是一個例子:

ls | nl

nl 這個指令會把每一行資料加上行號,這一指令會將 ls 的輸出導給 nl,讓每個檔案名稱加上行號後,輸出在螢幕上。

管線與重新導向可以混用:

ls | nl > output 2 > &1

串接多個程式的狀況也是很常見的:

ls | grep keyword | nl | head -n 5

這個例子是在 ls 的輸出中,以 grep 篩選出有 keyword 的檔名,交給 nl 加上行號,最後交給 head 輸出前 5 行資料。

Linux 特殊檔案

在 Linux 系統上有一些特殊的檔案,善用這些特殊檔案可以讓我們在撰寫 shell 指令稿時更方便。

/dev/null

/dev/null 是 Linux 系統的空裝置,所有寫入這個檔案的資料都會被直接丟棄,而如果從這個檔案讀取資料,則會像讀取空檔案一樣,立即得到一個 EOF。

最常見的用法就是將沒有用的程式輸出導引至 /dev/null,這樣就不會讓沒用的輸出干擾螢幕畫面:

ls > /dev/null

上面這個例子是將 ls 的輸出導向至 /dev/null,這樣螢幕上就不會出現任何訊息。

以下是一個實務上比較常用的例子,假設我們想要檢查 Linux 系統上特定的程式有沒有在執行,我們可以在 ps 指令的輸出中,尋找特定的關鍵字,執行完之後,以 $? 查看最後一個 grep 執行的結果:

KEYWORD=chromium
ps aux | grep $KEYWORD | grep -v grep > /dev/null
echo $?

這我們要找的是 Chromium 瀏覽器的行程,看看系統上是不是有 Chromium 瀏覽器在執行,若有找到的話,$? 的值就會是 0,若沒有找到的話 $? 就會是 1。

在這個例子中,我們只需要 grep 最後的傳回值,而不需要看它輸出,所以我們將其標準輸出直接導向至 /dev/null 丟棄。

另外一個典型的例子就是把程式的任何輸出都丟棄,也就是把標準輸出與標準錯誤輸出都導向至 /dev/null,這個用法在執行自己撰寫的指令稿時很有用:

/path/to/script.sh > /dev/null 2 > &1

如果我們將自己的指令稿放在 crontab 中執行時,通常都會以這個方式丟棄所有的輸出,因為如果輸出太多訊息,會造成系統管理者有收不完的回報信件。

/dev/zero

/dev/zero 是一個特殊檔案,當我們從這個檔案讀取資料時,會讀出無限個 NULL 字元(0x00)資料,這個檔案可以用於清除硬碟資料:

# 清除硬碟(不可執行)
dd if=/dev/zero of=/dev/sda1

或是建立內容都是 0x00 的檔案:

dd if=/dev/zero of=file.dat count=1024 bs=1024

請參閱:dd - 備份與回覆資料的小工具

/dev/random 與 /dev/urandom

/dev/random 與 /dev/urandom 是 UNIX/Linux 系統的隨機資料來源,從這兩個檔案讀取出來的資料是隨機產生的,這兩個檔案功能相同,差別在於資料產生速度與品質。

/dev/random 的資料產生速度比較慢,但是品質比較好(比較接近隨機),如果從中讀取大量資料,超過系統所能產生的隨機量時,就會出現阻塞(block)的狀況。

/dev/urandom 在產生隨機資料時,若系統所能產生的隨機量不足,就會根據其他的方式生成隨機的資料,不會產生阻塞的狀況,缺點就是資料沒有那麼隨機。

以下是從 /dev/urandom 讀取資料,交給 cksum 產生隨機亂數的例子:

head -200 /dev/urandom | cksum

/dev/full

/dev/full 是一個常滿裝置,不管寫入什麼資料,都會因為空間不足而無法寫入,這個檔案通常被用來測試磁碟無剩餘空間時,程式所產生的行為。

ls > /dev/full

ls: write error: No space left on device