jiayi Rss

Expect 手册中文版

Posted by jiayi | Posted in shell | Posted on 04-01-2009

0

Expect 手册 中文版

本文由gunman翻译,在此感谢~

EXPECT(1)
名字:
Expect—–能与交互式程序进行“可程序化”会话的脚本语言
大纲:
        (命令选项概述)
        expect [ -dDinN ] [ -c cmds ] [ -[f|b] ] cmdfile ] [ args ]
概述:
    Expect是一种能够按照脚本内容里面设定的方式与交互式程序进行“会话”的程序。根据脚本内容,Expect可以知道程序会提示或反馈什么内容以及 什么是正确的应答。它是一种可以提供“分支和嵌套结构”来引导程序流程的解释型脚本语言。另外,还可以在随时需要的时候把控制权交给用户,然后再还给脚 本。
    ExpectK是Expect和Tk的混合体。它就按照Expect和Tk的方式运行。Expect也可以直接嵌入到C或是C++程序中(这种情况是不涉及Tcl解释)。请看libexpect(3).
    Expect的名字是从被UUCP(UNIX到UNIX的拷贝),Kermit(一种文件传输协议, 由哥伦比亚大学设计)和一些其他Modem控制等程序设计思维大众化的“send/expect”时序理念中得出的。不像UUCP,Expect已经被广 泛应用于很多你可以想像的到的程序和任务当中了。
Expect还能同时和多个程序交互。
    例如:下面是一些Expect可以做到的事情
1)让你的计算机呼叫你,这样你可以不用付呼叫费。
2)启动一个游戏(例如:rogue),如果不是最佳配置,则一直重新启动,直到是最佳配置为止。然后把控制权转交给你。
3)运行fsck的时候,用”yes”或是”no”来回应fsck的交互问题。在没有预设答案标准的情况下把控制权返还给你。
4)连接到另一个网络或是BBS站点,自动收取你的邮件,就像邮件是发往你的当地系统一样。
5)在执行rlogin,telnet,tip,su,chgrp等等命令的时候保存“环境变量”,“当前目录”或是其他一些信息。
有很多原因致使Shell不能完成这样的任务(你自己可以试试看)。而这一切对于Expect
来说都是可以的。一般情况下,当一个程序需要程序与用户进行交互的时候就需要用到Expect。还需要的一个前提是这种交互必须能程序化(例如:循环结 构,选择结构等等,个人认为必须是有规律可循的)。如果需要的话,Expect还能把控制权返还给用户。同样,用户也可以在任何时候把控制权还给脚本程 序。

用法:
    Expect通过读取cmdfile(命令文件)来执行一系列指令。只要系统支持“#!“,在Script脚本文件的首行标明“#!/usr /local/bin/expect –f“,并赋予脚本文件可执行权限,执行脚本文件就可以(隐含方式或是默认)调用Expect。
    当然,上面的路径必须正确地指明Expect解释程序的位置。/usr/local/bin只是一个例子。
    -c 选项用来标明需要在执行脚本内容之前来执行的命令。
      这条命令(-c选项后的命令)应该用引号括起来,以免在执行时被shell分开解释。
      -c选项可能会被反复使用。多条命令可以使用同一个”-c”,命令之间需要用分号隔开。这些命令会按照它们出现的先后顺序执行。(在Expectk中,”-c”相当于”-command”)。
    -d 选项允许输出调试性信息。这些信息主要报告像expect和interact等命令执行时的内部行为。这个选项与写在脚本开头的 ”exp_internal 1”具有同样的效果,同时还会打印出Expect的版本。(strace命令用在跟踪变量声明,trace命令用于跟踪变量的赋值)(在Expectk 中,”-d”相当于”-diag”)
    - D选项开启交互调试器。后面必须跟有一个整数值作为参数,当值为非零或是按下CTRL+C的时候(或是遇到断点,或是在脚本中恰好出现其他的调试语句), 调试器会在进行下一次Tcl Procedure前取得控制权。想了解更多信息请参见README文件或是下面的SEE ALSO。(在Expectk中,这个选项相当于”-Debug”)。
    - f 选项指明从哪个文件中读取命令。这个选项是可选的,因为只有当使用”#!”时它才有可能被用到。而其他选项可以写在命令行中。(在Expectk中,它相当于”-file”)。
    默认情况下,命令文件是全部读入内存一并执行的。但有些时候需要每次只读一行。例如:stdin(标准输入)就是这样读取的。如果强制任意文件以这种方式(每次读一行)执行的话就使用”-b”选项。(在Expectk中,它相当于”-buffer”)。
    如果”-“被一个文件名替代,那么脚本就会用读指定文件的方式来替代从标准输入读的方式。(例如:”./ -“就表示从一个名为”-”的文件中读所需的信息)。
    -i 选项使Expect能交互式的提示输入命令,而不是从文件中读取。在遇到文件尾或是执行了exit命令时,提示输入命令终止。要了解更多信息请参见下面的 interpreter。-i 选项是假设既不是从一个命令文件读,也没有使用-c选项。(在Expectk中,它相当于”-interactive”)。
    – 是用来为划定选项尾的。当你需要像使用选项一样传一个参数,但希望这个参数不要被当作选项解释时,就需要用到这个选项。当阻止其他选项时,可以把它放在”#!”行中。例如:下面的例子会让所有参数(包括脚本文件名)都存储在argv中。
    #!/usr/local/bin/expect –
注意:当在”#!”行中使用参数时,必须遵守getopt(3)和execve(2)的规定。
    $exp_library下如果有expect.rc这个文件的话,它会自动被加载为资源文件(应该是类似于标准配置文件,像用户根目录下 的.bash_profile文件一样)。除非使用-N选项取消自动加载。(在Expectk中,它相当于”-NORC”)。这个文件被加载后,紧接着用 户根目录下的.expect.rc(~/.expect.rc)会被加载。除使用-n选项取消。如果定义了环境变量DOTDIR,那么它被认为是存放 有.expect.rc文件的目录。然后从这个目录中读取.expect.rc文件。(在Expectk中,它相当于”-norc”)。这些加载配置文件 的动作是出现在执行完-c选项指定的命令之后。
    -v 选项用来打印出版本号,然后退出。(在Expectk中的相应选项是-version)。
    可选的参数汇成一列,存放在变量argv中。Argc被初始化为argv的长度(变量个数)。Argv0被设置为脚本名称(or binary if no script is used)。例如:下面的例子打印出脚本的名称和前三个参数。
    Send_user “$argv0 [lrang $argv 0 2 ]\n”

命令:
    Expect使用Tcl语言(Tool Command Language).Tcl提供诸如流控制,表达式值和一些其他的特性。像递归调用,定义函数等等。在这里用到的没有说明的命令都是Tcl命令。
    Expect支持一些额外的命令。下面具体描述。除非另外声明,否则命令返回空字符串。命令按字母顺序排序,这样便于查找。仅管如此,初学者还是觉得按照”spawn , send , expect , interact”这种方式来读比较容易。
    注意,Exploring Expect这本书中提供了关于”Expect和Tcl”的介绍.这本Manpage手册中也提供了一些例子.但数量有限,因为这本是做为入门的教材手册使用的.
    在本手册中,以E开头的是指Expect程序,小写e开头的是指expect命令.
    close [-slave] [-onexec 0|1] [-i spawn_id]
    关闭与当前进程的连接.大多数交互程序会在它们的stdin(标准输入)中检测到EOF(文件尾),然后退出.所以通常close也有能力杀死进 程.-i选项指定了要杀死的对应于spawn_id的进程.expect和interact都能检测到当前程序的退出,并隐含的执行一个关闭.如果你通过 执行”exec kill $pid”来杀死进程的话,那么你就需要再显式的调用一下close.
    -onexec选项用来确定spawn_id在开始新的spawned process(我将其翻译为监测进程)时是被关闭还是要被覆盖.如果想保持这个spawn_id打开的话,那么后面的参数需要设为0.一个非零值将会使 spawn_id关闭,并可以将这个spawn_id用于新的进程(默认行为).-slave选项是用来关闭从属进程.(参见spawn -pty)。如果在连接中止的时候,从属进程还打开的话,那么它将自动关闭。不管进程是显式的调用或是隐式的被中止,你都需要调用wait命令来清理进程 执行的残余。Close不会调用wait。因为在关闭进程的时候,并不能保障它“正常退出”(个人认为,可能是指退出时做相应的清理工作)。要了解更多的 信息,请参见wait命令。
    debug [[-now] 0|1]
    控制Tcl调试器以步进方式执行语句,设置断点等等。在没有参数的情况下,如果调试器没有运行,返回1,否则返回0。用1做参数时,启动调试器,用0做 参数时,停止调试器。如果连同-now一起使用的话,调试器将立即启动(也就是说,在debug命令当中)。否则调试器会在执行下一条语句的时候启动。
    调试命令不会改变任何的traps。可以参见以-D选项启动Expect(参见上面)
    要了解更多关于debugger的内容,请参见README文件和下面的SEE ALSO。
    disconnect
从终端关闭与一个克隆进程的连接,但让它在后台继续运行。这个进程将被赋予为单独的进程组(如果可能的话)。标准I/O被重定向到/dev/null。下面的代码使用disconnect命令使脚本在后台继续运行。
    if [fork]!=0 exit
        disconnect
    下面的脚本需要读取一个密码,然后每小时执行一次,每次执行的时候都要求输入密码。脚本提供了所需的密码,所以你只需输入一次就可以了。(参见能关闭回显的终端命令)。
    send_user "password?\ "
        expect_user -re "(.*)\n"
        for {} 1 {} {
            if [fork]!=0 {sleep 3600;continue}
               disconnect
               spawn priv_prog
               expect Password:
               send "$expect_out(1,string)\r"
               . . .
               exit
        }
