在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
作者告诉我们:到目前为止基础已经搞定,可以将前边所学结合shell变成进军中等难度的任务了。激动的要哭了,终于看到本书结束的曙光了 T T 。码字比码代码还辛苦。不过令人兴奋的是立刻就学以致用了,花了一天半的时间处理了一个3.8G的服务器日志文件,你妹啊,破电脑内存才2G。不过切割化小然后写了几个awk文件和sh文件按规则处理合并,算是搞定了!
解决这个问题,我们程序必须处理的情况可能有这些: 合并密码文件几个步骤: 这里书中针对上述步骤书写了程序,很大一部分代码是处理UID的,个人感觉全部使用新的UID来重新映射username,不是很简单就搞定一切了。只用把所有出现的username记录出来,重复的干掉,再顺序给出对应UID,很简单几步搞定了。至于之后根据old UID更改文件权限,完全可以做新旧UID的映射,直接改到新的里边就OK了。这样想来如果更改文件权限是程序主要耗时部分的话,书中原方法还是可取的,只是编码复杂度较高。如果更改权限耗时能够承受,还是选择编码复杂度低的来搞速度还快点,也方便。 这里更改文件权限使用chown命令,可以更改文件拥有用户或用户组。-R选项递归处理。但出现的问题是用户拥有的文件未必只放在用户根目录里。所以更改用户在每一个地方的文件需要使用find命令,从根目录开始做。类似这样: 这里有个另外的问题,加入old-new-list里的数据这样: 这里还剩最后一个小问题,就是find命令寻找用户的时候,注意我们问题的环境,目前是有两台服务器,find寻找用户的时候是有可能找不到另一台服务器用户的。需要作出处理。 再说一下我们解决这个问题时规避的一些真实世界的问题。最明显的是我们很可能也需要合并/etc/group文件。再者,任何一个大型的系统,都可能会出现文件拥有已不存在于/etc/passwd与/etc/group里的UID或GID值,寻找这里文件可以这样: 详细代码之类的略过吧,没特殊算法,都很简单。 第十二章拼写检查 最初的unix拼写检查原型为代码说一下: 复制代码 代码如下: prepare filename | #删除格式化命令 tr A-Z a-z | #大写转化为小写 tr -c a-z '\n' | #删除字母以外字符 sort | uniq | comm -13 dictinary - #报告不再字典内的单词 comm命令是用以比较两个排序后的文件,并选定或拒绝两个文件里共同的行。-13选项是仅输出来自第二个文件(管道输入的内容)但不在第一个文件(字典)里的行。-1 不显示第一列(只在第一个文件出现的行)-2 不显示第二列(只在第2个文件出现的行)-3不显示第三列(两个文件都有的行)。 后续的有改良的命令ispell和aspell,有一个不错的功能就是可以提供本地有效的单词拼写列表,如:spell +/usr/local/lib/local.words myfile > myfile.errs 复制代码 代码如下: $ env LC_ALL=en_GB spell +ibmsysj.sok < ibmsysj.bib | wc -l 3674 $ env LC_ALL=en_US spell +ibmsysj.sok < ibmsysj.bib | wc -l 3685 $ env LC_ALL=en_C spell +ibmsysj.sok < ibmsysj.bib | wc -l 2163 默认的locale在操作系统版本之间可能有所不同。因此最好的方式便是将LC_ALL环境变量设置与私人字典排序一致,再执行spell。env命令的作用是在重建的环境中运行命令。 书中展现了spell的awk版本,也展现awk的强大。为引导程序进行,先列出我们预期的设计目标: 复制代码 代码如下: #语法: # awk [-v Dictionaries="sysdict1 sysdict2 ..."] -f spell.awk -- \ # [=suffixfile1 =suffixfile2 ...] [+dict1 +dict2 ...] \ # [-strip] [-verbose] [file(s)] BEGIN { initialize() } { spell_check_line() } END { report_exceptions() } function get_dictionaries( files, key){ if((Dictionaries == "") && ("DICTIONARIES" in ENVIRON)) Dictionaries = ENVIRON["DICTIONARIES"] if(Dictionaries == ""){ #使用默认目录列表 DictionaryFiles["/usr/dict/words"]++ DictionaryFiles["/usr/local/share/dict/words.knuth"]++ }else{ split(Dictionaries, files) for(key in files) DictionaryFiles[files[key]]++ } } function initialize(){ NonWordChars = "[^" "'" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "abcdefghijklmnopqrstuvwxyz" \ "\241\242\243\244\245\246\247\248\249\250" \ "\251\252\253\254\255\256\257\258\259\260" \ "\261\262\263\264\265\266\267\268\269\270" \ "\271\272\273\274\275\276\277\278\279\280" \ "\281\282\283\284\285\286\287\288\289\290" \ "\291\292\293\294\295\296\297\298\299\300" \ "\301\302\303\304\305\306\307\308\309\310" \ "\311\312\313\314\315\316\317\318\319\320" \ "\321\322\323\324\325\326\327\328\329\330" \ "\331\332\333\334\335\336\337\338\339\340" \ "\341\342\343\344\345\346\347\348\349\350" \ "\351\352\353\354\355\356\357\358\359\360" \ "\361\362\363\364\365\366\367\368\369\370" \ "\371\372\373\374\375\376\377" \ get_dictionaries() scan_options() load_dictionaries() load_suffixes() order_suffixes() } function load_dictionaries(file, word){ for(file in DictionaryFiles){ while((getline word < file) > 0) Dictionary[tolower(word)]++ close(file) } } function load_suffixes(file, k, line, n, parts){ if(NSuffixFiles > 0){ #自文件载入后缀正则表达式 for(file in SuffixFiles){ while((getline line < file ) > 0){ sub(" *#.*$","",line) #截去注释 sub("^[ \t]+", "", line) #截去前置空白字符 sub("[ \t]+$", "", line) #截去结尾空白字符 if(line =="") continue n = split(line, parts) Suffixes[parts[1]]++ Replacement[parts[1]] = parts[2] for(k=3;k<=n;k++) Replacement[parts[1]] = Replacement[parts[1]] " " \ parts[k] } close(file) } }else{ #载入英文后缀正则表达式的默认表格 split("'$ 's$ ed$ edly$ es$ ing$ ingly$ ly$ s$", parts) for(k in parts){ Suffixes[parts[k]] = 1 Replacement[parts[k]] = "" } } } function order_suffixes(i, j, key){ #以递减的长度排列后缀 NOrderedSuffix = 0 for(key in Suffixes) OrderedSuffix[++NOrderedSuffix] = key for(i=1;i<NOrderedSuffix;i++) for(j=i+1;j<=NOrderedSuffix;j++) if(length(OrderedSuffix[i]) < length(OrderedSuffix[j])) swap(OrderedSuffix, i, j) } function report_exceptions(key, sortpipe){ sortpipe = Verbose ? "sort -f -t: -u -k1,1 -k2n,2 -k3" : \ "sort -f -u -k1" for(key in Exception) print Exception[key] | sortpipe close(sortpipe) } function scan_options(k){ for(k=1;k<ARGC;k++){ if(ARGV[k] == "-strip"){ ARGV[k] = "" Strip = 1 }else if(ARGV[k] == "-verbose"){ ARGV[k] = "" Verbose = 1 }else if(ARGV[k] ~ /^=/){ #后缀文件 NSuffixFiles++ SuffixFiles[substr(ARGV[k], 2)]++ ARGV[k] = "" }else if(ARGV[k] ~ /^[+]/){ #私有字典 DictionaryFiles[substr(ARGV[k], 2)]++ ARGV[k] = "" } } #删除结尾的空参数(for nawk) while ((ARGC > 0) && (ARGV[ARGC-1] == "")) ARGC-- } function spell_check_line(k, word){ gsub(NonWordChars, "") #消除非单词字符 for(k=1;k<=NF;k++){ word = $k sub("^'+","",word) #截去前置的撇号字符 sub("'+$","",word) #截去结尾的撇号字符 if(word!="") spell_check_word(word) } } function spell_check_word(word, key, lc_word, location, w, wordlist){ lc_word = tolower(word) if(lc_word in Dictionary) #可接受的拼写 return else{ #可能的异常 if(Strip){ strip_suffixes(lc_word, wordlist) for(w in wordlist) if(w in Dictionary) return } location = Verbose ? (FILENAME ":" FNR ":") : "" if(lc_word in Exception) Exception[lc_word] = Exception[lc_word] "\n" location word else Exception[lc_word] = location word } } function strip_suffixes(word, wordlist, ending, k, n, regexp){ split("", wordlist) for(k=1;k<=NOrderedSuffix;k++){ regexp = OrderedSuffix[k] if(match(word, regexp)){ word = substr(word, 1, RSTART - 1) if(Replacement[regexp] == "") wordlist[word] = 1 else{ split(Replacement[regexp], ending) for(n in ending){ if(ending[n] =="\"\"") ending[n] = "" wordlist[word ending[n]] = 1 } } break } } } function swap(a, i, j, temp){ temp = a[i] a[i] = a[j] a[j] = temp } 又是很长的代码,码的头晕。。。不保证全对,注释也先不写了。执行命令: 这里针对搞算法竞赛的同学说一点,shell脚本里的高效,怎么样叫高效,我也是搞竞赛的,总是追求程序运行时的效率,但是在shell脚本里追求的是总体效率。完成一个任务假如编码时间用了1个小时,最终完成的代码运行花30秒钟,和为了优化程序提高运行效率而编码时间花了2个小时乃至更多时间,最后运行代码时间缩减,无论缩减多少,我们都认为这个优化还是不太值得肯定的。这里不是否定代码运行效率,而是要平衡这个编码时间。而且shell目前我感觉应该是线下运行的程序多,不是在线运行的程序,所以时间上的要求可以放宽很多。所以我们要做的就是完成一个任务花费更少的时间就好。个人感觉,不对了感谢指正。 第十三章进程
shell程序处理下一个命令之前会等待前一条命令结束,但是在命令最后加入&可以使其在后台运行,便可不用等待上一个命令了。wait命令可以用来等待某个特定进程完成,在不加任何参数情况下,则为等待所有后台进程完成。另外控制的还有bg、fg、jobs等都处理目前shell下所建立的执行中的进程。 有4组键盘字符可用以中断前台进程,这些字符可通过stty命令选项而设置。一般是Ctrl-C(intr:杀除)、Ctrl-Y(dsusp:暂时搁置,直到输入更新为止)、Ctrl-Z(susp:暂时搁置),与Ctrl-\(quit:以核心存储方式杀除)。 用上边的几个命令实现一个简单的top命令: 复制代码 代码如下: #! /bin/sh - # 持续执行ps命令,每次显示之间,只作短时间的暂停 # # 语法: # simple-top IFS=' ' #自定PATH,以先取得BSD式的ps PATH=/usr/ucb:/usr/bin:/bin export PATH HEADFLAGS="-n 20" PSFLAGS=aux SLEEPFLAGS=2 SORTFLAGS='-k3nr -k1,1 -k2n' HEADER="`ps $PSFLAGS | head -n 1 `" while true do clear uptime echo "$HEADER" ps $PSFLAGS | sed -e 1d | sort $SORTFLAGS | head $HEADFLAGS sleep $SLEEPFLAGS done 再实现一个针对user查询的脚本: 复制代码 代码如下: #! /bin/sh - # 显示用户及其活动中的进程数和进程名称 # 可选择性限制显示某些特定用户 # 语法: # puser [ user1 ... ] IFS=' ' PATH=/usr/local/bin:/usr/bin:/bin export PATH EGREPFLAGS= while test $# -gt 0 do if test -z "$EGREPFLAGS" then EGREPFLAGS="$1" else EGREPFLAGS="$EGREPFLAGS|$1" fi shift done if test -z "$EGREPFLAGS" then EGREPFLAGS="." else EGREPFLAGS="^ *($EGREPFLAGS) " fi case "`uname -s`" in *BSD | Darwin ) PSFLAGS="-a -e -o user,ucomm -x" ;; * ) PSFLAGS="-e -o user,comm" ;; esac ps $PSFLAGS | sed -e 1d | EGREP_OPTIONS= egrep "$EGREPFLAGS" | sort -b -k1,1 -k2,2 | uniq -c | sort -b -k2,2 -k1nr,1 -k3,3 | awk '{ user = (LAST == $2)?" " : $2 LAST = $2 printf("%-15s\t%2d\t%s\n",user,$1,$3) }' 内容都很简单,不再赘述注释。 进程列表有了,如何控制或者删除某一个进程呢。之前有说exit()能让进程终止,但有时候我们会提前终止,这时我们需要kill命令。kill命令会传送信号(signal)给指定的执行程序,不过它有两个例外,稍后提到。进程接到信号,并处理之,有时可能直接选择忽略它们。只有进程拥有者或root、内核、进程本身可以传送信号给它。但是接收信号的进程本身无法判断信号从何而来。不同的系统支持不同的信号类型,你可以通过kill -l 来列出你当前使用的系统支持的信号类型。每个处理信号的程序都可以自由决定如何解决接到的这些信号。信号名称反应的是惯用性(conventions),而非必须性(requirement),所以对不同的程序而言,信号所表示的意义也会稍有不同。 kill pid 就可以直接终止进程。控制进程的话,就使用刚才kill -l罗列出来的进程信号,用法:kill[-ssignal|-p][-a]pid... 需要自行了解自己系统的进程信号。比如: 复制代码 代码如下: $ kill -STOP 17787 #终止进程 $ sleep 36000 && kill -CONT 17787 & #十小时后恢复 删除进程必须直到四个信号:ABRT(中断)、HUP(搁置)、KILL、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命令,说明被抓取(caught)与未被抓取的信号。 复制代码 代码如下: #! /bin/sh - trap 'echo Ignoring HUP ... ' HUP trap 'echo Terminating on USR1 ... ; exit 1 ' USR1 while true do sleep 2 date >/dev/null done $ looper & #运行这个脚本于后台 其他进程控制命令自行测试,或者搜文章学习。后边又讲了一些进程的日志。 进程延迟。sleep命令暂停执行一段时间后唤醒。at是延迟至特定时间,这个命令在不同系统有差异,但下列例子普遍适用: atq命令列出at队列里的所有工作,而atrm则是删除它们。batch在系统负载水平允许的时候执行命令,换句话说当平均负载低于0.8或降到了在atrun文件中指定的期望值时运行。 大部分计算机有许多管理工作需要重复执行,像每晚文件系统备份之类的。crontab命令可在指定的时间执行工作,其包括了系统启动时起始的cron daemon。crontab -l 列出你目前工作调度,以crontab -e启动编辑器更新调度。编辑器的选择根据EDITOR环境变量而定,有些计算机会因为未设置此参数而拒绝执行crontab。crontab适用的调度参数: 复制代码 代码如下: mm hh dd non weekday command 00-59 00-23 01-31 01-12 0-6(0=Sunday) 前5栏除了使用单一数字外,还可以搭配连字符分隔,指出一段区间,或者使用逗点分隔数字列表或区间。还可以使用星号,指该字段所有可能数字。范例: 最后讲了一下/proc文件系统,大概意思是每个子进程在那里有个目录用进程ID命令。 |
请发表评论