回憶起一件事情:之前用linux尋找中文IME的時候,在百度輸入了fcitx,然後結果上邊有個,您要找的是不是: 諷刺騰訊 。本來一直記不住這個IME名字,不過以後哥就記住這個IME的名字是怎麼拼了,感謝百度。
第九章awk的驚人表現
awk的調用可以定義變數、提供者並且指定輸入檔案,文法:
複製代碼 代碼如下:
awk [ -F fs ] [ -v var=value ... ] 'program' [ -- ] [ var=value ... ] [file(s) ]
awk [ -F fs ] [ -v var=value ... ] -f programfile [ -- ] [ var=value ... ] [ file(s) ]
短程式通常直接在命令列上提供,而比較長的程式則委託-f選項指定,可以重複使用此選項。如果命令列未指定檔案名稱,則awk會從標準輸入讀取。 -- 是特殊選項,指出awk本身已經沒有更進一步的命令列選項。任何接下來的選項都可被你的程式使用。
-F選項是用來重新定義預設欄位分隔字元,且一般慣例將它作為第一個命令選項。緊接-F選項後的fs參數是一個Regex或是被提供作為下一個參數。欄位分隔字元也可以設定使用內建變數FS所指定。如:
awk -F '\t' '{ ... }' files FS="[\f\v]" files
上邊例子-F選項設定的值,應用到第一個檔案組,而由FS指定的值,則應用到第二個組。初始化的-v選項必須放在命令列上直接給定的任何程式之前,他們會在程式啟動前生效。在一命令列程式之後-v選項會被解釋為一個檔案名稱。在命令列上其他地方的初始化會在處理參數時完成,並且會帶上檔案名稱,如:
awk '{...}' Pass=1 *.tex Pass=2 *.tex
處理檔案的列表兩次,第一次Pass設為1,第二次為2。使用字串值進行初始化無須用引號框起來,除非shell要求這樣的引用以保護特殊字元或空白。
特殊檔案名稱-(連字號)表示標準輸入。大部分現代的awk實現(不包括POSIX)都認定特殊名稱/dev/stdin為標準輸入,即使主機作業系統不支援該檔案名稱。同樣:/dev/stderr與/dev/stdout可用於awk程式內,分別表示標準錯誤輸出與標準輸出。
一般awk命令模式或操作可省略一個,如果模式省略,則每條輸入都被操作;如果操作省略,則預設操作為輸出匹配模式的記錄。雖然模式多半是數字或字串運算式,不過awk以保留自BEGIN與END提供兩種特殊模式。
與BEGIN關聯的操作只會執行一次,在任何命令列檔案或一般命令列賦值被處理之前,但是在任何開頭的-v選項指定已完成之後。它大部分是用來處理常式所需要的任何特殊初始化工作。END操作也是只執行一次。用於所有輸出資料已被處理完之後。BEGIN和END模式可以是任意順序,可以存在awk程式內任何位置。當指定多個BEGIN或END模式,則他們將按照在awk程式裡的順序執行。
awk提供了標量與數組兩種變數以儲存資料、數字與字串運算式,還提供了一些語句類型以處理資料:賦值、注釋、條件、函數、輸入、迴圈及輸出。awk運算式許多功能與c語言相似。awk裡注釋是從#開始到行尾。跨行語句需要在結尾處加上反斜線。
awk裡的字串常數是以引號定界,字串可包含任何8bit的字元除了控制字元NUL以外。因為NUL在底層實現語言(C)裡,扮演的是一個字串中斷字元的角色。awk字串長度視記憶體而定。反斜線逸出序列允許非列印字元的表示。
awk提供了許多內建函數,可以在字串上執行,之後再詳細說,這會說兩個length(string)返回string內的字元數。字串的比較用的是傳統的關係運算子:==、!=、<、<=、>、>=。比較不同長度的字串,且其中一個字串為另一個的初始子字串時,較短的定義為小於較長的那個。在shell裡字串串連可以直接進行,不需要串連符號。
awk功能強大的地方大多來自於它對Regex的支援。有兩個運算子:~(匹配)與!~(不匹配)讓awk更容易使用Regex:"ABC" ~ "^[A-Z]+$"結果為真,Regex常量可以用引號或斜杠加以定界:/^[A-Z]+$/。注意如果有字面意義的符號,需要反斜線來轉義。
awk裡的數字,都以雙精確度浮點值表示,如1/32 寫成0.03125、3.125e-2等,awk裡沒有提供字串轉數位函數,不過想做到也很簡單,只要加個零到字串裡,如:s = "123" , n = 0 + s 。這樣123便賦值給n了。一般"+123ABC"轉化為123,而"ABC123"與""都轉化為0。即使awk裡所有的數值運算都是在浮點算術內完成,整數值還是可以表示的,只要值不太大,這個值限定在53位,即2^53即9千萬億的樣子。awk的數值運算子沒有位元運算符,多一個指數運算子(^ 或 ** 或 **=,但是避免使用**和*=,它不是POSIX awk的一部分)它是右結合性的,且與賦值運算子是僅有的右結合性運算子。比如a^b^c^d運算順序是a^(b^(c^d))。awk裡的取餘運算測試了 5 % 3 是2 ; 5 % -3 是2; -5 % 3 是-2; -5 % -3是-2;發現取餘的結果取決於被取餘的數的正負。還有一個內建函數:
int(x) 對x取整
rand 取 0到1之間的隨機數
srand(x) 設定x為rand的新輸入值
cos(x) 給出x的餘弦值
sin(x) 給出x的正弦值
atan2(x,y) 給出y/x的正切值
exp(x) 給出e的x次冪
log(x) 給出x的常用對數值(基為e)
sqrt(x) 給出x的正平方根值
exit(x) 結束awk程式,若有x值,則返回x,否則返回0.
index(s,t) 返回t在s中的第一個開始位置,如t不是s的子串,則返回0]
length(x) 求x的長度(字元個數)
substr(s,x,y) 在字串s中取得從x個字元開始的長度為y的子字串.
awk內建字串函數
gsub(r,s) 在整個$0中用s替代r
gsub(r,s,t) 在整個t中用s替代r
index(s,t) 返回s中字串t的第一位置
length(s) 返回s長度
match(s,r) 測試s是否包含匹配r的字串
split(s,a,fs) 在fs上將s分成序列a
sprint(fmt,exp) 返回經fmt格式化後的exp
sub(r,s) 用$0中最左邊最長的子串代替s
substr(s,p) 返回字串s中從p開始的尾碼部分
substr(s,p,n) 返回字串s中從p開始長度為n的尾碼部分
awk提供許多內建變數,都是大寫名稱,時常用到的幾個有:
FILENAME 當前輸入檔案的名稱
FNR 當前輸入檔案的記錄數
FS 欄位分隔字元(Regex)(預設為:" ")
NF 目前記錄的欄位數
NR 在工作中的記錄數
OFS 輸出欄位分隔字元(預設為:" ")
ORS 輸出記錄分隔字元(預設為:"\n")
RS 輸入記錄分隔字元(僅用於gawk與mawk裡的Regex)(預設為:"\n")
awk允許的測試:
x==y x等於y?
x!=y x不等於y?
x>y x大於y?
x>=y x大於或等於y?
x x<=y x小於或等於y?
x~re x匹配Regexre?
x!~re x不匹配Regexre?
awk的操作符
= 、+=、 -=、 *= 、/= 、 %=
|| && > >= < <= == != ~ !~
xy (字串連結,'x''y'變成"xy")
+ - * / % ++ --
awk沒有提供位操作符,但是提供了相關的函數:
and(v1, v2) Return the bitwise AND of the values provided by v1 and v2.
compl(val) Return the bitwise complement of val.
lshift(val, count) Return the value of val, shifted left by count bits.
or(v1, v2) Return the bitwise OR of the values provided by v1 and v2.
rshift(val, count) Return the value of val, shifted right by count bits.
xor(v1, v2) Return the bitwise XOR of the values provided by v1 and v2.
awk的陣列變數允許數組名稱之後,以方括弧將任一數字或字串運算式括起來作為索引。以任意值為索引的數組稱之為關聯陣列。awk將應用於數組中,允許尋找插入和刪除等操作,在一定時間內完成,與儲存多少項目無關。(說了這麼多其實就是hash數組)。delete array[index]會從數組中刪除元素。delete array刪除整個數組。awk數組還可以這麼用:
print maildrop[53, "Oak Lane", "T4Q 7XV"]
print maildrop["53" SUBSEP "Oak Lane" SUBSEP "T4Q 7XV"]
print maildrop["53\034Oak Lane", "T4Q 7XV"]
print maildrop["53\034Oak Lane\034T4Q 7XV"]
以上輸出結果都是一樣的。內建變數SUBSEP預設值是\034,可以更改它。如果稍後更改了SUBSEP的值,將會使已經儲存資料的索引失效,所以SUBSEP其實應該在每個程式只設定一次,在BEGIN操作裡。
awk對於命令列的自動化處理,意味著awk程式幾乎不需要關心他們自己。awk通過內建變數ARGC(參數計數)與ARGV(參數向量,或參數值),讓命令列參數可用。給出例子說明其用法:
複製代碼 代碼如下:
$ cat >showargs.awk
BEGIN{
print "ARGC = ",ARGC
for ( k = 0 ; k < ARGC ; k++)
print "ARGV[" k "] = [" ARGV[k] "]"
}
$ awk -v One=1 -v Two=2 -f showargs.awk Three=3 file1 Four=4 file2 file3
ARGC = 6
ARGV[0] = [awk]
ARGV[1] = [Three=3]
ARGV[2] = [file1]
ARGV[3] = [Four=4]
ARGV[4] = [file2]
ARGV[5] = [file3]
正如C/C++中,參數儲存在數組項目0、1....、ARGC-1中,第0個項目是awk程式本身的名稱。不過與-f 和 -v選項結合性的參數是不可使用的。同樣的,任何命令列程式也不可使用:
複製代碼 代碼如下:
$ awk 'BEGIN{for(k=0;k<ARGC;k++)
print "ARGV["k"] = ["ARGV[k]"]"}' a b c
ARGV[0] = [awk]
ARGV[1] = [a]
ARGV[2] = [b]
ARGV[3] = [c][/c][/c]
是否需要顯示在程式名稱裡的目錄路徑,則看實際情況而定。awk程式可修改ARGC和ARGV,注意保持倆個的一致性。
awk一見到參數含有程式內容或是特殊--選項時,它會立即停止將參數解釋為選項。任何接下來的看起來像是選項的參數,都必須由你的程式處理,並接著從ARGV中被刪除或設定為空白字串。
awk提供訪問內建數組ENVIRON中所有的環境變數:
複製代碼 代碼如下:
$ awk 'BEGIN{ print ENVIRON["HOME"]; print ENVIRON["USER"]}'
/home/administrator
administrator
ENVIRON數組並無特別之處,可以隨意修改刪除。然而,POSIX要求子進程繼承awk啟動時生效的環境,而我們也發現,在現行實現下,並無法將對於ENVIRON數組的變更傳遞給子進程或者內建函數。特別地,這是指你無法通過對EVNIRON["LC_ALL"]的更改控制字元串函數,例如tolower(),在特定locale下的行為模式。因此你應將ENVIRON看成一個唯讀數組。如果要控制子進程的locale,則可通過在命令列字串裡設定適合的環境變數達成。如:
system("env LC_ALL=es_Es sort infile > outfile")#以Spanish的locale排序檔案。
system()函數稍後說明。
模式與操作構成awk程式的核心。模式為真則進行操作。一般模式是Regex,就會被拿來與整個輸入記錄進行匹配,比如:
NF == 0 #選定空記錄
NF > 3 #選定擁有三個欄位以上的記錄
NR < 5 #選定第一到第四條記錄
$1 ~ /jones/ #選定欄位1中有jones的記錄
/[xX][mM][lL]/ #忽略大小寫選定含xml的記錄
awk在匹配功能上,還可以使用範圍運算式,以逗點隔開的兩個運算式。比如:
(FNR == 3) , (FNR == 10) #選定每個輸入檔案按裡記錄3到10
/<[Hh][Tt][Mm][Ll]>/ , /<\/[Hh][Tt][Mm][Ll]>/ #選定html檔案裡的主體
在BEGIN操作裡,FILENAME、FNR、NF與NR初始都未定義;引用到他們時,會返回null。
通過模式的匹配,就要把為真記錄的傳給操作。給出一些執行個體:
#unix單詞計數程式wc:
awk '{ C += length($0) + 1 ; W += NF } END { print NR, W, C}'
注意:模式/操作組並不需要以換行字元分隔,一般換行是為了閱讀方便。我們也可以使用BEGIN{ C = W =0} 來初始化,但是awk具有預設的初始化保證。
#將未經處理資料值及他們的對數列印為單欄資料檔案:
awk ' { print $1 , log($1) }' file(s)
#要從文字檔裡隨機列印5%行左右的樣本:
awk 'rand() < 0.05 ' file(s)
#以空白分隔欄位的表格中,報告第n欄的和:
awk -v COLUMN=n '{ sum += $COLUMN } END { print sum } ' file(s)
#產生欄位n欄的平均值
awk -v COLUMN=n '{ sum += $COLUMN } END { print sum / NR } ' file(s)
#統計檔案最後一個欄位的總數
awk '{ sum += $NF; print $0 , sum }' file(s)
#三種尋找檔案內文本的方式:
egrep 'pattern|pattern' file(s)
awk '/pattern|pattern/' file(s)
awk '/pattern|pattern/ { print FILENAME ":" FNR ":" $0 }' file(s)
#僅尋找100-150行 的匹配資訊
sed -n -e 100,150p -s file(s) | egrep 'pattern'
awk '(100<=FNR)&&(FNR<=150)&& /pattern/ { print FILENAME":"FNR":"$0}' file(s)
#要在四欄表格裡調換二三欄,假設定位字元分隔:
awk -F'\t' -v OFS='\t' '{ print $1,$3,$2,$4}' old > new
awk 'BEGIN {FS=OFS='\t' } {print $1,$3,$2,$4 }' old > new
awk -F'\t' '{ print $1 "\t"$3"\t"$2"\t"$4} ' old > new
#將格欄分隔字元由定位字元替換成&:
sed -e 's/\t/\&/g' file(s)
awk 'BEGIN { FS="\t"; OFS="&" } {$1 = $1; print }' file(s)
#刪除排序後的重複行:
sort file(s) | uniq
sort file(s) | awk 'Last != $0 { print } {Last = $0} '
#將斷行符號字元/分行符號的行終結,一致轉換為以換行字元為行終結:
sed -e 's/\r$//' file(s)
sed -e 's/^M$//' file(s)
mawk 'BEGIN { RS="\r\n" } { print } ' file(s)
#找出長度超過72個字元的行:
egrep -n '^.{73,}' file(s)
awk 'length($0) > 72 { print FILENAME":"FNR":"$0}' file(s)
awk支援語句的連續執行。支援條件陳述式,if else 類似C語言,支援迴圈 while(){} 或do{} while()或for( ; ; ){] 類似c語言。還有一個for(key in array) { } 。
如 awk 'BEGIN { for( x=0; x<=1;x+=0.05) print x}' 。雖然很多類似C,但是注意awk中是缺乏逗點運算子的。迴圈同樣可以使用break和continue 。
awk直接處理命令列上標明的輸入檔案,一般不用使用者自己開啟與處理檔案,但是也可以通過awk的getline語句做這些事情。用法:
getline 從當前輸入檔案讀取下一條記錄存入$0,並更新NF、NR、FNR
getline var 從當前輸入檔案中,讀取下一條記錄存入var並更新NR、FNR
getline < file 從fle中讀取下一條記錄,存入$0,並更新NF
getline var < file 從file讀取下條記錄存入var
cmd | getline 從外部命令cmd讀取下條記錄存入$0,並更新NF
cmd | getline var 從外部命令讀取下條記錄,存入var
如果像確保來自控制終端的輸入則:getline var < "/dev/tty"
在awk裡可以通過管道與外部的shell命令混寫:
複製代碼 代碼如下:
tmpfile = "/tmp/telephone.tmp"
comman = "sort > " tmpfile
for ( name in telephone)
print name "\t" telephone[name] | command
close (command)
while((getline < tmpfile) > 0)
print
close(tmpfile)
close可以關閉開啟的檔案以解約可用資源。awk裡也沒有排序函數,以為它只需要複製功能強大的sort命令即可。
getline語句以及awk管道裡的輸出重新導向都可與外部程式通訊,system(command)函數提供的是第三種方式:其傳回值是命令的退出碼。所以上邊的例子可以寫成:
複製代碼 代碼如下:
tmpfile = "/tmp/telephone.tmp"
for ( name in telephone)
print name "\t" telephone[name] | > tmpfile
close (tmpfile)
system("sort < " tmpfile)
while((getline < tmpfile) > 0)
print
close(tmpfile)
對於被system()執行的命令並不需要調用close(),因為close()僅針對以I/O重新導向運算子所開啟的檔案或管道,還有getline、print、printf。其他幾個例子:
system("rm -f " tmpfile)
system("cat < 由於每次調用system()都會起始一個全新的shell,因此沒有簡單方式可以在分開的system()調用內的命令之間傳遞資料,除非通過中間檔案。
就到目前這裡,awk足夠編寫任何資料處理程式了。對於大型程式,不利於維護和查看,所以awk提供函數,就像c一樣,awk也可選擇性的返回標量值。函數可以定義在程式頂層的任何位置:成對的模式/操作組之前、之間、之後。在單一檔案的程式裡,慣例是將所有函數放在成對的模式/作業碼之後,且讓他們依字母順序排列,這樣會讀起來方便。定義如下:
function name(arg1,arg2....){ statement(s) ; return expression ;}
局部的變數會覆蓋全域的同名變數。
awk裡其他的內建函數:
子字串提取substr(string,start,len),下標從1開始。
字母大小寫轉換tolower(string),toupper(string)。無法處理罕見字母和重音字母。
字元尋找index(string,find),返回起始位置,找不到給0.
字串匹配match(string,regexp),匹配則返回string的索引,並且會更新全域變數RSTART和RLENGTH,擷取匹配方法:substr(string, RSTART,RLENGTH)。
字串替換sub(regexp,replacement,target)和gsub(regexp,replacement,target)。前者將target與Regex進行匹配,將最左邊最長的匹配部分替換為字串。
gsub()的運行類似,不過它會替換所有匹配的字串。兩種函數都返回替換的數目。如果省略第三個參數,則預設值為當前的記錄$0。兩個函數裡replacement裡的字元&都會被替換為target中與regexp匹配的文本。使用\&可關閉這一功能,而且請記得如果你要在引號字串裡使用它時,以雙斜杠轉義它。如gsub(/[aeiouyAEIOUY]/,"&&")令所有當前$0裡的母音字母乘以兩倍,而gsub(/[aeiouyAEIOUY]/,"\\&\\&")則是將所有母音字母替換為一對&符號。
字串分割:awk針對$0自動提供了方便的分割為$1 $2 .... $NF,也可以函數來做:split(string,array,regexp)將string切割為片段,並儲存到array裡。如果regexp省略,則預設內建欄位分隔符號為FS。函數返回array裡的元素數量。填寫分割符的時候留意預設欄位分隔符號" "與"[ ]"的差異:前者會忽略前置與結尾的空白,並於運行時將空白視為一個單獨空格,後者則正好匹配一個空格,對絕大多數文本處理而言,第一種模式已經滿足功能上的需求了。
字串格式化sprintf(format,expression1,expression2,...) ,它會返回已格式化的字串作為其函數值。printf()的運行方式也是這樣,只不過它會在標準輸出或重新導向的檔案上顯示格式化後的字串,而不是返回其函數值。這倆函數類似shell裡的printf,但是還有些許差異,使用的時候注意一下。
數值函數:
atan2(y,x) 返回y/x的反正切
exp(x) 返回x的指數,ex
int(x),log(x),cos(x),sin(x),sqrt(x),
rand() 返回0<=r<1
srand(x) 設定虛擬隨機產生器的種子為x,並返回正確的種子。如果省略x,則使用目前時間(以秒計)。如果srand()未被調用,則awk每次執行都會從預設種子開始。
awk內建變數(預定義變數)
說明:表中v項表示第一個支援變數的工具(下同):A=awk,N=nawk,P=POSIX awk,G=gawk
V 變數 含義 預設值
--------------------------------------------------------
N ARGC 命令列參數個數
G ARGIND 當前被處理檔案的ARGV標誌符
N ARGV 命令列參數數組
G CONVFMT 數字轉換格式 %.6g
P ENVIRON UNIX環境變數
N ERRNO UNIX系統錯誤訊息
G FIELDWIDTHS 輸入欄位寬度的空白分隔字串
A FILENAME 當前輸入檔案的名字
P FNR 目前記錄數
A FS 輸入欄位分隔符號 空格
G IGNORECASE 控制大小寫敏感0(大小寫敏感)
A NF 目前記錄中的欄位個數
A NR 已經讀出的記錄數
A OFMT 數位輸出格式 %.6g
A OFS 輸出欄位分隔符號 空格
A ORS 輸出的記錄分隔字元 新行
A RS 輸入的記錄他隔符 新行
N RSTART 被匹配函數匹配的字串首
N RLENGTH 被匹配函數匹配的字串長度
N SUBSEP 下標分隔字元 "34"
以上基本上把所有awk的內容詳細講完了,十分的強大,網上搜了些別的關於awk的講解,沒發現有哪篇講解像這本書裡這麼全的。
上邊例子給出的比較少,這裡有很多例子可供參考。
第十章檔案處理
先講了ls命令,應該很熟了,再羅列一下主要選項吧:
-1 數字1,強制單欄輸出,預設的以適合視窗寬度輸出
-a 顯示所有檔案
-d 顯示與目錄相關資訊,而非他們包含的檔案的資訊
-F 使用特殊結尾字元,標記特定的檔案類型。試了一下路徑加了斜杠,可執行檔加了*號。別的沒怎麼試。
-g 僅適用於組:省略所有者名稱
-i 列出inode編號
-L 緊連著符號性串連,列出他們指向的檔案。
-l 小寫L,顯示詳細資料。
-r 倒置預設排序
-R 遞迴列出下沿進入每個目錄
-S 按照由大到小的檔案大小計數排序,僅GNU版本支援。
-s 以塊(與系統有關)為單位,列出檔案的大小。
-t 按照最後修改時間排序
--full-time 顯示完整的時間戳記
說明一下長資訊顯示的時候的內容:
drwxrwxr-x 2 administrator administrator 1024 1月 5 10:43 bin
第一個字母 - 表示一般檔案 d表示目錄 l表示符號串連
接下來的9個字元,每三個是一組,報告所有組的許可權,r表示可讀,w表示可寫,x表示可執行。前三個是擁有者選前,中間三個是使用者所在組的許可權,最後三個是其他人的許可權。
第二欄包含串連計數。第三四欄表示所有者和所屬組。第五欄是位元組單位大小。最後是時間和檔案(夾)名。
書中給了一個命令od 說顯示真是的檔案名稱,ls | od -a -b ,嘗試了一下,完全看不懂輸出內容。貌似是以nl(八進位012)做分隔字元,然後羅列處來檔案名稱的樣子。如果檔案名稱有漢字,顯示會是一些符號。各種不懂。
書中用一節說使用touch更新修改時間,並說有時時間戳記是有意義的,但內容則否。常見例子是用於鎖定檔案,以指出程式已在執行中,不應該啟動第二個執行個體。另一用途則為記錄檔案的時間戳記,供日後與其他檔案對照用。touch預設(-m) 操作會改變檔案的最後修改時間,也可以使用-a選項改變檔案的最後訪問時間。也可以搭配-t選項修改時間,方式是加上[[CC]YY]MMDDHHMM[.SS]形式的參數,世紀、公元年和秒數是可選項,例如:
$ touch -t 201201010000.00 date #建立一個檔案設定時間戳記
touch還提供-r選項,複製參照檔案的時間戳記。
以日期來看,unix時間戳記是從零開始,由1970/1/1/ 00:00:00 UTC算起。
然後又用一節介紹了一下臨時檔案/tmp 。一般要解決自己程式產生的臨時檔案,共用的目錄或同一程式的多個執行執行個體可能造成臨時檔案命名衝突,一般使用的都是進程ID,可以在shell變數
複製代碼 代碼如下:
umask 077 #???????????????
TMPFILE=${TMPDIR-/tmp}/myprog." />You can't use 'macro parameter character #' in math mode #產生臨時性檔案名稱
trap 'rm -f $TMPFILE' EXIT #完成時刪除臨時檔案
但是像/tmp/myprog.
複製代碼 代碼如下:
$ cat $HOME/html2xhtml.sed
s/<H1>/<h1>/g
...
s:H2>:h2>:g
...
cd top level web site directory
find . -name '*.html' -o -name '*.htm' -type f |
while read file
do
echo $file
mv $file $file.save
sed -f $HOME/html2xhtml.sed < $file.save > $file
done
書中說了一小節尋找問題檔案,意思是檔案名稱裡有特殊字元,可以實用find -print0 來解析,但是沒搞明白說這些是幹嘛用的。
然後介紹了一個執行命令xargs,是為了處理給指令碼傳參過長的問題,不如有時候我們會寫尋找字串的命令如下:
$ grep POSIX_OPEN_MAX /dev/null $(find /usr/include -type f | sort )
我們在後邊一堆檔案中尋找 POSIX_OPEN_MAX這樣的一個字串。如果後邊find出來的檔案很少,那很好,這條命令就會順利執行,但是如果過長會給出提示:****:Argument list too long. 這樣子。我們可以通過getconf ARG_MAX來查看你的系統允許的最大值是多少。上邊這條命令有一個檔案是空檔案/dev/null,這是為了防止find沒找到任何檔案使grep進入從標準輸入擷取資訊的空等狀態,也為了使grep命令有多個檔案參數而使結果可以顯示檔案名稱和出現的行數。
我們可以解決這樣的一個參數過長的問題通過開始提到的xargs命令,如:
$ find /usr/include -type f | xargs grep POSIX_OPEN_MAX /dev/null
這裡xargs如果未取得輸入檔案名稱,則會預設終止。GNU的xargs支援--null選項:可處理GNU find的-print0選項所產生的NUL結尾的檔案名稱列表。xargs將每個這樣的檔案名稱作為一個完整參數,傳遞給它執行的命令,而沒有shell(錯誤)解釋問題或分行符號號混淆的危險,然後是交給其後的命令處理它的參數。另外xargs的選項可以控制哪些參數需要被替換,還可以限制傳遞的參數個數等。
如果瞭解檔案系統的空間資訊,我們可以通過find和ls命令配合awk程式協助就可辦到,比如:
$ find -ls | awk '{sum +=$7} END {printf("Total: %.0f bytes\n",sum)}'
但並不好用,編碼長不說還不知道可用空間。有兩個好用的命令來解決這一需求:df和du。
df(disk free)提供單行摘要,一行顯示一個載入的檔案系統的已使用和可實用空間。顯示單位具體看相應版本。可以實用-k強制實用kilobytes單位。還有一個選項-l 僅顯示本地檔案系統,排除網路載入的檔案系統。還有-i選項,提供訪問inode使用量。GNU的df還提供-h(human-readable)選項,方便閱讀。還可以提供一個或多個檔案系統名稱或載入點來限制輸出項目:$ df -lk /dev/sda6 /var 。
du會摘要檔案系統的可用空間,但是不會告訴你某個特定的分類樹需要多少空間,這是du(disk usage)的工作。不同系統可能有所不同,-k控制單位,-s顯示摘要。
GNU版本提供-h,同df。du可以解決的一個常見問題是:找出哪個使用者用掉最多的系統空間:$ du -s -k /home/users/* | sort -k1nr | less
假設使用者目錄全部放在/home/users下。
關於比較檔案好用的兩個命令cmp和diff。cmp直接後邊跟兩個檔案參數即可,如果不同輸出結果會指出第一個不同處的位置,相同沒有任何輸出。-s可以抑制輸出,可以通過$?來查看離開狀態代碼,非零表示不同。diff慣例是將舊檔案作為第一個參數,不同的行會以前置左角括弧的方式,對應到左邊檔案,而前置右角括弧則指的是右邊的檔案。還有一個擴充是diff3,比較3個檔案。
有時候需要修複不同的地方,patch命令提供了十分方便的做法:
複製代碼 代碼如下:
$ echo test 1 > test.1
$ echo test 2 > test.2
$ diff -c test.[12] > test.dif
$ patch < test.dif
此時你查看test.1會發現裡邊的內容已經變為test 2了。patch會儘可能套用不同之處,然後報告失敗的部分,由使用者自行處理。雖然patch也可以處理一般的diff輸出,但是常規都是處理diff -c選項的資訊。
如果有你懷疑有很多檔案有相同的內容,實用cmp或diff就十分麻煩。這時可以實用file checksum(檔案校正和),取得近似線性效能完成這一繁瑣的工作。有很多工具可以提供,如:sum、cksum、checksum,訊息摘要工具md5與md5sum,安全性三列(secure-hash)演算法工具sha、sha1sum、sha256以及sha384。可惜的是:sum的執行個體在各個平台都不想同,使得他們的輸出無法跨越不同unix版本進行檔案校正和的比較。一般的會這樣:
$ md5sum /bin/l?
57e35260b8e017f4696b8559ed4f32f2 /bin/ln
0f68a291df1a708e130e0f407a67e9ca /bin/ls
輸出結果有32個十六進位數,等同128位,因此兩個不同檔案最後散列出相同簽名的可能性非常低。瞭解這個後可以寫一個簡單指令碼來實現我們之前的目標了。
複製代碼 代碼如下:
#! /bin/sh -
# 根據他們的MD5校正和,顯示某種程度上內容機會一直的檔案名稱
#
# 文法:
# show-indentical-files files
IFS='
'
PATH=/usr/local/bin:/usr/bin:/bin
export PATH
md5sum "$@" /dev/null 2> /dev/null |
awk '{
count[$1]++
if( count[$1] ==1 ) first[$1]=$0
if( count[$1] ==2 ) print first[$1]
if( count[$1] >1 ) print $0
}' |
sort | awk '{
if ( last != $1 ) print ""
last = $1
print
}'
程式很簡單,就不弄注釋了吧。可以測試一下:
$ show-indentical-files /bin/*
發現好多命令都很能裝啊,其實內容都一樣的 - -!。
這裡說一下數位簽章驗證,很有用。
軟體發布的時候,一般會包含分發檔案的校正和,這可以讓你方便得知所下載的檔案是否與原始檔案匹配。不過單獨的校正和不能提供驗證(verification)工作:如果校正和被記錄在你下載軟體裡的另一個檔案中,則攻擊者可以惡意的修改軟體,然後只需要相應的修改校正和即可。
這個問題的解決方案是公開金鑰加密(public-key cryptography)。在這種機制下,資料的安全保障來自兩個相關密鑰的存在:一個私密密鑰,只有所有者知悉,以及一個公開密鑰,任何人都可得知。兩個密鑰的其中一個用以加密,另一個則用於解密。公開祕密金鑰加密的安全性,依賴已知的公開密鑰及可被該密鑰解密的文本,以提供一條沒有實際用途的資訊但可被用來回複私密密鑰。這一發明最大的突破是解決了一直以來密碼學上極為嚴重的問題:在需要彼此溝通的對象之間,如何安全的交換加密金鑰。
私密密鑰與公開密鑰是如何使用和運作的呢?假設Alice想對一個公開檔案簽名,她可以使用她的私密密鑰(private key)為檔案加密。之後Bob再使用Alice的公開密鑰(public key)將簽名後的檔案解密,這麼一來即可確信該檔案為Alice所簽名,而Alice無須泄漏其私密密鑰,就能讓檔案得到信任。
如果Alice想傳送一份只有Bob能讀的信給他,她應以Bob的公開密鑰為信件加密,之後Bob再使用它的私密密鑰將信件解密。只要Bob妥善保管其私密密鑰,Alice便可確信只有Bob能讀取她的信件。
對整個資訊加密其實是沒有必要的:相對的,如果只有檔案的校正和加密,它就等於有數位簽章(digital signature)了。如果資訊本身是公開的,這種方法便相當有用,不過還需要有方法驗證它的真實性。要完整說明公開祕密金鑰加密機制,需要整本書才行,可參考《安全性與密碼學》。
電腦越來越容易受到攻擊,下載檔案或軟體要很注意安全。一般軟體封存檔案都併入了檔案校正和資訊的數位簽章,如果不確定下載的東西是否安全,可以驗證它。舉例:
$ ls -l coreutils-5.0.tar*
-rw-rw-r-- 1 jones devel 6020616 Apr 2 2003 coreutils-5.0.tar.gz
-rw-rw-r-- 1 jones devel 65 Apr 2 2003 coreutils-5.0.tar.gz.sig
$gpg coreutils-5.0.tar.gz.sig #嘗實驗證此簽名
gpg: Signature made Wed Apr 2 14:26:58 2003 MST using DSA key ID D333CBA1
gpg: Can't check signature: public key not found
驗證失敗,是因為我們還未將簽名者的公開密鑰加入gpg密鑰環。我們可以在簽名作者的個人網站找到公開密鑰或者通過email詢問。然而幸好使用數位簽章的人多半會將他們的公開密鑰註冊到第三方(thrid-party)的公開密鑰伺服器,且該註冊會自動地提供給其他的密鑰伺服器共用。
將金鑰產製原料儲存到臨時檔案如”temp.key",加到密鑰環中:
$ gpg --import temp.key
然後就可以成功驗證簽名了。