用这种方式,而不是用Shell后台方式来执行程序的好处是,用disconnect可以在关闭前保存终端参数,然后把它们应用于新的终端中。如果使用&的话,Expect没有机会读取终端参数,因为在Expect取得控制权的时候,终端已经退出了。
    exit [-opts] [status]
    使Expect退出或是准备退出。-onexit选项使下一个参数做为退出的句柄被使用。没有参数时,返回当前的退出句柄。-noexit选项使 Expect准备退出,而不是把控制权暂时返还给系统。用户定义的退出句柄和Expect内部的句柄都是以同样的方式被返回。接下来将不再执行 Expect命令。这在Tcl扩展环境下执行Expect时非常有用。保留当前的解释器(在Tk环境中的主窗口)以清除其他的Tcl扩展。如果 Expect再次调用exit(这有可能发生),不会返回句柄。退出时,全部连接将关闭,关闭的动作会被监测的进程检测为“到文件尾”。exit只按照正 常exit(2)的中的语句来执行,不会引发其他的动作。因此监视的进程如果没有检测到“到文件尾”的话,会继续执行。(能检测更多的情况是很重要的。例 如:什么样的信号会发给监测进程,但这些是由系统决定的,它们放在exit(3)的文档中)。如果被监测的进程继续运行的话,将会被init继承。当前的 状态信息将做为Expect的退出信息返回(如果没有指明的话,返回信息为0)。退出命令在脚本程序执行的最后才自动隐含的调用。
    Exp_continue [-continue_timer]
    这个命令可以使expect继续执行而不是正常的返回.默认情况下,exp_continue会重高超时时钟,-continue_timer选项会阻止时钟重新计数(连续计数).
    Exp_internal [-f file] value
    如果是value非零的话,使接下来的命令将调试信息输出到Expect和标准错误输出.如果是0的话,输出的信息将会被屏蔽.调试信息包括收到的每条 信息和每次尝试用当前输出与脚本中的模式相匹配的信息.如果设置了输出文件,那么正常的和调试的信息都会被写到这个文件当中.(忽略上面value选项的 值).任何之前打开的调试输出文件将会被关闭.-info选项使exp_internal返回最近关于non-info参数的描述.
    Exp_open [args] [-I spawn_id]
    它返回对应于原始spawn id的文件描述符.这样这个文件描述符就可以被使用了,就好像这个文件是被Tcl的open指令打开的一样.(这个spawn id将不再使用,wait指令将不能用在这个进程.).-leaveopen选项使spawn id保持打开,以便供Expect命令使用.
    Exp_pid [-i spawn_id]
    它将返回对应于当前被跟踪进程的ID.如果使用-i选项,将返回对应于指定的spawn id的进程ID.
    Exp_send
        它是send的别称
    Exp_send_error
        它是Send_error的别称
    Exp_send_log
        它是Send_log的别称
    Exp_send_tty
        它是Send_tty的别称
    Exp_send_user
        它是Send_user的别称
    Exp_version [[-exit] version] 它用于确保脚本程序与当前的Expect兼容。在没有参数的情况下,返回当前Expect的版本.这个版本就会编译到脚本中.如果你确切的知道你的脚本程 序不需要最新版本的特性,可以指定一个以前的版本。    
    版本号由三个由句点分隔的数字组成.第一个是主序号.对应某一主序号版本的Expect写的脚本程序,在不同主序号版本的Expect环境下基本不能正 常运行.exp_version在主版本不同的情况下会返回一个错误.第二个数字是次版本号.编写脚本的Expect的次版本号如果比当前的Expect 大的话,可能会用到一些新的特性,在当前的环境下可能不能正常运行.exp_version会在当主序号相同,但次序号比当前Expect版本大的时候返 回一个错误信息.第三个数字在Expect的版本比较中没有多大作用.它只是当发行版有任何变化的时候会增加.比如说增加一些新的文档或是做了优化.当升 级到一个新的次版本号时,这个数字会被初始化为零.如果使用了-exit选项,Expect会在当前的版本过期的时候打印一个错误信息,然后退出.
    expect [[-opts] pat1 body1] … [-opts] patn [bodyn]
    等待直到被监视进程的输出与设定的模式相匹配,或是一个指定的时间过后,或是遇到文件尾.如果最后的body是空的,那么它将被忽略.最近的 expect_before设定的模式会在其他模式之前被隐含地使用.最近的expect_after设定的模式将在所有其他模式匹配完后才被调用.如果 整个Expect命令的参数超过一行,这个参数可能被分为多行,各行之间用一个”\”连接,以防被分开解释.在这种情况下,Tcl解释器通常会置换掉” \”.如果一个模式设定为eof.则相应的语句被在当达到文件尾的时候执行.如果一个模式设定为timeout,那么相应的语句会在超时时执行.如果没有 设定timeout对应的执行语句,将会在timeout时隐含执行空指令.即不执行任何语句.默认的超时时钟设的是10秒,但可以自己设定.通过 ”set timeout 30”,可以将超时时钟设定为30秒.如果设定为-1的话,那么超时时钟将是无穷大,如果一个模式设定为default,那么相应的语句将会在遇到文件尾 或是超时时执行.如果触发了相应的模式,则此模式对应的语句将会被执行.Expect返回语句执行的结果(或是在没有模式触发的情况下是空字符串).在多 种模式匹配的情况下,第一个匹配的模式对应的语句将被执行.
    每次出现新的输出的时候,它们都会依次匹配相应的模式.因此,如果你想测试匹配是否成功,可以把最后一个模式设定为肯定会出现的东西,例如一个提示符. 在没有提示符的情况下,你需要使用一个timeout模式。模式被定义为三种类型.默认情况下,模式被定义为Tcl的string match(字符串匹配)指令.(这些模式很像C Shell中的正则表达式,它们通常被用来做模糊匹配).-gl选项保护那些可能被认为是Expect的选项的模式匹配字符串。以”-”开头的所有模式匹 配字符串都需要这样保护起来。(因为默认情况下,以”-”开头的字符串都被保留起来做为将来的选项)。例如:下面的代码期望一个正确的登录.(注意下面的 abort是一个已经在脚本的其他位置定义好的函数)
        expect {
                     busy               {puts busy\n ; exp_continue}
                     failed             abort
                     "invalid password" abort
                     timeout            abort
                     connected
                 }
    在第四行需要使用引号,因为它包含了一个空格.如果不用引号把模式括起来的话,它会被分别解释为模式与执行函数.执行同样动作的其他模式同样需要把执行 函数的名称写出来(像其他两个执行”abort”函数的模式),如果使用regexp-style模式的话(看下面的例子),更多关于建立glob- style模式的信息请参见Tcl手册.regexp模式以-re开头.上面的例子可以用regexp模式改写成下面的代码:
        expect {
                     busy       {puts busy\n ; exp_continue}
                     -re "failed|invalid password" abort
                     timeout    abort
                     connected
                 }
两种类型都可以被正确匹配。这就是说设置的类型可以不用是整个字符串。可以只匹配头部或是尾部(就假设其他部分也匹配一样)。用^来匹配字符串头部。 用$来匹配字符串尾部。如果你不希望等待直到字符尾,你可以在监视进程回显字符的中间时刻结束响应。虽然仍能打印出正确的结果,但最后的输出可能看上去有 点混乱。因此如果能够详细描述预期的字符串尾部的话,还是鼓励使用$来匹配尾部。在许多编辑器中,^和&分别表示首行和尾行。因为Expect不 是基于行缓冲的程序。所以这两个字符分别用来表示当前匹配缓冲区中的头数据和尾数据。-ex使模式进行精确匹配。这时,不对*,^等字符进行解释(但还是 要遵守Tcl的规则)。Expect patterns are always unanchored.
    -nocase选项使输出中的大写字符也按小写字符匹配。模式匹配字符串本身改变。在读取输出进行匹配时,超过2000字符将会强制将前面的字符丢弃。 这个数目可以通过match_max来改变。(但太大的数目会降低匹配的效率)。如果patlist是full_buffer,则在收到 match_max个字节而没有相应的模式匹配成功时,执行full_buffer所对应的语句。不管是否使用了关键字full_buffer,丢弃的字 符都会被写到expect_out缓冲区中。如果patlist是关键字null。并且空是有效字符(通过remove_nulls指令访问),如果输出 是一个单个的ASCII码0,那么null相对应的语句将被执行。通过glob或是regexp模式是不能来匹配0字符。        
在匹配字符串时(或是遇到文件尾,或是缓冲区满full_buffer),任何匹配的或是前面没有匹配的输出都会被保存在expect_out缓冲区中。 匹配到的9个字符分别被放到expect_out(1,string)至expect_out(9,string)中。如果在模式前使用了 -indices选项,那么,这10个字符的开始字符和结尾字符在字符串中的位置被分别存放在变量expect_out(X,start)和 expect_out(X,end)中。其中X是自然数(应该是0到9)。0(expect_out(0,*))是指整个匹配的字符串,它可以用于 glob模式,也可以用于regexp模式。例如:如果一个进程的输出为“abcdefgh\n”,那么expect “cd”的执行结果和下面的代码执行结果是一样的。
        Set    expect_out(0,string) cd
        Set    expect_out(buffer) abcd
“efgh\n”被丢弃到输出缓冲区了。如果一个进程的输出是”abbbcabkkkka\n”,那么expect –indices –re “b(b*).*(k+)”的执行结果和下面语句的执行结果是相同的。
        set expect_out(0,start) 1
                 set expect_out(0,end) 10
                 set expect_out(0,string) bbbcabkkkk
                 set expect_out(1,start) 2
                 set expect_out(1,end) 3
                 set expect_out(1,string) bb
                 set expect_out(2,start) 10
                 set expect_out(2,end) 10
                 set expect_out(2,string) k
                 set expect_out(buffer) abbbcabkkkk
