回憶起一件事情:之前用linux尋找中文輸入法的時候,在百度輸入了fcitx,然后結果上邊有個,您要找的是不是: 諷刺騰訊 。本來一直記不住這個輸入法名字,不過以后哥就記住這個輸入法的名字是怎么拼了,感謝百度。
第九章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參數是一個正則表達式或是被提供作為下一個參數。字段分隔字符也可以設置使用內建變量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功能強大的地方大多來自于它對正則表達式的支持。有兩個運算符:~(匹配)與!~(不匹配)讓awk更容易使用正則表達式:"ABC" ~ "^[A-Z]+$"結果為真,正則表達式常量可以用引號或斜杠加以定界:/^[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 字段分隔字符(正則表達式)(默認為:" ")
NF 當前記錄的字段數
NR 在工作中的記錄數
OFS 輸出字段分隔字符(默認為:" ")
ORS 輸出記錄分隔字符(默認為:"\n")
RS 輸入記錄分隔字符(僅用于gawk與mawk里的正則表達式)(默認為:"\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匹配正則表達式re?
x!~re x不匹配正則表達式re?
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;kARGC;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程序的核心。模式為真則進行操作。一般模式是正則表達式,就會被拿來與整個輸入記錄進行匹配,比如:
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與正則表達式進行匹配,將最左邊最長的匹配部分替換為字符串。
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=r1
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
然后就可以成功驗證簽名了。
您可能感興趣的文章:- shell腳本學習指南[二](Arnold Robbins & Nelson H.F. Beebe著)
- shell腳本學習指南[一](Arnold Robbins & Nelson H.F. Beebe著)
- shell腳本學習指南[六](Arnold Robbins & Nelson H.F. Beebe著)
- shell腳本學習指南[五](Arnold Robbins & Nelson H.F. Beebe著)
- shell腳本學習指南[三](Arnold Robbins & Nelson H.F. Beebe著)