您现在的位置是:网站首页> 编程资料编程资料

shell脚本学习指南[五](Arnold Robbins & Nelson H.F. Beebe著)_linux shell_

2023-05-26 409人已围观

简介 shell脚本学习指南[五](Arnold Robbins & Nelson H.F. Beebe著)_linux shell_

作者告诉我们:到目前为止基础已经搞定,可以将前边所学结合shell变成进军中等难度的任务了。激动的要哭了,终于看到本书结束的曙光了 T T 。码字比码代码还辛苦。不过令人兴奋的是立刻就学以致用了,花了一天半的时间处理了一个3.8G的服务器日志文件,你妹啊,破电脑内存才2G。不过切割化小然后写了几个awk文件和sh文件按规则处理合并,算是搞定了!


第十一章扩展实例:合并用户数据库


问题描述就是有两台UNIX的计算机系统,这两个系统现在要合并,用户群同样需要合并。有许多用户两台系统上都有帐号。现在合并需要的功能是:
将两个系统里的/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编号列表。可用来寻找新的未使用UID。
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进程。可以替换成:
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
这样做将产生所有这样的文件输出。可以使用管道进一步处理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拼写检查原型为代码说一下:

复制代码 代码如下:

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
针对所写文档提供哦功能私有拼写字典,非常重要,这能使拼写检查更高效准确。但是spell还有一些棘手的事情,即locale变动后会使命令达不到预期效果如:

复制代码 代码如下:

$ 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的强大。为引导程序进行,先列出我们预期的设计目标:
1、程序将会能够读取文字数据流、隔离单词、以及报告不在已知单词列表的单词。
2、将会有一个默认的单词列表,由一个或多个系统字典收集而成。
3、它将可能取代默认的单词列表。
4、标准单词列表将有可能由一个或多个用户所提供的单词列表而扩增。该列表在技术性文件上特别有用,例如首字母缩写、术语及专有名词等。
5、单词列表将无须排序。
6、虽然默认单词列表都是英文,但辅以适当的替代性单词列表,程序将可能处理任何语言的文字,只要它是以基础为ASCII的字符集呈现,以空白字符分隔单词。
7、忽略字母大小写,让单词列表维持在易于管理的大小,但异常报告采用原大小写。
8、忽略标点符号,但顿点符号(缩写的撇)将视为字母。
9、默认的报告将为排序后具有独一无二单词的列表以一行一个单词的方式呈现。为拼写异常列表。
10、将可通过选项增加异常列表报告,并有位置信息,如文件名行号等,以利于寻找异常单词。报告将以位置排序,且当他们在同一位置发现多个异常时,则进一步依异常单词排序。
11、支持用户可指定的后缀缩写,让单词列表保持在易于管理的大小。

复制代码 代码如下:

#语法:
#   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        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        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 = sub

-六神源码网