“a\n”被丢弃了输出缓冲区中了。含有”*”(和-re  “.*”)的模糊匹配的模式会清空输出缓冲区,不再读取从进程中输出的字符。一般情况下,匹配的输出会被Expect的内部缓冲区丢弃.可以通过在模式前 加上-notransfer选项来避免被丢弃.这个选项在实验时非常有用(为了方便,可以简写成-not).与匹配输出相对应的spawn id被存储在expect_out(spawn_id)中.
    -timeout 选项使得Expect使用选项后面的数值做为超时时间,而不是timeout变量中设置的时间.    
    默认情况下,设定的模式只与当前进程的输出进行匹配.-i选项使得指定spawn_id或是spawn_id列的输出与下面列出的所有模式进行匹配(直 到下一个-i选项为止).spawn_id列要么是用空格分隔的一列spawn_id,要么是用变量存储的这要一列spawn_id.例如,下面的例子 中,当前进程与”connected”进行匹配,由变量$proc2指定进程与”busy”,”failed”,”invalid passowrd”进行匹配。
        expect {
                     -i $proc2 busy {puts busy\n ; exp_continue}
                     -re "failed|invalid password" abort
                     timeout abort
                     connected
              }
    全局变量any_spawn_id的值是在当前expect中所有-i选项定义的spawn_id进程列的总和.它用来使这些spawn_id进程列与 模式进行匹配。在一些-i选项中可能只给出了spawn_id列,但没有给出相应匹配模式.(例如,-i选项紧接下来就是另一个-i选项).那么这些 spawn_id列将会去匹配与any_spawn_id相对应的模式.    
    -i选项还可以定义一个全局变量,里面存储着spawn_id列.当变量内容发生变化时,它会被重新读取.这样就可以在程序执行的时候改变I/O源.以这种方式提供的spawn_id被称为”indirect spawn_id”.
    Break和continue使流程(例如:for结构,proc函数)按照正常的顺序执行.exp_continue使expect继续执行而不是像 通常一样返回.这对于避免explicit loops(不执行以后有语句,直接进入下一次循环)和重复的语句很有用.下面的例子是一个自动rlogin的代码片断.exp_continue的使用 避免了在rlogin揭示输入密码的时候的再写一个重复的expect语句.(需要等待第二次提示)
        expect {
                     Password: {
                         stty -echo
                         send_user "password (for $user) on $host: "
                         expect_user -re "(.*)\n"
                         send_user "\n"
                         send "$expect_out(1,string)\r"
                         stty echo
                         exp_continue
                     } incorrect {
                         send_user "invalid password or account\n"
                         exit
                     } timeout {
                         send_user "connection to $host timed out\n"
                         exit
                     } eof {
                         send_user \
                             "connection to host failed: $expect_out(buffer)"
                         exit
                     } -re $prompt
                 }
    例如,下面的代码使用户可以在任务完全自动化的情况下,还能引导人机交互.这种情况下,终端被设置成原始状态.如果按下”+”,那么一个变量的值增加, 如果按下”P”,那么向进程发送几个回车符,或是以其它的方式回应一下.如果按下”i”,那么用户就会从脚本那儿把控制权收回,来与进程进行交互.在每个 情况下,exp_continue都使在执行完当前的动作之后,继续执行模式匹配.
    stty raw -echo
                 expect_after {
                     -i $user_spawn_id
                     "p" {send "\r\r\r"; exp_continue}
                     "+" {incr foo; exp_continue}
                    "i" {interact; exp_continue}
                     "quit" exit
                 }
    默认情况下,exp_continue会重置超时时间.如果以带有-continue_timer选项的方式执行exp_continue的话,超时时钟不会重新启动.
    Expect_after[expect_args]
    它和expect_before的工作方式相同.在expect和expect_after能同时匹配的情况下.进程与expect命令下面的模式进行匹配.想了解更多的信息请参见expect_before.
    Expect_background [expect_args]
    它和expect有一样的参数列表.但不同的是它是立即返回.一旦有新的输入到达时就开始进行模式匹配,timeout和default两个模式对于 expect_background来说没有意义,它们会被隐含忽略.否则,expect_background会像expect一样调用 expect_before和expect_after的模式匹配.
    当expect_background在执行模式匹配时,对应于这个spawn_id的后台进程将被阻塞.当执行完成时,后台进程被解开.在后台进程被 阻塞期间,还可以在前台以同样的spawn_id执行一个expect脚本.但在非阻塞情况下是不可能这样做的.在用同一个spawn_id声明一个新的 expect_background时,前一个就会被自动删除.声明一个没有匹配模式的expect_background将会使相应的spawn_id 失去在后台匹配模式的能力.
    Expect_before [expect_args]
    它和expect具有相同的参数列表.但不同的是它立即返回.相同spawn_id最近的expect_before下的匹配模式会自动隐含的加载到下 面的expect命令中.如果其中一个模式匹配成功了,就好像匹配的模式是列在expect命令本身下面一样.如果expect_before和 expect的模式同时匹配,那么将使用expect_before.如果没有相应的匹配列出来,那么这个spawn_id将不进行任何模式匹配的动作.
    除非使用-i选项强制声明,否则expect_before的模式将与执行expect_before命令时对应的spawn_id的进程输出进行匹配 (而不是有模式匹配成功时的spawn_id).-info选项会返回当前模式的详细信息.默认情况,它会报告当前的spawn_id的信息。也可以通过 指定spawn_id来显示指定spawn_id的信息.
例如:    expect_before –info –I $proc
    这样最多返回一个spawn_id的详细信息. The flag -indirect suppresses direct spawn ids that come only from indirect specifications.
    -all选项使expect_before报告所有spawn_id的信息,而不是单个spawn_id的信息。
    expect_tty [expect_args ]
    和expect的用法很像,但它是从/dev/tty读取字符串(例如:用户的击键)。默认情况下,读是工作在精加工缓冲模式下的。因此,每行之后必须 以回车结尾,这样expect才能分别识别它们。读模式(例如行缓冲,等等)可以通过stty命令更改(参见下面的stty命令)
    expect_user [expect_args]
    和expect的用法很像,但它是从stdin(标准输入)读取字符串(例如:用户的击键)。默认情况下,读是工作在精加工模式下的。因此,每行之后必 须以回车结尾,这样expect才能分别识别它们。读模式(例如行缓冲,等等)可以通过stty命令更改(参见下面的stty命令)。
    Fork
    创建一个新进程。这个新进程是当前进程的完整拷贝。成功时,会返回0给新进程,返回新进程的ID给当前进程。失败时(失败的原因可能是资源匮乏,如交换 分区,内存不足等),返回一个-1给当前进程,没有新进程创建。复制的新进程和它的父进程一样通过exit命令退出。复制的新进程允许写日志文件。如果不 屏蔽大多程序的debugging(调试)和logging(写日志)功能,结果(个人认为:输出结果或是日志)看起来会显得有点混乱。在多个用户的情况 下,即使是很短暂的pty执行结果,看起来也会很让人混乱迷惑。因此,在监视某个进程(个人认为是执行spawn)之前执行fork更好一点。
    interact [string1 body1] … [stringn [bodyn]]
    返回当前进程的控制权给用户。所以击键会被传给当前进程(就像平时操作一样)。当前进程的stdout和stderr也会返回(个人认为:可能在脚本执 行时,标准输出和标准错误输出是被重定向到Expect的,因为执行spawn之后,expect会等待进程的输出,包括错误输出)。String- body被指定为参数。在这种情况下,当有指定的string输入时,对应的body就会被执行(默认情况,string不会被传给当前进程)。如果没有 最后的body部分,那么将执行interact命令。如果整个interact语句参数过长,超过一行,这些参数会用反斜线连接,分隔在多行,这样避免 了语句在执行时被隔断。这种情况下,在Tcl进行语法解释的时候会忽略这些反斜线,把这多行做为一条语句来执行。例如,下面的代码举例说明了以 string-body方式执行interact命令。String-body是这样设定的:当你按下Ctrl+Z时,Expect 将挂起,按下Ctrl+A时,用户将会看到屏幕显示“you typed a control A”,并且也向当前进程发送一个Ctrl+A。当用户按下$时,用户会看到屏幕上显示系统日期。按下Ctrl+C 时Expect将退出。如果输入”foo” ,用户将在屏幕上看到“bar”,如果输入~~,那么Expect解释器交互执行。
        set CTRLZ \032
                 interact {
                     -reset $CTRLZ {exec kill -STOP [pid]}
                     \001   {send_user "you typed a control-A\n";
                             send "\001"
                            }
                     $      {send_user "The date is [exec date]."}
                     \003   exit
                     foo    {send_user "bar"}
                     ~~
                 }
    在string-body中,字符是按string在string-body中出现的顺序匹配的。
    在不清楚余下的字符是什么的情况下,只是部分匹配的字符是不会被发送到当前进程的。如果在获得了余下的字符之后,整个字符串没有相应的string- body可以匹配(也就是说整个字符串在string-body中,没有对应相同的string),除了上面说的匹配字符外,也没有其他更多的匹配(个人 理解:比如整个字符xxxbbccada.第一次提到的匹配字符xxxbb,string-body中有两个对应的 string:string1=xxxbb,string2=xxxbbcc,那么也就是说整个字符是没有相应的string与之匹配,如果只有 string1,没有string2,那也就是“没有更多的匹配”,只有xxxbb会发送到当前进程,如果存在string2,那么我们最好把 string2放在string1前面,这样可以先在匹配string2,如果输出字符串中,没有相应的xxxbbcc,然后再去匹配string1。也 就是说把“最大匹配”放在前面),那么只有匹配的字符会发给当前进程。因此,我们可以把“部分匹配”放在后面,如果整个字符(或是“最大字符”)匹配失 败,我们再进行“部分匹配”。默认情况下,string匹配必须是精确完全匹配。(与之相反,expect命令默认使用glob-style模式)。 -ex选项保证那些可能被解释成interact选项的string能被正确执行。任何以”-”开头的string都需要使用-ex。(所有以”-”开头 的字符将被做为选项)
    -re选项强制string按regexp模式解释。这种情况下,像expect会把它的输出存储在变量expect_out里面一 样,interact匹配的字符串也会在存储在变量interact_out中。-indices选项的作用也和expect中的一样。Eof模式列出了 在遇到文件尾的时候要执行的语句。一个单独的eof模式可能跟在-output选项后面,这样当写输出遇到文件尾的时候,就会触发eof模式,执行相应的 语句。默认的eof行为是返回,所以执行interact命令时,在遇到文件尾就是返回。Timeout模式介绍了超时(以秒为单位)的概念,并列出了 (超时)连续数秒没有读取到字符后的执行语句。Timeout作用于最近指定的进程。**这里没有默认的timeout,特殊变量 timeout(expect命令里面使用的)对这里的timeout模式没有影响。**例如,下面的命令可以用于自动退出用户,他们在一小时之内没有输 入任何字符,却一直收到系统消息。
