入門
printf
1 2 |
printf "The first program always prints '%s, %s!'\n" Hello world |
- 格式聲明 (format specfications) 是一種佔位符號 (placeholder),結構包含 1) 百分比符號 (%) 2) 指示符 (specifier),常用的有字符串
%s
及十進位整數%d
tr
範例:Translate DOS file to UNIX
1 2 |
tr -d '\r' < dos-file.txt | sort > UNIX-file.txt |
tr -d
: 自 stdin 刪除 source-char-list 的字符\r
: ASCII carriage return
/dev/tty
範例:Read password via /dev/tty
1 2 3 4 5 |
printf "Enter new password: " stty -echo read pass < /dev/tty stty echo |
- 當程序打開 /dev/tty 時,UNIX 會將它重定向到一個終端再與程序結合,該終端可以是 1)實體的 console 2) 串行端口 (serial port) 3) 偽終端 (pseudoterminal)
- stty (set tty) 用來控制終端的設置,
echo
/-echo
選項用來開關自動打印輸入的功能
i18n and l10n
- 當 i18n 作為設計軟體的過程時,無須再修改軟體或重新編譯程式代碼,就可以給特定的群體使用
- 當 l10n 作為設計軟體的過程時,目的是讓特定的使用者可以使用軟體。其中包含翻譯輸出的文字、貨幣、日期、時間、單位等格式
- 對使用者來說,用來控制讓哪種語言或文化環境生效的功能,叫做 locale
- 除了
C
與POSTFIX
以外,locale 名稱並未標準化 - BSD 與 Mac OS X 完全不支援 locale
- locale 的支援仍未成熟:Shell 腳本常受到 locale 影響;在大多數 UNIX 系統下,很難從 locale 文件與工具來判定字元集 (character class)、等價字元集 (equivalence class) 實際上包含了哪些字元,以及有哪些排序符號 (collating symbol) 可用。
- Shell 腳本開發者應了解 locale 對他們代碼所造成的影響
列出所有 locales
1 2 3 4 5 6 7 |
$ locale -a C C.UTF-8 POSIX en_US.utf8 zh_TW.utf8 |
取得特定 locale 的資訊
定義 LC_ALL 來覆寫預設的 locale,可查詢的變量有日期時間格式 LC_TIME
、貨幣格式LC_MONETARY
等
1 2 |
$ LC_ALL=en_US.utf8 locale -ck LC_TIME |
Matching text
- 1992 POSFIX 整合了
grep
、egrep
、fgrep
,透過不同選項控制- 最早的
grep
: 使用 BRE (Basic Regular Expression),只能匹配單一正則 egrep
: 使用 ERE (Extended Regular Expression),只能匹配單一正則fgrep
: 不使用正則,使用特定演算法可以同時匹配多個固定字串
- 最早的
Reqular expression
一些重要的 POSIX BRE/ERE 的 meta 字元
Unix 程序使用何種類型
Bracket expression
UNIX 的範圍表達式在 POSIX 的標準下,現在叫做方括號表達式。其中包含三種字元集構造:
- 字元集 (character class):
[:關鍵字:]
,關鍵字描述不同的字元集,如alpha
、digit
,例如[[:alpha:]!]
匹配任一英文字母或驚嘆號 - 排序符號 (collating symbol):
[.排序元素.]
,受特定 locale 影響,在非英語系的語言,某些成對的字元必須視為單個字元,例如在捷克或西班牙語系,ch 兩個字元會保持連續狀態,在 locale 支持下,可用於如[ab[.ch.]de]
的匹配 - 等價字元集 (equivalence class):
[=字元=]
,列出應視為等值的一組字元,如在法文的 locale 裡,[[=e=]]
可能匹配 e、é、ë
BRE
- 在前面放置
\
用來轉義 meta 符號,常用的有\\
及\[
- 在方括號表達式中,
^
放在字首是取反 (complement) 的意思 - 方括號表達式可以表達字元的範圍,如
[0-9a-fA-F]
,但範圍表示法是根據機器中字元集的數值(ASCII vs EBCDIC),有可移植性的問題。建議用 POSIX 字元集表示法取代。 - 在方括號表達式中,meta 符號不再具備特殊含意
- 後向引用 (backreference) 的結構
\( \)
以\digit
於後方調用,例如匹配所有引號括起來的字\(["']\).\1
- 錨點 (anchor) meta 字元
^
、$
,分別用來針對字串的開始/結尾處進行匹配
範例:預先消除空行
1 2 |
grep -v '^$' < file.txt > file-without-blank-lines.txt |
BRE 運算元優先級 (precedence)
由高至低排列
[..]
、[::]
、[==]
\metacharacter
[]
\(\)
、\digit
*
、\{\}
- no symbol,表示連續
^
、$
ERE
- 以匹配單個字串來說,與 BRE 一致,不同之處在於匹配多個字串方面
- 不存在後向引用
?
及+
可以更細膩處理匹配控制- 方括號表達式用來「匹配此字串,或其他字串」;交替運算符
|
用來「匹配這個序列,或其他序列」 ()
用來分組^
與$
永遠是 meta 符號,與 BRE 不同,若放在字串中間會匹配不到任何東西
範例:匹配多個連續出現的 read 或是 write,且中間可以是空白
1 2 |
((read|write)[[:space:]]*)+ |
ERE 運算元優先級 (precedence)
[..]
、[::]
、[==]
\metacharacter
[]
()
*
、+
、?
、{}
- no symbol
^
、$
|
文本替換(substitution) sed
/
扮演定界符號(delimiter),分隔正則與替代文本- 除了
/
,任何可顯示的符號都可以當定界符號,在處理文件名稱時,通常用標點符號;
、:
、,
取代/
,因為轉義完路徑看起來會很醜 - 在 s 命令最後,若以 g 結尾,代表全域(global)取代,若以數字結尾,則表示「第 n 個匹配才取代」
- sed 會記住 -f 腳本裡遇到的最後一個正則,透過使用空的正則,可重複利用同一個正則
- 命令裡的每個文件會依序打開與讀取,如果沒有文件,則用標準輸入,或用單個破折號
-
表示標準輸入 - 讀取文件時,將讀取的行放到記憶體某個位置,稱為模式空間(pattern space),將一次次修改的結果覆蓋在相同位置,最後輸出模式空間裡的最後內容
範例:擷取第一個字段(將第一個冒號之後的文本取代為空字串)
1 2 |
sed 's/:.*//' /etc/passwd | sort -u |
範例:將 /home/tolstoy 的目錄結構建立一份副本在 /home/lt 底下,並追蹤執行
1 2 3 4 5 |
find /home/tolstoy -type d -print | sed 's;/home/tolstoy/;/home/lt/;' | sed 's/^/mkdir /' | sh -x |
- 使用產生命令(generating commands)的手法,使命令內容成為 shell 的輸入
範例:轉換 HTML 為 XHTML
1 2 3 4 5 6 |
s/<H1>/<h1>/g s:</H1>:</h1>:g s:<[Hh][Tt][Mm][Ll]>:<html>:g s:</[Hh][Tt][Mm][Ll]>:</html>:g s:<[Bb][Rr]>:<br/>:g |
範例:模擬 grep 的功能,只打印含 的行
1 2 3 |
#n /<HTML>/p |
addressing
默認情況下,每一個編輯命令(editing command)會套用到每個輸入行;要限制應用到哪些行,須在命令前加個地址(address),以下為不同種類的地址;注意 address 限用單引號
- 正則表達式
範例:為部份代碼增加註釋
12/oldfunc/ s/$/# XXX: migrate to newfunc/ - 最終行(符號
$
在 sed 裡表示「最終行」)範例:打印文件最終行
12sed -n '$p' file.txt - 絕對的行號
範例:模擬 head 功能,q 命令要求 sed 馬上離開,不再讀取其他輸入
12sed 10q file.txt - 範圍(以逗號隔開兩個絕對行號或正則)
範例:只打印 10~42 行
12sed -n '10,42p' foo.xml範例:只替代範圍內的行
12sed -n '/foo/,/bar/' s/baz/quux/g - 否定正則
注意
!
後面最好不要加上空格,因為會導致某些老舊的 sed 版本無法識別範例: 將沒有 used 的每個行裡的 new 取代為 used
12/used/!s/new/used/g
改變定界符
範例:以 :
隔開正則,以 ;
扮演 s 命令的定界符
1 2 |
sed -n '\:tolstoy: s;;Tolstoy;p' /etc/passwd |
Longest leftmost 從最長最左邊匹配原則
POSIX 標準指出:「完全一致的匹配,指的是自最左邊開始、針對每個子模式、由左至右,必須匹配到最長的可能的字串」
1 2 3 4 5 6 |
$ echo Tolstoy writes well | sed 's/Tolstoy/Camus/' # 使用固定字串 Camus writes well $ echo Tolstoy is happy | sed 's/T.*y/Camus/' # 使用正則 Camus |
留意 null 字串的匹配
1 2 3 |
$echo abc | sed 's/b*/1/g' 1a1b1c |
選定字段 cut
因行內的每個字段長度不一,使用字元 -c
剪下資料的風險較大,一般偏好使用字段 -f
配合定界符 -d
為基礎來提取資料
連接字段 join
範例:刪除註解、取代分隔符為 whitespace 並排序後合併文件
1 2 3 4 5 6 7 8 9 10 11 12 |
# sales # name age joe 24 jane 20 chris 33 # quotas # volume:name 2000:joe 3000:jane 5000:chris |
1 2 3 4 5 6 7 |
sed '/^#/d ; s/[[:blank:]]/ /g' quotas | sort -k 2 > quotas.sorted sed '/^#/d ; s/:/ /g' sales | sort > sales.sorted join -1 2 -2 1 quotas.sorted sales.sorted rm quotas.sorted sales.sorted |
重新編排字段 awk
- 程序架構為
pattern {action}
,pattern 通常是以/
定界的 ERE,action 為 awk 語句,意思是「如果 pattern 為真,則執行 action」 -
記得在參數間用逗號分隔,否則字段會連在一起
-
BEGIN
與END
模式12345BEGIN { startup code }pattern1 { action1 }pattern2 { action2 }END { cleanup code }
範例:以自訂分隔符輸出特定欄位
1 2 3 |
$awk -F: -v 'OFS=**' '/^tolstoy/ { print $1, $6 }' /etc/passwd tolstoy**/home/tolstoy |
範例:自訂變數、使用 printf 混和文本
1 2 3 |
$awk -F: -v fmt="User %s's home directory is %s\n" '/^tolstoy/ { printf fmt, $1, $6 }' /etc/passwd User tolstoy's home directory is /home/tolstoy |