作者告訴我們:到目前為止基礎已經搞定,可以將前邊所學結合shell變成進軍中等難度的任務了。激動的要哭了,終于看到本書結束的曙光了 T T 。碼字比碼代碼還辛苦。不過令人興奮的是立刻就學以致用了,花了一天半的時間處理了一個3.8G的服務器日志文件,你妹啊,破電腦內存才2G。不過切割化小然后寫了幾個awk文件和sh文件按規則處理合并,算是搞定了!
第十一章擴展實例:合并用戶數據庫
問題描述就是有兩臺UNIX的計算機系統,這兩個系統現在要合并,用戶群同樣需要合并。有許多用戶兩臺系統上都有帳號?,F在合并需要的功能是:
將兩個系統里的/etc/passwd文件合并,并確保來自這兩臺系統的所有用戶有唯一UID。
針對已存在的UID、但被用在不同用戶身上的情況,則將其所有文件的所有權變更為正確用戶。
解決這個問題,我們程序必須處理的情況可能有這些:
1、用戶在兩個系統都有用戶名和UID。
2、用戶的用戶名和UID只有一臺系統里有,另一臺沒有,這合并時不會有問題。
3、用戶在兩臺系統都有相同的用戶名但UID不同。
4、用戶在兩臺系統擁有相同UID但用戶名不同。
合并密碼文件幾個步驟:
1、直接物理合并文件,重復的username聚在一起,產生結果為下步輸入。
2、將合并文件分三分:具有相同username和UID的用戶放入unique,未重復的用戶username也放入。具有相同username但不同UID的放入dupusers,具有相同UID但不同username的放入dupids。
3、建立已使用中具有唯一性的UID編號列表??捎脕韺ふ倚碌奈词褂肬ID。
4、編寫另一個程序,搭配使用UID編號了解,尋找新的UID編號。
5、建立用以產生最后/etc/passwd記錄的三項組合(username、old UID、new UID)列表。還有最重要的:產生命令,以變更文件系統中文件的所有權。與此同時,針對原來就擁有數個UID的用戶以及同一UID擁有多個用戶,建立最后的密碼文件項目。
6、建立最終密碼文件。
7、建立變更文件所有權的命令列表,并執行,這部分要謹慎處理,小心規劃。
這里書中針對上述步驟書寫了程序,很大一部分代碼是處理UID的,個人感覺全部使用新的UID來重新映射username,不是很簡單就搞定一切了。只用把所有出現的username記錄出來,重復的干掉,再順序給出對應UID,很簡單幾步搞定了。至于之后根據old UID更改文件權限,完全可以做新舊UID的映射,直接改到新的里邊就OK了。這樣想來如果更改文件權限是程序主要耗時部分的話,書中原方法還是可取的,只是編碼復雜度較高。如果更改權限耗時能夠承受,還是選擇編碼復雜度低的來搞速度還快點,也方便。
這里更改文件權限使用chown命令,可以更改文件擁有用戶或用戶組。-R選項遞歸處理。但出現的問題是用戶擁有的文件未必只放在用戶根目錄里。所以更改用戶在每一個地方的文件需要使用find命令,從根目錄開始做。類似這樣:
find / -user $user -exec chown $newuid '{}' /;
-exec選項會針對每一個與條件比對相符的文件執行接下來的所有參數,直到分號為止。find命令里的{}意指替換找到的文件名稱至命令。這樣使用find代碼很高,因為它會針對每一個文件或目錄建立一個新的chown進程??梢蕴鎿Q成:
find / -user $user -print | xargs chown $newuid
#有GNU工具集可以:
find / -user $user -print0 | xargs --null chown $newuid
這樣就把所有需要更改的文件傳送至一個新的進程來處理,而不是很多個。
這里有個另外的問題,加入old-new-list里的數據這樣:
juser 25 10
mrwizard 10 30
也就是說如果先變更juser,把juser的文件權限UID25變更為UID10以后,再變更mrwizard的時候問題就來了,程序會把之前所有的juser的文件當成mrwizard的文件。這時就牽扯到處理順序問題,我們必須在25變成10之前,把10變成30。解決方法也簡單,給所有的UID編號是沒有任何地方使用過即可。
這里還剩最后一個小問題,就是find命令尋找用戶的時候,注意我們問題的環境,目前是有兩臺服務器,find尋找用戶的時候是有可能找不到另一臺服務器用戶的。需要作出處理。
再說一下我們解決這個問題時規避的一些真實世界的問題。最明顯的是我們很可能也需要合并/etc/group文件。再者,任何一個大型的系統,都可能會出現文件擁有已不存在于/etc/passwd與/etc/group里的UID或GID值,尋找這里文件可以這樣:
find / '(' -nouser -o -nogroup ')' -ls
這樣做將產生所有這樣的文件輸出??梢允褂霉艿肋M一步處理xargs chown...這樣。
第三點是在改變文件的用戶與組處理期間,文件系統絕對得靜止。處理時不應該有任何其他活動發生,使系統處于單用戶模式下root登錄,且只能在系統物理console設備上完成這個任務。
最后就是效率問題,每個用戶都需要跑一遍find是很不劃算的,我們可以跑一遍來處理所有用戶的文件,類似這樣:
find / -ls | awk -f make-command.awk old-to-new.txt - > /tmp/commands/sh ... 在執行前先檢查 /tmp/commands/sh ... sh / tmp/commands/sh
類似這樣。先讀取old-to-new.txt的舊換新UID變更,然后awk會針對每一個輸出文件尋找是否有必須被更改,如果要更改則使用chown命令。
詳細代碼之類的略過吧,沒特殊算法,都很簡單。
第十二章拼寫檢查
最初的unix拼寫檢查原型為代碼說一下:
后續的有改良的命令ispell和aspell,有一個不錯的功能就是可以提供本地有效的單詞拼寫列表,如:spell +/usr/local/lib/local.words myfile > myfile.errs
針對所寫文檔提供哦功能私有拼寫字典,非常重要,這能使拼寫檢查更高效準確。但是spell還有一些棘手的事情,即locale變動后會使命令達不到預期效果如:
默認的locale在操作系統版本之間可能有所不同。因此最好的方式便是將LC_ALL環境變量設置與私人字典排序一致,再執行spell。env命令的作用是在重建的環境中運行命令。
書中展現了spell的awk版本,也展現awk的強大。為引導程序進行,先列出我們預期的設計目標:
1、程序將會能夠讀取文字數據流、隔離單詞、以及報告不在已知單詞列表的單詞。
2、將會有一個默認的單詞列表,由一個或多個系統字典收集而成。
3、它將可能取代默認的單詞列表。
4、標準單詞列表將有可能由一個或多個用戶所提供的單詞列表而擴增。該列表在技術性文件上特別有用,例如首字母縮寫、術語及專有名詞等。
5、單詞列表將無須排序。
6、雖然默認單詞列表都是英文,但輔以適當的替代性單詞列表,程序將可能處理任何語言的文字,只要它是以基礎為ASCII的字符集呈現,以空白字符分隔單詞。
7、忽略字母大小寫,讓單詞列表維持在易于管理的大小,但異常報告采用原大小寫。
8、忽略標點符號,但頓點符號(縮寫的撇)將視為字母。
9、默認的報告將為排序后具有獨一無二單詞的列表以一行一個單詞的方式呈現。為拼寫異常列表。
10、將可通過選項增加異常列表報告,并有位置信息,如文件名行號等,以利于尋找異常單詞。報告將以位置排序,且當他們在同一位置發現多個異常時,則進一步依異常單詞排序。
11、支持用戶可指定的后綴縮寫,讓單詞列表保持在易于管理的大小。
又是很長的代碼,碼的頭暈。。。不保證全對,注釋也先不寫了。執行命令:
$ awk -f spell.awk testfile
這里針對搞算法競賽的同學說一點,shell腳本里的高效,怎么樣叫高效,我也是搞競賽的,總是追求程序運行時的效率,但是在shell腳本里追求的是總體效率。完成一個任務假如編碼時間用了1個小時,最終完成的代碼運行花30秒鐘,和為了優化程序提高運行效率而編碼時間花了2個小時乃至更多時間,最后運行代碼時間縮減,無論縮減多少,我們都認為這個優化還是不太值得肯定的。這里不是否定代碼運行效率,而是要平衡這個編碼時間。而且shell目前我感覺應該是線下運行的程序多,不是在線運行的程序,所以時間上的要求可以放寬很多。所以我們要做的就是完成一個任務花費更少的時間就好。個人感覺,不對了感謝指正。
第十三章進程
進程是一個執行中程序的一個實例,新進程由fork()與execve()等系統調用所起始執行直到exit()為止。進程會被指定優先權,nice和renice命令用于調整進程的優先權。任何瞬間,等待執行之進程的平均數,被成為平均負載,uptime命令可顯示。
列出進程狀態的命令是ps(process status)。System V形式下:ps -efl顯示更多信息,BSD形式是ps aux 。進程列表是動態的,如果想觀察動態的,可以使用top命令。
shell程序處理下一個命令之前會等待前一條命令結束,但是在命令最后加入&可以使其在后臺運行,便可不用等待上一個命令了。wait命令可以用來等待某個特定進程完成,在不加任何參數情況下,則為等待所有后臺進程完成。另外控制的還有bg、fg、jobs等都處理目前shell下所建立的執行中的進程。
有4組鍵盤字符可用以中斷前臺進程,這些字符可通過stty命令選項而設置。一般是Ctrl-C(intr:殺除)、Ctrl-Y(dsusp:暫時擱置,直到輸入更新為止)、Ctrl-Z(susp:暫時擱置),與Ctrl-/(quit:以核心存儲方式殺除)。
用上邊的幾個命令實現一個簡單的top命令:
再實現一個針對user查詢的腳本:
進程列表有了,如何控制或者刪除某一個進程呢。之前有說exit()能讓進程終止,但有時候我們會提前終止,這時我們需要kill命令。kill命令會傳送信號(signal)給指定的執行程序,不過它有兩個例外,稍后提到。進程接到信號,并處理之,有時可能直接選擇忽略它們。只有進程擁有者或root、內核、進程本身可以傳送信號給它。但是接收信號的進程本身無法判斷信號從何而來。不同的系統支持不同的信號類型,你可以通過kill -l 來列出你當前使用的系統支持的信號類型。每個處理信號的程序都可以自由決定如何解決接到的這些信號。信號名稱反應的是慣用性(conventions),而非必須性(requirement),所以對不同的程序而言,信號所表示的意義也會稍有不同。
kill pid 就可以直接終止進程。控制進程的話,就使用剛才kill -l羅列出來的進程信號,用法:kill[-ssignal|-p][-a]pid... 需要自行了解自己系統的進程信號。比如:
刪除進程必須直到四個信號:ABRT(中斷)、HUP(擱置)、KILL、TERM(終結)。不同系統有所不同貌似,可以查看一下,名字應該類似:
kill -l | grep -e "KILL/|BRT/|HUP/|TERM"
有些程序會在結束前做些清理工作,一般TERM信號解釋為“快速清理并結束”,如果未指定信號,默認的kill會傳送此信號。ABRT類似TERM它會抑制清除操作,并產生進程內的影像的副本。HUP類似要求中止,時常表示進程應該先停止正在處理的事情,然后準備處理新工作。有兩個進程沒有任何進程可以忽略的:KILL和STOP,這兩個信號一定會立刻被傳送,但是也有特例情況,根據實際情況也可能會被延時的。不同系統平臺有差異。
小心使用這些終止命令。當程序非正常中止,都可能在文件系統留下殘余數據,這些殘余數據除了浪費空間,還可能導致下次執行程序發生問題。比如:daemon、郵件客戶端程序、文字編輯器、以及網頁瀏覽器都會產生鎖定(lock)。如果程序第二實例被啟動,而第一實例仍在執行時,第二個實例會偵測到已存在的lock,回報該事實并立即中止。最糟糕的是,這些程序很少會告訴你lock文件的文件名,并很少將它寫入文件里。如果該lock文件長期執行進程的殘余數據,你可能發現程序無法執行,直到你找到lock并刪除為止。
有的系統提供pgrep和pkill。它們能根據進程名稱結束進程,詳細自行看manual。
關于捕捉進程信號。進程會向內核注冊哪些它們想要處理的信號。它們標明在signal()程序庫調用的參數里。man -a signal 可以查看所有關于信號的manual。trap可引起shell注冊信號處理器(signal handler),抓取指定的信號。trap取得一個字符串參數,其包含采取捕捉時要被執行的命令列表,緊接著一個要設置捕捉的信號列表。
下邊展示一個小型shell腳本:looper,它的功能是使用trap命令,說明被抓?。╟aught)與未被抓取的信號。
$ looper & #運行這個腳本于后臺
[1] 24179
$ kill -HUP 24179
Ignoring HUP ...
$ kill -USR1 24179
Terminating on USR1 ...
[1]+Done(1)
其他進程控制命令自行測試,或者搜文章學習。后邊又講了一些進程的日志。
進程延遲。sleep命令暫停執行一段時間后喚醒。at是延遲至特定時間,這個命令在不同系統有差異,但下列例子普遍適用:
at 21:00 #晚上9點執行
at now #立刻執行
at now + 10 minutes #10分鐘后執行
at now + 8 hours
at 0400 tomorrow #明天早上4點執行
at 14 July
at noon + 15 minutes #今天下午12:15執行
at teatime #下午16:00執行
at允許相當復雜的時間指定 。接受HH:MM的時間式樣,如果時間過了則為第二天這個時間。midnight是午夜,noon中午,teatime下午4點,也可以適用AM或PM后綴指定上下午,也可以month-name dat加上可選的年份式樣來指定日期,或者給出MMDDYY、MM/DD/YY或DD.MM.YY來執行日期。日期單位有minutes hours days weeks ,還有today、tomorrow。
atq命令列出at隊列里的所有工作,而atrm則是刪除它們。batch在系統負載水平允許的時候執行命令,換句話說當平均負載低于0.8或降到了在atrun文件中指定的期望值時運行。
大部分計算機有許多管理工作需要重復執行,像每晚文件系統備份之類的。crontab命令可在指定的時間執行工作,其包括了系統啟動時起始的cron daemon。crontab -l 列出你目前工作調度,以crontab -e啟動編輯器更新調度。編輯器的選擇根據EDITOR環境變量而定,有些計算機會因為未設置此參數而拒絕執行crontab。crontab適用的調度參數:
前5欄除了使用單一數字外,還可以搭配連字符分隔,指出一段區間,或者使用逗點分隔數字列表或區間。還可以使用星號,指該字段所有可能數字。范例:
15 * * * * command # 每個小時的第15分鐘執行
0 2 1 * * command # 每個月一開始的02:00執行
0 8 1 1,7 * command # 每個一月一日與七月一日的08:00執行
0 6 * * 1 command # 每周一06:00執行
0 8-17 * * 0,6 command # 每周末的08:00到17:00間一小時執行一次
在command可以詳細指出要執行的文件或重新設定要執行文件的查找路徑:
0 4 * * * /usr/local/bin/updatedb
0 4 * * * PATH=/usr/local/bin:$PATH updatedb
任何出現在標準錯誤輸出或標準輸出上的數據都會顯示給你,或是在其他實例中,將會寄到MAILTO變量的值所指定的用戶。實物上通常會比較傾向與將輸出重導至一個日志文件,并累積連續執行的記錄:
55 23 * * * $HOME/bin/daily >> $HOME/logs/daily.log 2>&1
這樣日志文件會過大,一般可以加上日期:
55 23 * * * $HOME/bin/daily > $HOME/logs/daily.`date +/%Y./%m./%d`.log 2>&1
這樣時間長了文件會過多,你可以輕松刪除或壓縮這些文件:
find $HOME/logs/*.log -ctime +31 | xargs bzip2 -9 #壓縮一個月前的日志文件
find $HOME/logs/*.log -ctime +31 | xargs rm #刪除一個月前的日志文件
這里小心crontab -r 將crontab文件整個刪除。它就像rm一樣無法撤回,也無法復原。建議保留備份:
crontab -l > $HOME/.crontab.`hostname` #存儲現行的crontab
恢復的時候:
crontab $HOME/.crontab.`hostname` #回復存儲的crontab
就像at命令那樣,系統目錄里也有cron.allow與cron.deny文件,用以控制是否允許cron工作,以及誰可以執行它們。
最后講了一下/proc文件系統,大概意思是每個子進程在那里有個目錄用進程ID命令。
新聞熱點
疑難解答