interact -input $user_spawn_id timeout 3600 return –output  $spawn_id
    如果模式为关键词null,而且null是允许的(通过remove_null命令),则在输出中如果出现单个的ASCII 0,那么null对应的语句将被执行。在glob和regexp模式下是不可能完成的。在模式前加上-iwrite选项,将会把匹配成功(或是遇到文件 尾)的进程的spawn_id赋值给变量interact_out(spawn_id)。Break和continue会使控制结构(for循环,子函数 等等)按照正常的方式运行,但return会使interact把信息返回给它的调用函数。Inter_return会使它的调用函数返回。例如,如果一 个子函数foo调用了inter_return,在执行inter_return时,子函数foo会返回。(这就是说,当interact交互式调用解释 器时,如果输入return,那么交互还将继续,如果输入inter_return,那么interact将返回)
    在interact执行过程中,终端工作在“原始状态”下,这样所有字符都将发送给当前进程。如果当前进程没有捕获到工作流程的信号,那么按下 Ctrl+Z会使其中止。如果想重启这个进程,可以给它发送一个“继续”信号(如执行:"kill  -CONT  <pid>"),如果你真想给当前进程发送一个“中止”信号,你可以考虑先监视csh,然后再启动你的程序。也就是说,如果你想发送中止信号 给Expect,首先要调出解释器(可能是按一下ESC键),然后按下Ctrl+Z。
    为了避免进入解释器,交互式的执行命令,string-body可以用做“速记”,当string-body对应的body执行的时候,使用的前一个终 端模式。为了程序的执行效率,默认情况下,终端使用原始状态。-reset使终端恢复到interact执行以前的状态(总是“精加工”状态)。注意的 是,在进行终端模式转换的时候,此时输入的字符可能丢失(在一些系统上,会出现这种
糟糕的现象)。最好在你必须使用”精加工”模式再使用-reset选项。-echo选项使与模式进行匹配的字符同时也被发送给产生这些字符串的当前进程,就好像是当前进程读取到他们一样。这在当用户希望在执行某些指令需要看到回显的时候非常有用。
    如果回显了一个模式,但最终没有匹配成功,这些字符会被发送到监视的进程,如果监视的进程再把它们显示出来的话,那么用户将会看到他们两次。-echo 可能仅仅适合于当用户不可能不完成模式匹配的情况。例如:下面是摘自于rftp,一个递归式ftp脚本,用户被提示输入”~g,~p,~l”,以便递归的 ”获得,上传,查看”当前路径。这些字符和常规的ftp命令相差太远,用户除非出错,否则基本上不会打出”~”后面跟有某些字符的情况。这种情况下,他们 就可能会忽略了正确的结果。
        interact {
                     -echo ~g {getcurdirectory 1}
                     -echo ~l {getcurdirectory 0}
                     -echo ~p {putcurdirectory}
                 }
    -nobuffer选项会把进行模式匹配的字符发送给输出进程,就像这些字符是被读取的一样。这在你想让进程回显模式的时候非常有用。例如,下面的代码监视了哪个用户在拨叫(一种Hayes模式的Modem),每次都会在脚本的日志文件中后面看到一个”atd”。
        proc lognumber {} {
                     interact -nobuffer -re "(.*)\r" return
                     puts $log "[exec date]: dialed $interact_out(1,string)"
                 }
                 interact -nobuffer "atd" lognumber
    在交互过程中,log_user的前一个值被忽略了。特别需要说明的是,interact会强制使他的输出记录成日志(输出到标准输出),因为它认为用 户不希望没有任何回应的交互。-o选项使下面的key-body模式应用于当前进程的输出(也就是说用当前进程的输出来匹配模式)。这对于处理像”在一个 telnet会话中输入很多错误字符(个人认为:非命令或是选项字符)”的情况非常有用。
    默认情况下,interact希望用户对标准输入进行写操作,对标准输出进行读操作。-u选项通过指定进程名(通常是指定一个spawn_id)来使此 进程的用户与其他进程进行interact(交互)。这就使两个毫不相差的进程通过这样一个联系连接起来。为了协助调试,Expect的调试信息经常会输 出到标准错误输出(或是是标准输出,为了记录日志和调试信息)。同样,解释器也会交互的从标准输入读取字符。例如:下面的代码,建立了一个登录进程,它呼 叫用户,然后使双方连接在一起。当然其他进程也可以取代这里的login进程。一个脚本,允许在不提供用户名与密码的情况下正常工作。
        spawn login
                 set login $spawn_id
                 spawn tip modem
                 # dial back out to user
                 # connect user to login
                 interact -u $login
    为了发送输出给多个进程,必须使用-output选项指定spawn_id列表。同样,要给多个进程输入字符,需要使用-input选项(-input 和-output,还有expect中的-i选项都支持列表,除了特殊变量any_spawn_id在interact命令中无效,在expect中有 效)。
    所有接下来的选项或字符串(或模式)对当前的输入有效。直到下一个input选项为止。如果没有-input选项,-output选项暗含表示”– input $user_spawn_id –output”(在不含有-input选项的模式中也一样)。如果指定了一个-input选项,那么它将覆盖$user_spawn_id,如果出现第 二个–input选项,那么它将覆盖$spawn_id,还有可能会指定更多的-input选项。
    这两个暗含的输入进程把它们的输出默认分别把$spawn_id和$user_spawn_id作为它们的输出(做了调换)。如果-input选项后面 没有-output,那么这个进程的输入将会被忽略。-i选项介绍了一种当没有使用-input或是-output选项时的替代方式。-i选项暗含一个 -o选项。
    使用“间接”spawn_id列可以改变交互进程 (“间接”spawn_id列已经在expect命令里面讲过) 。“间接”spawn_id列可以通过-i,-u,-input或是-output选项指定。
    interpreter  [args]
    使用户交互的输入Expect或是Tcl命令,每个命令的结果都会被打印出来。
    Break和continue会使控制结构(for循环,子函数等等)按照正常的方式运行,但return会使interact把信息返回给它的调用函 数。Inter_return会使它的调用函数返回。例如,如果一个子函数foo调用了inter_return,在执行inter_return时,子 函数foo会返回。其他命令使interpreter继续提示输入新的命令。默认情况下,提示包含两个整数。第一个表示the depth of evaluation stack嵌套的层数(也就是Tcl_Eval被调用了多少次)。第二个参数是Tcl的history identifier历史指针。提示符可以通过定义一个叫做”prompt1”的子函数来设置,这个子函数的输出会成为下一个提示符。如果一条语句中包含 半开的(也就是一个,不是一对儿)引号,大括号,中括号或是小括号,那么下一个提示符会被放在新一行。第二个提示符同样也可以通过定义一个叫做 ”prompt2”的子函数来设置。在interpreter执行过程中,终端使用“精加工”模式,即使它的调用函数使用的是“原始中”模式。如果在没有 使用-eof选项的情况下,标准输入被关闭,那么interpreter就会返回。如果使用了-eof选项,那么将调用下一个参数。
    log_file [args] [[-a] file]
    如果指定了文件名,那么log_file命令会把会话的记录写入文件(从执行这条语句开始),如果没有给定任何参数,那么log_file命令会停止记 录。前面的日志文件都将被关闭。不指定文件名,还可以通过-open或是-leaveopen选项来指定Tcl文件描述符,这和spawn命令的用法一样 (参见spawn命令)。-a选项强制把log_user命令产生的输出记录到日志。默认情况下,为了在一次会话中能很方便的多次关闭日志记 录,log_file命令会把输出信息添加到文件尾,而不是覆盖原来的内容。如果想覆盖原来的内容,可以使用-noappend选项。-info选项使 log_file命令返回关于最近的non-info(非info选项)参数的描述。
    log_user -info|0|1
    默认时,send/expect对话会被记录到标准输出中,可以通过log_user 0来禁止,通过log_user 1来恢复。输出到日志文件维持不变。-info选项使log_user命令返回关于最近的non-info(非info选项)参数的描述。
    match_max [-d] [-i spawn_id] [size]
    这个命令定义Expect内部使用的缓冲区大小。如果没有参数,返回当前大小。如果使用-d选项的话,将缓冲区设置为默认大小(初始的默认大小是 2000 Bytes)。如果使用了-i选项,那么设置的是对应于spawn_id的进程的缓冲区大小。否则设置的是当前进程的。
    overlay [-# spawn_id] [-# spawn_id] [...] program [args]
    终止当前的Expect程序,执行program args。一个连字符没有指定参数,那么连字符将被放到命令之前,就像它是一个登录Shell一样。除了那些在命令行中做为参数指定的spawn_id 外,其他将全部被关闭。这些在命令行中指定的spawn_id将被重定向到指定的文件描述符。这些spawn_id被重定向到文件描述符是为了新程序来继 承。例如,下面的命令运行chess程序,而且允许当前程序–chess master(chess控制者)来控制。
        overlay -0 $spawn_id -1 $spawn_id -2 $spawn_id chess
    虽然它牺牲了执行程序化交互的能力,因为Expect已经失去控制权,但还是要比”interact –u”更有效率。注:在这里,没有提供控制终端,因此,如果你断开或是重定向了标准输入,那么控制作业的程序(Shell,login等等)将不能正常访问。
    parity [-d] [-i spawn_id] [value]
    定义parity是否需要与当前监视的进程的输出分隔开。如果设为0,则是分隔开,否则将不分开。如果没有参数的话,将返回当前值。-d选项将 parity设置为默认的值(初始默认值为1,不分开)。-i选项用来指定需要设置parity值的,对应于spawn_id的进程。否则设置当前进程的 parity值。
    remove_nulls [-d] [-i spawn_id] [value]
    此命令用来定义null在匹配模式或是存储到变量expect_out或是interact_out之前,是否需要与监视进程的输出分隔开。如果设为 1,则分开,如果为0,则不分开。没有参数,返回当前值。-d选项用来把它设为默认值(初始默认值为1,分隔开).-i选项用来设置对应于 spawn_id的进程的remove_nulls的值。如果没有使用-i选项,那么将设置当前进程的remove_nulls的值。不管null是否被 分隔,Expect都会把它们记录到日志和标准输出中。
    send [-flags] string
    发送字符串给当前进程。例如” send “hello world” ”会向当前进程发送“hello空格world回车”(Tcl提供了一个类似于printf的命令,它可以构建任意复杂的字符串)。尽管工作在行缓冲模式 下的程序直到遇到回车的时候才会读到这些字符,但这些字符还是在输入的时候就被立即发送出去了。回车被表示为”\r”。
    –选项使下面的参数做为字符串而不是选项。所有的字符串,不管它是否像选项,都可以前缀一个”–”。这提供了一种安全机制:不会再去费力区分很像选项的字符串到底是什么了。(所有以”-”开头的字符串将作为选项使用)。
    -i选项指定字符串将发往对应于spawn_id的进程。如果指定的spwan_id是user_spawn_id的话,而且终端工作在原始状态下,那 么字符串中的换行将被转换为回车换行,以使它们看起来像是工作在“精加工“状态下。-raw选项禁止这种转换。-null选项用来发送null字符。默认 情况是发送一个null字符。可以通过设定一个整数来确定发送多少个null字符。-break产生一个中断条件。这只有对使用spwan_open命令 打开tty设备对应的spawn_id时才有效。如果你监视了类似于tip的程序,那么你应当遵循tip的规则来产生一个中断。-s选项强制发送速度变 慢。这是避免计算机输入时,会出现输入缓冲区溢出。因为某些输入缓冲区是以人的输入速度为标准设计的。输出的速度由变量send_slow的值来限定的。 它带有两个参数。第一个是一个整数,它用来描述自动发送的字节数。第二个参数用来设定发送的时间间隔。例如:set send_slow{10 .001} 将使”send -s”在每发10个字符后停1毫秒,然后再发10个字符。-h选项使发送模拟人的击键速度。每个字符之间的时间间隔也像人手敲击间隔。(这种算法是基于 WeiBull规则,它带有修正值以适应特殊情况)这种输出的速度通过变量send_human中的5个参数来设定。前两个参数是字符敲击之间的平均时 间,第一个是默认使用时间。第二个是输入一个词后的停顿时间。以模拟这种转变过程中(从连续输入字符到输入一个词再输入另一个词的转变)的微小停顿。第三 个参数是稳定度,.1表示非常不稳定,1表示相对不稳定,10表示非常稳定。极限是0和无限大。最近两个参数分别指定了最小和最大的间隔时间。它们用来修 正最终时间。如果最小和最大有大量的取值范围,最后的平均时间可能会和给定的平均间隔时间有很大的差别。
    例如:下面模拟了一个快速稳定的打字员
    set send_human {.1 .3 1 .05 2}
send -h "I’m hungry.  Let’s do lunch."
下面模拟的更适合于一个经常有较大停顿的打字员
set send_human {.4 .4 .2 .5 100}
send -h "Goodd party lash night!"
注:错误是不可以模拟的。虽然你可以通过在send命令参数里面嵌入错误和更正来设定错误更正模式。
这些发送null字符,设定中断条件和强制发送变慢,模拟人的输入的选项之间都是互斥的,只有最后的选项有效,还有需要说明的是,发送null字符和设定中断条件选项是不能带有其他参数的。
在第一个send前加一个expect命令是个不错的主意,因为expect会等待进程开始,而send不会等待。在某些情况下,进程还没有开始,你的第 一个send已经发送完毕了,这样你就会有把你的数据丢失的危险。在某些交互程序没有初始化提示符的情况下,可以在send之前加一个延时。
        # To avoid giving hackers hints on how to break in,
                 # this system does not prompt for an external password.
                 # Wait for 5 seconds for exec to complete
                 spawn telnet very.secure.gov
                 sleep 5
                 send password\r
exp_send是send的别名。如果你使用的是Expectk或是Tk环境下某些Expect的变体或是扩展,那么Tk会把send做为与现在完全不同用途的命令。
    send_error [-flags] string
    用法很像send,除了它是把输出发送到stderr(标准错误输出),而不是当前进程。
    send_log [--] string
用法很像send,除了它是把string发送给logfile。如果没有logfile打开的话,那么参数将被忽略。
send_tty [-flags] string
用法很像send,除了输出是发往/dev/tty而不是当前进程。
    send_user [-flags] string
    用法很像send,除了输出是发往标准输出stdout而不是当前进程。
    sleep seconds
    使脚本停顿给定的秒数。Seconds是十进制数。在Expect停顿期间,中断(或是在使用Expectk时的Tk事件)也会正常响应。
    spawn [args] program [args]
    创建一个执行program args命令的进程。它的stdin,stdou,stderr(标准输入,标准输出,标准错误输出)都连到Expect。这样它们可以被其他的命令读取 和写入。执行close命令或是进程本身关闭这三个描述符之中的一个都将关闭这个连接。当用spawn开启一个新进程时,这个进程的描述符将赋给变量 spawn_id。Spawn_id指定的进程被认为是当前进程。为了有效的提供作业控制,可能需要读写spawn_id。User_spawn_id是 一个全局变量,它指定了用户的ID,例如:把user_spawn_id赋给spawn_id时,expect将以expect_user方式执行。 Error_spawn_id也是一个全局变量。它包含了指向错误输出的描述符。例如:当spawn_id赋了这个值的话,那么send将以 send_error方式工作。Tty_spwan_id也是一个全局变量,它包含了一个指向/dev/tty的描述符。如果/dev/tty不存在(例 如在一个cron任务,at任务,或是batch脚本中,无人操作),那么tty_spawn_id将不被赋值。可以通过下面的代码得到验证。
        if {[info vars tty_spawn_id]} {
                     # /dev/tty exists
                 } else {
                     # /dev/tty doesn’t exist
            &n

awk + expect 一例

Posted by jiayi | Posted in shell | Posted on 22-10-2008

3

一文件,格式如下:
192.168.1.1    command1
192.168.1.2    command2
192.168.1.3    command3
192.168.1.4    command4
……….

第一列是写主机IP列表,第二列是不同的命令
现在求的脚本要实现这样的功能:
要在一台主控机上分别登陆第一列列出的ip在该台主机上执行对应的command

这个问题不管两台主机之间有没有做过信任,expect + awk 都可以搞定

解:
假设用户root,端口22。
ssh.exp文件:

CODE

#!/usr/bin/expect
set timeout 100
set ip [lindex $argv 0]
set command [lindex $argv 1]
spawn ssh root@$ip
expect "#"
send "$command\r"
expect "#"
send "exit\r"

info.txt文件:

192.168.1.1    echo "haha" > file1.txt
192.168.1.2    echo "haha" > file2.txt

shell下执行

CODE

awk ‘{ip=$1;$1="";command=$0;while("./ssh.exp "ip" \""command"\" " | getline){next}}’ info.txt

说明一点,这里为什么不用shell作循环而选用awk,因为shell作循环效率太差…

Linux 备份之 scp & rsync

Posted by jiayi | Posted in shell | Posted on 12-10-2008

4

最近做了些备份工作,实话说,rsync很好用~ 这里把常用的 scp 和 rsync 的用法一起介绍下

scp的备份:
需求:异地主机建立安全的信任关系,用scp自动完成异地文件的定时copy

1、在A机器上用root运行ssh-keygen,将生成/

root/.ssh/identity和/root/.ssh/identity.pub两个文件;

2、在A机器上运行scp /root/.ssh/identity.pub test@192.168.1.10:/upload/.ssh/authorized_keys(假设B机器的IP地址为192.168.1.10B机器上开了用户test,要上传到的目录是/upload,需输入密码)。目的是将本地的identity.pub放到远程机器上并改名为authorized_keys,这样就建立了新任主机,以后就不需要每次ssh都需要输入密码了;

3、通过crontab建立脚本,例:30 02 * * 1-5 scp /home/data/* test@192.168.1.10:/upload。
(每天2:30am自动将/home/data下面的所有文件以test用户名传到远程主机192.168.1.10的/upload目录中);

认证原理: 
  如果你希望从A作为用户user1 SSH 到B 作为用户user2, 若要用RSA键对法认证且不需要密码,则B上的ssh 后台程序拿出~user2/.ssh/authorized_keys中的与A有关的RSA公钥来对A上的以user1身份运行的ssh客户程序进行挑战,前面我们已经把A上的identity.pub拷贝到B上作为authorized_keys了。则A保持私钥identity,而B上的sshd又拿出A的公钥来挑战,因此成功,你可以加很多行到authorized_keys以允许其它服务器的公钥可以加进来。 

scp特点:
使用ssl加密,安全性高。

rsync的备份指南:
需求:建立异地信任关系 ,用rsync完成异地文件的同步

1、在A主机上(rsync服务器)上编译安装rsync,需要版本在2.4.3以上(http://rsync.samba.org),在/etc目录下建立rsyncd.conf文件,内容如下:
uid = nobody 
gid = nobody 
use chroot = no  # 不使用chroot 
max connections = 4  # 最大连接数为4
log file = /var/log/rsyncd.log 
pid file = /var/run/rsyncd.pid 
lock file = /var/run/rsync.lock  # 日志记录文件

[test]  # 这里是认证的模块名,在客户端需要指定
path = /home/test  # 需要同步的目录
comment = test folder 
uid = root 
ignore errors  # 可以忽略一些无关的IO错误
read only = yes  # 只读
list = no  # 不允许列文件
auth users = rsynctest # 认证的用户名,如果没有这行,则表明是匿名
secrets file = /etc/test.scrt  # 认证用户密码文件

2、在/etc下建立test.scrt文件,输入:
用户名:密码
例:rsynctest:testrsync
将文件属性修改为600(千万注意)

3、启动rsync服务:rsync –daemon (rsync运行在tcp 873端口,可以通过netstat -an|grep LISTEN察看)。

4、在B主机上(rsync客户机)上建立/etc/test文件,内容为A主机的密码,例:
testsync

5、用crontab建立脚本,例:0 21 * * 1-5 rsync -vzrtp –progress –delete –password-file=/etc/test rsynctest@192.168.1.10::test /home/rsynctest 

rsync中的参数:v是verbose,z是压缩,r是recursive,tp都是保持文件原有属性如属主、时间 
的参数。–progress是指显示出详细的进度情况,–delete是指如果服务器端删除了这一文件,那么客户端也相应把文件删除,保持真正的一致。–password-file=/etc/test来指定密码文件,这样就可以在脚本中使 
用而无需交互式地输入验证密码了,这里需要注意的是这份密码文件权限属性要设得只有属主可读(600)。
 
rsynctest@192.168.1.10中,rsynctest是指定密码文件中的用户名,192.168.1.10是A主机的IP地址::test是指模块名[test],也就是在/etc/rsyncd.conf中自定义的名称。最后的/home/rsynctest是备份到本地的目录名。
(也可以用-e ssh的参数建立起加密的连接,然后和scp中信任主机的办法一样如法炮制)
(在上面实例中的rsynctest并不是真实的用户,可以根据自己需要文本定义,这也是使用rsync的一大好处)

rsync的特点:
特性如下: 

1、可以镜像保存整个目录树和文件系统。 
2、可以很容易做到保持原来文件的权限、时间、软硬链接等等。 
3、无须特殊权限即可安装。 
4、优化的流程,文件传输效率高。 
5、可以使用rcp、ssh等方式来传输文件,当然也可以通过直接的socket连接。 
6、支持匿名传输。 

rsync的功能还很强大,深入的眼就可以到官方网站上了解。

2008/02/01至今 Linux下使用最多的10个命令

Posted by jiayi | Posted in shell | Posted on 05-10-2008

7

烦啊,作LFS真烦…冗长的编译测试之余到处乱逛,看到又有人提到使用最多的命令…那些测试最多无非是最近使用的1000个命令的统计,jiayi 从 2008/02/01 开始备份自己使用的命令,总共 36792 条

统计结果有点出乎意料,awk 居然排第一位,sed 也进入top 10…

jiayi:~ # cat -n /mnt/3/backup/history/history* | awk ‘{CMD[$2]++;count++;}END { for (a in CMD)print CMD[a] " " CMD[a]/count*100 "% " a;}’ | grep -v "./" | column -c3 -s " " -t | sort -nr | nl |  head -n10
     1  4633  12.5924%     awk
     2  4181  11.3639%     ls
     3  3682  10.0076%     vi
     4  3674  9.98587%     cd
     5  1060  2.88106%     ..       #上级目录
     6  1055  2.86747%     sed
     7  998   2.71255%     echo
     8  993   2.69896%     gcc
     9  752   2.04392%     exit
    10  597   1.62264%     man
 

苦中作乐,自娱自乐吧

Linux tar 的一些使用例子

Posted by jiayi | Posted in shell | Posted on 26-09-2008

4

1.打包
tar cvf sand.tar sand/
将当前目录的 sand 目录打包为 sand.tar ,但基本没有进行压缩…

tar zcvf sand.tar.gz sand/
将当前目录的 sand 目录用tar打包,用gzip压缩为 sand.tar.gz

tar vjcf sand.tar.gz2 sand/
将当前目录的 sand 目录用tar打包,用bzip2压缩为 sand.tar.gz2

2.解压
tar xvf sand.tar sand/
解压 sand.tar 包

tar zxvf sand.tar.gz sand/
解压 sang.tar.gz 包

tar vjxf sand.tar.gz2 sand/
解压 sang.tar.gz2 包

3.列出 tar 包中的目录
tar tf sand.tar.gz

4.解压tar包中的一个文件
tar zcvf sand.tar.gz sand/jiayi.txt
sand.tar.gz 中将 jiayi.txt 解压出来

5.将比 jiayi.txt 创建晚的文件打包
tar -N ‘jiayi.txt’ -zcvf sand.tar.gz sand/
OR
tar -N ‘2008-09-25′ -zcvf sand.tar.gz sand/

6.将除 lisa.txt 的文件打包
tar –exclude ‘lisa.txt’ -zcvf sand.tar.gz sand/

除了这些,还有一些操作只能作用于没有压缩过得 *.tar 文件,如 从tar包里删除特定文件,将两个tar包合并等,jiayi 认为基本用不到,所以就省了…

Tcl expect 备忘

Posted by jiayi | Posted in shell | Posted on 30-08-2008

0

先说说Tcl种种…


赋值

% set i 1

该用引号括起来:
% set str "test"
str为“test”

要输出一个标量的内容,使用puts语句:
% puts $str

$用来说明str是一个变量。puts函数在标准输出显示变量的内容。

数组也可以用set语句定义,实际上,tcl中建立数组只是单个建立数组的元素。例如 :
% set arr(1) 0
% set arr(2) 1
这样就建立了一个两个元素的数组arr。

在TCL中,不存在相当于数组边界这样的东西 ,例如 :
% set arr(100) ”to“
这时数组中实际只存在arr(1),arr(2)和arr(100),这是和C语言不同的地方。用array size命令可以返回数组的大小:
% array size arr
结果为3

访问数组的方法和访问标量实际是一样的,例如:
% puts $arr(100)
结果为 to

可以用同样的方法创建多维数组。

要使用数组中的所有元素,需要使用一种特殊的遍历方式。首先要启动startsearsh:
% array startsearch arr
这里返回了一个搜索id,你可以把它传递给某个变量,因为以后还要使用它进行进一 步的搜索:
% set my_id [array startsearch arr]
现在my_id的内容是s-1-arr,然后,就可以搜索arr的内容了:
% array nextelement arr $my_id
这里的array nextelement返回的是什么?可能有点出乎你的意料,是arr数组的下标 ,再执行一次array nextelement命令又会找出另外一个下标。

这样遍历下去,可以找出arr数组的所有下标,而知道下标之后,就可以用$arr(100)之类的方式访问arr的内容了。当遍历完成之后,array nextelement命令将简单地返回空:
% array nextelement arr $my_id
这时就可以停止遍历过程了,如果你想确认遍历是否完成,可以使用array anymore命令:
% array anymore arr $my_id
返回0说明遍历已经完成。

串处理
TCL中可以进行一般的串处理过程,这可以使用string命令和append命令,append命令
将某个字符串加到另外一个字符串的后面:
% set str1 "test "
% set str2 "cook it"
% append $str1 $str2 " and other"
test cook it and other

string命令可以执行字符串的比较,删除和查询,其格式是 string [参数] string1 [string2]
参数可以是下面的命令之一:
compare 按照字典顺序对字符串进行比较,根据相对关系返回-1,0或者+1。
first 返回string2中第一次出现string1的位置,如果失败,返回-1。
last 返回string2中最后一次出现string1的位置,如果失败,返回-1
trim 从string1中删除开头和结尾的出现在string2中的字符
trimleft 从string1中删除开头的出现在string2中的字符。
trimright 从string1中删除结尾的出现在string2中的字符

下面几个用在string中的参数不需要string2变量:
length 返回tring1的长度
tolower 返回将string1全部小写化的串
toupper
返回将string1全部大写化的串

运算
TCL的运算方式比较别扭,它使用expr命令作为计算符号,其用法类似C语言的+=和/=,例如:
% set i 5
% set j [expr $i/5]
1

注意TCL会自动选择整数或者浮点计算: E % set l [ expr $i /4.0]
1.25

% set l [ expr $i /4]
1

在TCL里面可以使用+ – * /和%作为基本运算符,另外通常还包括一些数学函数,如abs,sin,cos,exppower(乘方)等等。

另外,还有一个起运算符作用的命令incr,它用来对变量加一:

% set i 1
% incr i
2

流程控制
tcl支持分支和循环。分支语句可以使用ifswitch实现。if语句的和C语言类似,如

CODE

if { $ x < 0 } {
set y 10;
}

注意判断子句也需要使用花括号。

与C语言一样,tcl的if语句也可以使用elseelseif

switch语句的用法有点类似这样:

CODE

switch $x {
0 { set y 10;}
10 { set y 100;}
20 { set y 400;}
}

与C的switch语句不同,每次只有符合分支值的子句才被执行。

循环命令主要由for,foreachwhile构成,而且每一个都可以使用breakcontinue子句。

for语句的格式有点类似这样:
     for { set i 0} {$i < 10} { incr i} {puts $i}
将会输出从1到9的整数。

如果用while循环,这个句子可以写成

CODE

while {$i < 10 } {
puts $i;
incr i;
}
 

foreach是对于集合中的每一个元素执行一次命令,大致的命令格式是

CODE

foreach [变量] { 集合 } {
语句;
}

例如 :

CODE

foreach j { 1 3 5} {
put $j;
}

1
3
5

函数

如同在一般的编程语言里面一样,在tcl里面也可以定义函数,这是通过proc命令实现的:

CODE

proc my_proc {i}{
puts $i;
}

这样就定义了一个名字叫proc的函数,它只是在终端显示输入变元的内容。

要使用这个函数,简单地输入它的名字:
% my_proc { 5 }
5

如果变元的数目是0,只要使用空的变元列表,例如 proc my_proc {} {语句;}

尽管tcl还可以处理更复杂的过程,但是我们不再介绍了,例如文件的读写以及tk图形语言,因为我们处理tcl的主要目标就是理解expect,对于更杂的编程工作,我们建议你使用perl

expect
expect是建立在tcl基础上的一个工具,它用来让一些需要交互的任务自动化地完成。
这里,我们想设置一个自动的文件下载程序。

我们看一看这样的一个例子脚本:

CODE

#!/usr/bin/expect
spawn ftp 202.199.248.11
expect "Name"
send "ftpr\r"
expect "Password:"
send "nothingr\r"
expect "apply"
send "cd /pub/UNIX/Linux/remoteXr\r"
expect "successful."
send "binr\r"
expect "set to I"
send "get exceed5.zipr\r"
expect "complete."
send "quitr\r"

这个是什么意思?呵呵,就是个自动下载程序。第一行说明这个程序应该调用/usr/bin/expect去执行,然后的就是expect命令。

察看expect的手册页面(man expect)可以得到一个很长的expect说明,可惜其中关于expect的语法仍然介绍的不够。一般来说,expect主要用在需要自动执行人机交互的过程中,例如fsck程序,这个程序会不断地提问"yes/no",像这样的命令就可以用expect来完成。

spawn语句在expect脚本中用于启动一个新的进程,在我们的程序中,spawn ftp 202.199.248.11就是去执行ftp程序,接下来,就是expectsend的指令对了。

每一对expectsend指令代表一个信息/回应。如果这样说不好理解的话,那么可以看一看ftp的具体执行过程:

ftp 202.199.248.11
Connected to 202.199.248.11.
220 mail.asnc.edu.cn FTP server (BeroFTPD 1.3.3(3) Sun Feb 20 15:52:49 CST
2000.
Name (202.199.248.11:wanghy):

显然,一旦连接成功,服务器会返回一个Name(202.199.248.11:wanghy):的字符串来要求客户给出用户名。expect语句简单地在返回信息中查询你给出的字符串,一旦成功就执行下面的命令,现在,expect " Name"已经成功地找到了Name字符串,接下来可以执行send命令了。

send命令比expect命令更简单,它简单地向标准输入提交你设定的字符串,现在设置为send "ftpr"表示等到登录信息之后就给出一个输入ftp回车,也就是标准的登录过程。

下面的行与这些行完全一样,只是机械地等待服务器的回应,并且提交自己的输入。

要使用这个expect脚本,你只需要将它设置为可执行的属性,然后执行它,expect就会执行你需要的服务。

由于expect是tcl的扩展,所以你在expect文件中可以象tcl脚本一样设置变量和程序流程。

现在我们看一看我们还能够如何改进我们的expect脚本。ftp命令可能会失败,比如远端的机器可能会无法提供服务,或者在启动ftp命令时本地机器发生问题。为了处理这一类的问题,我们可以使用expect的timeout选项来设置超时的话expect脚本自动退出:

CODE

#!/usr/bin/expect
spawn ftp 202.199.248.11
expect {
timeout exit
Connect
}

注意这里面使用的花括号。它的含义是使用一组并列表达式。使用并列表达式的主要原因是这样:
如果使用下面的指令对:
expect timeout
exit
那么由于expect脚本是顺序执行的,那么当程序执行到这个expect的时候就会阻塞,所以程序会一直等待到timeout然后退出。并列表达式则是相当于switch的行为,只要列出的几项内容有一项得到满足,expect命令就得到满足,于是程序可以正常执行。上面的脚本表示,如果连接ftp的时候发生了超时,那么就退出,否则,一旦发现Connect应答,说明服务器已经正常了,那么就可以继续运行了。

我们可以看看用tcl能够对我们的expect脚本提供什么帮助。我们可以设置让expect脚本不断地连接远端服务器的服务,直到正常建立连接开始,为此,我们可以把建立连接的命令放在一个循环里面,并且根据回应的不同自动选择重新输入命令还是继续执行:

CODE

spawn ftp
while {1} {
expect "ftp>"
send "o 202.199.248.11r"
expect {
"Connected" break
"refused" { sleep 10} ;
}
}
 

这里使用了我们在tcl语言中讲到的whilebreak命令,熟悉C的读者应该很容易看出它的行为:不断地等待ftp>提示符,在提示符下面发送连接远端服务器的命令,如果服务器回应是refused(连接失败),就等待10秒钟,然后开始下一次循环;如果是Connected,那么就跳出循环执行下面的命令。sleep是expect的一个标准命令,表示暂停若干秒钟。

expect还支持许多更复杂的进程控制方式,如forkdisconnect等等,你可以从手册页面中得到详细的信息。另外,各种tcl运算符和流程控制命令,包括tcl函数也可以使用。

有些读者可能会问,如果expect执行的话是否控制台输入不能使用了,答案是否定的。expect命令运行时,如果某个等待的信息没有得到,那么程序会阻塞在相应的expect语句处,这时,你在键盘上输入的东西仍然可以正常地传递到程序中去,其实对于那些expect处理的信息,原则上你输入的内容仍然有效,只是expect的反映太快,总是抢在你的前面“输入”就是了。知道了这一点之后,你就可能写一个expect脚本,让expect
自动处理来自fscki的那些恶心的yes/no选项(我们介绍过,这些yes/no其实完全是多余的,正常情况下你除了选择yes之外什么也干不了)。

缺省下,expect在标准输出(你的终端上)输出所有来自应用程序的回应信息,你可以用下面的两个命令重定向这些信息:
     log_file [文件名]
这个命令让expect在你设置的文件中记录输出信息。必须注意,这个选项并不影响控制台输出信息,不过如果你通过crond设置expect脚本在半夜运行的话,你就确实可能需要这个命令来记录各种信息了。例如:
     log_file expect.log
     log_user 0/1

这个选项设置是否显示输出信息,设置为1时是缺省值,为0 的话,expect将不产生任何输出信息,或者说简单地过滤掉控制台输出。必须记住,如果你用log_user 0关闭了控制台输出,那么你同时也就关闭了对记录文件的输出。

这一点很让人困扰,如果你确实想要记录expect的输出却不想让它在控制台上制造垃圾的话,你可以简单地把expect的输出重定向到/dev/null:

./test.exp > /dev/null

你可以象下面这样使用一对fork和disconnect命令。expect的disconnect命令将使得相应的进程到后台执行,输入和输出被重定向到/dev/null:

CODE

if [fork]!=0 exit
disconnect

fork命令会产生出一个子进程,而且它产生返回值,如果返回的是0,说明这是一个子进程,如果不为0,那么是父进程。因此,执行了fork命令之后,父进程死亡而子进程被disconnect命令放到后台执行。注意disconnect命令只能对子进程使用。

Vim远程编辑空间源代码

Posted by jiayi | Posted in shell, vim | Posted on 28-08-2008

4

各位bloger大都有web功底,而且改改网站代码也是日常之需。这里把jiayi平日使用本地vim远程编辑代码的方法记录下来。

工具:vim(越新越好~),expect,shell。

一般方法:
1.在/home/user目录创建expect文件edit.exp
加入如下代码

CODE

#!/usr/bin/expect
set timeout 100
set file [lindex $argv 0]
spawn vi ftp://192.168.1.1/domains/jiayii.com/public_html/$file #空间代码根目录
expect "username: "
send "your_uname\r" #空间用户名
expect "Password: "
send "your_pswd\r" #空间密码
interact

2.修改权限:

CODE


chmod
u+x edit.exp

3.修改目录
edit.exp拷贝到/sbin目录或其他

CODE

$PATH目录

4.使用
命令行执行 :

CODE

edit.exp index.php

人性化方法:
前两步相同

3.添加 alias
在.bashrc中加入: alias jiayivi=‘/home/user/edit.exp’

4.使用
命令行执行:

CODE

jiayivi index.php

好啦,现在就可以不受任何限制,尽情享受自己的vim了~~~

awk文本处理实例(原创)

Posted by jiayi | Posted in awk | Posted on 22-08-2008

0

awk教程一文中,我介绍了awk的基础知识。现在介绍些awk文本处理的实际例子
例一:
a.txt
    a b c d e f
    1 2 3 4 5 6
现在要变成将最后的一个字段插到第二字段后面,然后其他字段往后移,变为
  a b f c d e
  1 2 6 3 4 5

CODE

awk ‘{$2=$2" "$NF;NF–;print}’ a.txt

NF–用来欺骗print语句

例二:
用下面一列字符串
SUNW,Netra-240
SUNW,Netra-400
SUNW,Netra-800
得到240/400/800

awk解1:

CODE

awk -F"-" ‘{printf $2"/"}END{print ""}’  filename

awk解2:

CODE

awk -F"-" ‘{a=(a=="")?$2:a"/"$2}END{ print a}’ filename

恩,awk可以三目运算

其他解:

CODE

cut -d"-" -f2 filename | xargs | tr " " "/"

速度应该快些

例三:
一个网络接口参数的配置文件,格式如下:
auto eth0
        ip 192.168.0.10
        netmask 255.255.0.0
        gateway 192.168.0.1
auto eth1
        ip 192.168.0.20
        netmask 255.255.0.0
        gateway 192.168.0.1
auto bond0
        ip 10.6.5.56
        netmask 255.255.0.0
        gateway 10.6.0.1
要求解析出bond0的网关

awk解1:

CODE

awk ‘BEGIN{i=-4} $2=="bond0"{i=NR} NR==i+3′

awk解2:

CODE

awk ‘/bond0/{flag=1}flag==1&&/gateway/{print $2;flag=0}’

此法更为通用

例四
把下面一批文件,文件名前六位是200801的合并到一个文件200801
20071228 20071229 20071230 20071231 20080101 20080102 20080103 20080104 20080105 20080106

合并之前需要转换, 上述文件类似这样:
a  a  aaaaaaaa
b  b  bbbbbbbb

最后一个域按定长分割,转换成:
a,a,aaa,aa,aaa
b,b,bbb,bb,bbb

CODE

cat 200801* | awk -vOFS="," ‘{print $1,$2,substr($3,1,3),substr($3,4,2),substr($3,6,3)}’

先合并后处理,与先处理后合并是一样的。只有print 语句用逗号分割或直接print 时,OFS才起作用。

例五:
报表里的数据手机号(2000+),找出全部前7位相同的为异常的号段
数据格式:
13406211154
13407349944
13409810871
13412418614
13412418935
13414598641
13414754454

执行后结果为
13412418614 
13412418935

CODE

awk ‘{a[substr($0,1,7)]=a[substr($0,1,7)]$0" ";}END{for(i in a){if(length(a)>12)print a}}’ filename

例六:

有数据如下:
no
CP_COVER_ID
20080319COVR00850228
no
CP_COVER_ID
20080319COVR00850234

ok
CP_COVER_ID
20080319COVR00850235

ok
CP_COVER_ID
20080319COVR00850248

ok
CP_COVER_ID
20080319COVR00850254

ok
CP_COVER_ID
20080319COVR00850257

no
CP_COVER_ID
20080319COVR00850259

ok
CP_COVER_ID
20080319COVR00850262

no
CP_COVER_ID
20080319COVR00850266
no
CP_COVER_ID
20080319COVR00850267
no
CP_COVER_ID
20080319COVR00850268

ok
CP_COVER_ID
20080319COVR00850276

ok
CP_COVER_ID
20080319COVR00850299

ok
CP_COVER_ID
20080319COVR00850301

ok
CP_COVER_ID
20080319COVR00850302

ok
CP_COVER_ID
20080319COVR00850304

ok
CP_COVER_ID
20080319COVR00850307

ok
CP_COVER_ID
20080319COVR00850308

ok
CP_COVER_ID
20080319COVR00850331

ok
CP_COVER_ID
20080319COVR00850334

ok
CP_COVER_ID
20080319COVR00850336

ok
CP_COVER_ID
20080319COVR00850337

要求滤成如下的格式
no
CP_COVER_ID
20080319COVR00850228
20080319COVR00850234
20080319COVR00850259
20080319COVR00850266
20080319COVR00850267
20080319COVR00850268

ok
CP_COVER_ID
20080319COVR00850235
20080319COVR00850248
20080319COVR00850254
20080319COVR00850257
20080319COVR00850262
20080319COVR00850276
20080319COVR00850299
20080319COVR00850301
20080319COVR00850302
20080319COVR00850304
20080319COVR00850307
20080319COVR00850308
20080319COVR00850331
20080319COVR00850334
20080319COVR00850336
20080319COVR00850337

干拔awk:

CODE

awk ‘/no/{flag="no";next}
     /ok/{flag="ok";next}
     /^[yn0-9]/{if(flag=="no")no=no$0"n";else ok=ok$0"n"}
     END{print "nonCP_COVER_IDn"no"noknCP_COVER_IDn"ok}’

     filename

追求速度,一趟遍历完成。time测试结果:
real    0m0.005s
user    0m0.000s
sys    0m0.004s

sed+awk:

CODE

sed -e ’s/no/n&/;s/ok/n&/’ shu.txt | awk ‘BEGIN {FS="n";RS=""}
    $1 ~ "no" {a[i++]=$3}
    $1 ~"ok" {b[j++]=$3}
    END { print "nonCP_COVER_ID";for (k=0;k<i;k++) print a[k];
               print "oknCP_COVER_ID";for (k=0;k<j;k++) print b[k] }’

sed 将所有的 no,ok前面插入一个回车,保证文件格式如下
no/ok
CP_COVER_ID
number
1 or n个空行
no/ok
CP_COVER_ID
number
…..
让每3个记录用一个或多个空行来分割,方便awk使用RSFS来处理。但是sed多一次遍历,END中多两次循环,所以速度肯定受影响。time测试结果:
real    0m0.014s
user    0m0.008s
sys    0m0.008s

例七:
Ip=192.168.1.1
/dev/sdb1              68G   64G  760M  99% /disk1
/dev/sdc1              68G   63G  1.3G  98% /disk2
Ip=192.168.1.2
/dev/sda3              15G   13G  1.5G  90% /
/dev/sda3              15G   10G  4.2G  71% /
上面是一个检查系统的程序所得结果的部分内容。该部分主要是检查磁盘使用率的信息。现由于需要,要将该部分信息保存成下列字符串的形式
#5$192.168.1.1$[ dev/sdb1][ 99%][/dev/sdc1][ 98%]#
#5$192.168.1.2$[/dev/sda3][ 90%][/dev/sda4][ 71%]#
其中5代表类型#和$,[]都是规定的分隔符

CODE

awk ‘BEGIN{RS="IP=";OFS=""}{$1="#5$"$1"$";$2="["$2"]";$3="[ "$6"]";$4="["$8"]";$5="[ "$12"]#";NF=5;print}’ shu.txt | sed ‘{1d}’
 

RS牛力可见一斑

例八:
jiayi:/mnt/3/pdf/linuxclass/shell/experience/en # nl 1.sh
     1  #!/bin/bash
     2  echo $$
     3  ./2.sh &
执行nl命令后,每行前多出一串空格。现在要将行首空格去掉,并将行号后面的空白合并为一个空格:
1 #!/bin/bash
2 echo $$
3 ./2.sh &

用awk处理So easy!

CODE

nl 1.sh | awk ‘$1=$1′

对比sed繁琐的正则处理:

CODE

nl 1.sh | sed -r ’s/^ +([0-9]+)t/1 /’

呵呵,awk的牛力~
其中$1=$1的奥妙,读者结合我 awk教程一文自行思考

例九:
文本如下:
43 141
31 3
43 111
21 5
92 3
31 52
62 2
43 1
75 9

要求以第一个字段为标,得到如下结果
31 3
31 52
43 1
43 111
43 141

CODE

awk ‘{a[$1]++}END{while(getline<"FILENAME"){if(a[$1]>1) print}}’ filename | sort

此例着重观看getline的用法。getline<"FILENAME"中,FILENAME为awk内置的变量,代表当前处理的文件名。经此句处理,从此文件中读入的每一行都被自动分解为$1 $2 $3$NF,全行$0,相当于awk套awk。
我使用awk的原则是尽量一趟循环搞定,所以没有用管道。

例十:
文本如下:
HAN  1
12 23 34 45
23 45 56
HAN  2
12 23 34 45
23 45 56
12 23 34 45
HAN  3
12 23 34 45
23 45 56 44
12 23 34 45
23 45 56
HAN  4
12 23 34 45
23 45 56
HAN  n

不幸此文本命运不济,被莫名地”拉直“为:
HAN  1 12 23 34 45 23 45 56
HAN  2 12 23 34 45 23 45 56 12 23 34 45
HAN  3 12 23 34 45 23 45 56 44 12 23 34 45 23 45 56
HAN  4 12 23 34 45 23 45 56
HAN  n ……
如何恢复她本来面目?

CODE

awk ‘{for(i=1;i<=NF;i++){if(i%4==3)print "";printf($i" ")}print ""}’ filename

恩,取余运算简化代码

好啦,今天就写这么多吧。
如果处理这些问题不在话下,那谁也不敢说你是awk“新玉米”了~

awk教程(原创)

Posted by jiayi | Posted in awk | Posted on 20-08-2008

1

Linux文本处理工具有两款用的较多,sed&awk。sed是一款流编辑器,而awk是一门脚本语言。从功能上讲,awk可以完全取代sed;从速度上讲,awk一般要慢于sed。jiayi一直是awk的fans,现在就全面的介绍一下awk^_^

概述
awk 是一种编程语言,她是由AT&T 贝尔实验室的Alfred Aho, Peter Weinberger 和Brian Kernighan开发的,Brian Kernighan(此公大家不陌生吧~) 目前仍在维护及增强awk。awk的语法与C类似。

调用
1.awk ‘pattern-action statements’ input_file_list
2.将awk命令插入一个文件,并使awk程序可以执行,然后用awk命令解释器作为脚本首行。
3.将所有awk命令插入一个单独文件
    awk -f awk-script-file inputfile

模式和动作
一个awk 程序是由一系列的"模式-动作"语句构成的:
pattern {action}
pattern {action}
pattern {action}
……

awk 程序为每个输入行依次地进行每一个"模式"的匹配寻找,对每一个匹配上的模式执行相应的"动作",接着读取下一行并再次开始匹配,直到所有的输入都处理完毕。

在一条语句中可以省略"模式"或者"动作",缺省的模式为匹配所有行,缺省的动作为输出当前行:print $0。无论何时,动作都必须用花括号引起来。

awk 从输入中一次读取一行(一条记录),缺省的行分割符(记录分割符)为\n。

然后awk 将记录分割为一个个的字段,缺省的字段分割符为“Blank”(空白)。一行中的第一个字段称为$1,第二个字段称为$2,. . . ,整个记录称为$0。

打印
一个动作可以没有模式,在这种情况下动作在所有行上执行。最简单的动作是打印某些或所有的记录;这可以通过 awk 命令 print 来完成。awk 程序{ print }打印每个记录,也就是把输入完好的复制到输出。更有用的是打印来自每个记录的一个字段或某些字段。例如

CODE

awk ‘{print $2, $1}’ filename

按逆序打印前两个字段。在 print 语句中用逗号分隔的项,在输出的时候会用当前输出字段分隔符分隔开。没有用逗号分隔的项会串联起来,所以

CODE

awk ‘{ print $1 $2 }’ filename

把第一个和第二个字段合在一起。

可以使用预定义的变量 NF 和 NR;例如

CODE

awk ‘{ print NR, NF, $0 }’ filename

打印出前导了记录数和字段数的每个记录。

输出可以被重定向到多个文件中

CODE

awk ‘{ print $1 >"foo1"; print $2 >"foo2" }’ filename

写第一个字段 $1 到文件 foo1 中,写第二个字段到文件 foo2 中。还可以使用 >> 符号:

CODE

awk ‘{ print $1 >>"foo" }’ filename

添加输出到文件 foo。(在每种情况下,输出文件都在必要时建立)。

文件名可以是一个变量或字段,同常量一样;例如

CODE

awk ‘{ print $1 >$2 }’ filename

使用字段 2 的内容作为文件名字。

自然的,有对输出文件数目的限制,目前是 10 个。

awk 还提供 printf 语句用于输出格式化:
    printf format,expr, expr, …
依据在 format 中的规定格式化在列表中的表达式并打印它们。例如,

CODE

awk ‘{ printf "%8.2f %10ld\n", $1, $2 }’ filename

打印 $1 为 8 位宽的小数点后有两位的浮点数,打印 $2 为 10 位长的长十进制数,并跟随着一个换行。不自动生成输出分隔符;你必须自己增加它们,如这个例子那样。这个版本的printf 同于C 所使用的。

输出
1.抽取域

 

CODE

awk -F: ‘{print $1}’ /etc/passwd # -F 指定字段分割符

2.保存输出

 

CODE

awk -F: ‘{print $1}’ /etc/passwd | tee user

awk -F: ‘{print $1}’ /etc/passwd >user

3.使用标准输出

CODE

awk ‘/root/’ /etc/passwd # /xxx/为正则表达式,表示打印包含"root"的行

4.打印所有记录

CODE

awk ‘{print $0}’ /etc/passwd

5.打印表头

CODE

awk -F: ‘BEGIN {print "NAME\n"} {print $1}’ /etc/passwd

6.打印表尾

 

CODE

awk -F: ‘{print $1} END {print "this is all users"}’ /etc/passwd

条件操作符
1.匹配

 

CODE

awk ‘{if($1~/root/) print $0}’ /etc/passwd    #如果field1包含"root",打印该行

2.精确匹配
!=  ==

3.不匹配
!~

4.大小比较
> >= < <=

5.设置大小写

CODE

awk ‘/^[Rr]oot/’ /etc/passwd # 打印包含行首为Root或者root的行

6.任意字符

CODE

awk ‘$2~/^…a/’ /etc/passwd # 打印第二个字段开头第四个字母为t的行

7.或关系匹配

CODE

awk ‘/(root|ftp)/’ /etc/passwd #打印包含"root"或者"ftp"的行

8.AND &&    OR  ||

CODE

awk ‘{$1~/mail/ && $7==/bin/bash}’ /etc/passwd


系统变量:

ARGV 命令行参数数组
ENVIRON 环境变量数组
FILENAME 当前输入文件名
FNR 当前文件中的记录号
FS 字段分隔符
IGNORECASE 忽略正则表达式和串的大小写
NF 当前记录中的字段数
NR 至今读取的记录数
OFMT 数的输出格式,缺省为"%.6g"
OFS 输出字段分隔符
ORS 输出记录分隔符
RS 输入记录分隔符
RSTART 由match() 匹配的第一个字符的索引
RLENGTH 由match() 匹配的串的长度
SUBSEP 下标分隔符,缺省为"�34"
 

内置字符串函数

gsub(r,s,t) 在字符串t中,用字符串s替换和正则表达式r匹配的所有字符串。返回替换的个数。如果没有给出t,缺省为$0
index(s,t)   返回s 中字符串t 的位置,不出现时为0
length(s) 返回字符串s 的长度,当没有给出s时,返回$0的长度
match(s,r) 返回r 在s 中出现的位置,不出现时为0。设置RSTARTRLENGTH的值
split(s,a,r) 利用r 把s 分裂成数组a,返回元素的个数。如果没有给出r,则使用FS。数组分割和字段分割采用同样的方式
sprintf(fmt,expr_list)   根据格式串fmt,返回经过格式编排的expr_list
sub(r,s,t) 在字符串t中用s替换正则表达式t的首次匹配。如果成功则返回1,否则返回0。如果没有给出t,默认为$0
substr(s,p,n) 返回字符串s中从位置p开始最大长度为n的字串。如果没有给出n,返回从p开始剩余的字符串
tolower(s) 将串s 中的大写字母改为小写,返回新串
toupper(s) 将串s 中的小写字母改为大写,返回新串
 

gsub(r,s,t):

CODE

echo ababab | awk ‘gsub(/a/,"c")’ # cbcbcb


sub(r,s,t)

CODE

echo ababab | awk ’sub(/a/,"c")’  # cbabab


其余函数自行尝试。

内置算术函数

cos(x)    返回x的余弦值
sin(x) 返回x的正弦值
int(x) 返回x的整数部分
log(x) 返回x的自然对数
sqrt(x) 返回x的平方根
antan2(x)       返回y/x的反正切,值在 -π到 π之间
rand() 返回随机数r,0 <= r < 1
srand(x) 建立rand()的随机种子,如果没有指定种子,则按当天时间。返回旧的种子
 

cos(x):

CODE
CODE

pai=$(echo "scale=66; a(1)*4" | bc -l)
awk -va=$pai ‘BEGIN{print cos(a/4)}’ OR awk ‘BEGIN{print cos(’$pai‘/4)}’ #0.707107

其余函数自行尝试。     

附算术运算符

x^y x的y次幂
x**y 同上
x%y 计算x/y的余数(求模)
x+y x加y
x-y x减y
x*y x乘y
x/y x除y
-y 负y(y的开关符号);也称一目减
++y y加1后使用y(前置加)
y++ 使用y值后加1(后缀加)
–y y减1后使用y(前置减)
y– 使用后y减1(后缀减)
x=y 将y的值赋给x
x+=y 将x+y的值赋给x
x-=y 将x-y的值赋给x
x*=y 将x*y的值赋给x
x/=y 将x/y的值赋给x x%=y 将x%y的值赋给x
x^=y 将x^y的值赋给x
x**=y 将x**y的值赋给x

bash位运算及shell awk C格式化输出杂烩

Posted by jiayi | Posted in shell | Posted on 10-08-2008

2

先抛一个问题,话说/proc/net/route中的ip地址是低位在前,高位在后的16进制数字,如0101A8C0 ,如何将其转化为192.168.1.1呢?
解决这个问题,先说些无聊的东西…
进制转换,所有的语句实现将DD转换为十进制数
    bash:shell的进制转换方法比较多,这里肯定列不全,只介绍几种常用的

        1. echo $((16#DD))
       
2. echo $((0xDD)) #此二种方法仅限于任意进制到十进制
        3. printf "%d\n" 0xDD
    awk:
        awk ‘BEGIN{printf "%d\n",0xDD}’
   
C:
        printf("%d\n,0xDD);

再补充些内容,数字到字符的转换,所有语句实现将相应数字转换为字符‘~‘
    bash:
        1. echo -e "\0176"
            echo -e "\x7e"  #十进制数到字符的转换方法,至今没有找到
        2. 很抱歉,printf没有找到此功能
    awk:
        awk ‘BEGIN{printf "%c\n",0×7e}’
    C:
        printf("%c\n",0×7E);

回到一开始的问题吧。用上面给的知识,已经足够得到一个像样但并不完美的结果。面对格式化的要求,首先想到用sed将0101A8C0格式化,然后printf转化为10进制,代码如下:
    printf "%d.%d.%d.%d\n" $(echo 0101A8C0 | sed ’s/../ 0x&/g’)
    结果为 1.1.168.192

相信追求完美的你肯定不会对此满意吧。0101A8C0低位在前,高位在后,如何得到192.168.1.1呢?下面的代码应该会让你感到bash的强大。
    a=0×0101A8C0; echo $((a&255)).$((a>>8&255)).$((a>>16&255)).$((a>>24&255))
    结果为 192.168.1.1
挖!原来bash支持位运算~