通常的shell脚本只能将拥有固定输入或输出的应用程序自动化。当遇到passwd这样无法应用管道或输入输出重定向功能的命令时,shell脚本显得无能为力。当面对telnet、ftp这种强依赖与交互输出的程序时,shell脚本能完成的任务更是少的可怜了。

相对于这种需要进行交互式输入输出的场景,expect脚本是常见的一种选择,一来,expect脚本无论从风格还是语法上,还是比较接近shell脚本的。其次expect脚本基于tcl语言,可利用的变成因素也是很丰富完备。

简介

expect不是linux默认安装的工具集,通常需要额外安装。在ubuntu上安装的方法十分简单:

apt-get install expectd

expect依赖与tcl语言,所以apt在安装的过程中会自动帮你安装好tcl程序包,如果是自己手动安装则需要你自己安装好tcl语言环境。

在行为上expect和shell很像。当你执行了expect命令后,实际上进入了expect的执行环境中,在这个环境中你可以使用tcl语言编写你自己的逻辑。

之所以expect可以提供交互式程序的自动化,关键在与spawn命令,expect脚本利用该命令打开一个需要交互的程序,并捕获该程序的所有输入和输出接口,并使用send命令向该程序输入信息,通过expect命令从该程序中捕获输出。

expect脚本基本的思想便是利用expect等待某种特定模式的输出,当该模式被满足时,执行一组操作,提取信息或利用send命令,向被捕获的程序输入内容。仔细比较,该脚本很类似与一个自动聊天机器人,不同的是,聊天的对象是另一个交互式程序。

让你的脚本运行起来

expect脚本有两种运行的模式,一种是作为独立的脚本运行:

文件名:testexpect.exp

#!/usr/bin/expect

spawn telnet 127.0.0.1
expect "*login:*" {
    send "username\r"
}

expect "*Password:" {
    send "password\r"
}
send "exit\r"

另外一种模式是作为shell脚本的一部分,通过“此处文档”的方式标记expect脚本的起始和结束。

#!/bin/sh

expect > /dev/null <<END
spawn telnet 127.0.0.1
expect "*login:*" {
    send "username\r"
}

expect "*Password:" {
    send "password\r"
}
send "exit\r"
END

无论哪种方式,主要思想还是一样的。需要注意的是:

  1. “{”必须位于expect命令同一行上,表明花括弧中的内容同该expect是一体的。
  2. 在send送出的内容往往需要增加一个\r作为结尾,因为大部分的交互式程序都是需要一个换行符作为输入的结尾的。
  3. 当使用shell脚本内嵌expect脚本时,请注意”$”需要使用”\”进行转义,否则将被作为shell中的变量被替换。

expect中的常用命令

spawn

通常每个expect脚本中只有一个spawn命令,但是该命令却是整个expect脚本的关键,正是这个命令启动了一个交互式应用程序,并允许expect脚本同这个程序进行交互。

expect命令后面进跟着需要交互的命令,后面的命令中可以包含全局变量,使用$var_name的方式进行引用。
例如

spawn telnet $ip_addr

expect
不同于启动expect脚本时使用的命令,expect是expect脚本中的一个关键字,表明期待特定的输出并执行一个指定的动作。

当使用了spawn命令捕获了一个程序的输入输出之后,就可以用expect命令进行固定输出内容的捕获。需要注意的是,如果spawn命令没有成功捕获一个交互式程序或者该程序已经退出运行,则expect命令将报错。

expect后面进跟着一个表达式,或者使用-re参数指定一个正则表达式。当该模式被命中时,将触发expect命令后面紧跟着的处理命令。
例如,下面的代码表示,当出现“Password:”这样的提示时,输入password+回车。由于一些命令行交互的程序喜欢在提示字符串后面添加空格,所有expect后面的模式往往使用两个*包围起来,以命中可能的白字符。

expect "*Password:*" {
    send "password\r"
}

一个expect命令可以想switch语句一样,期待多个结果,分别进行处理:

set timeout 10
expect {
    "*login:*     { send "user_name\r"; exp_continue }
    "*Password:*" { send "password\r" }
    timeout       { exit 1 }
}

可以使用”;”来区分一行中的多条命令。也可以使用timeout关键字来指定匹配超时对应的动作,使用set timeout 10可设定超时时间为10秒。
send
和expect从应用程序获取输出相对,send命令可以向应用程序发送一个字符串作为输入。
send命令可以利用变量中的值。例如

send “ping $ip_addr\r”

exit
无论在expect脚本的任何位置,你都可以使用exit命令退出expect脚本。并经exit命令后面紧跟的数字作为脚本的返回值。

其他技巧

变量
expect脚本使用tcl语言作为脚本的实现方式,因此expect脚本中的变量,即tcl语言中的变量。
声明一个变量或者是给一个变量赋值的方法为:

set user_name "admin"

这段代码给变量user_name赋了一个值”admin”。如果变量user_name是第一次出现,则创建了一个新的变量,并赋值为”admin”。

引用变量的方式为$var_name,如果是在嵌入在shell脚本中,则需要注意用”\$”对”$”进行转义。

如果在proc中引用全局变量,则需要在proc开始的地方,使用global var_name的方式进行声明。
proc
在tcl中定义过程的方式就是proc了。

proc do_something { var1 var2 } {
    global g_var
    expect "*something*" {
       send "do_xxx $var1 $var2\r"
       set g_var "ok"
       return 1
    }
    return 0
}

执行proc的方式和普通的命令是一样的。例如上面的命令可以通过下述方式调用:

do_something $var_name1 $var_name2

return命令可以为proc指定返回值。获取该返回值,需要set命令和 []协作完成。

set ret_val [ do_something $var_name1 $var_name2 ] 

expect_out
交互式程序的灵活之处在于根据不同的提示完成不同的操作,提示的内容中往往存在很多变化的内容,如何捕获这些变化的内容并进行处理,则成了处理交互式程序的关键。使用正则表达式和expect_out变量可以很好的完成这个工作。

expect -re "the number (a|b) is (\[0-9\]{1,3})" {
    set number_name  $expect_out(1,string)
    set number_value $expect_out(2,string)
    set the_mached_string $expect_out(buffer)
}

关于expect_out变量,帮助手册中有比较详细的说明,此处仅介绍一种比较使用的应用场景。在使用正则表达是的情况下,$expect_out(1,string)可以将命中正则表达式中第一个括号(第一个分组)中的内容取出来;依此类推,$expect_out(2,string)可以取出第二个分组中的值。$expect_out(buffer)可以取出完整的被命中的字符串。
string
string命令提供了一些tcl中简单的字符串处理函数,不再赘述,有需要的同学可以自行搜索相关内容。
控制流
tcl提供了if、for、while作为控制流程的关键字。此处尽介绍if的用法

if { $var == 1 } {
    send "string1\r"
} elseif { $var == 2 } {
    send "string2\r"
} else {
    send "end\r"
}

注意花括弧的位置。而且tcl中条件部分两侧的是花括弧,不是圆括弧。
其他
本文仅仅介绍了expect和tcl语言的极小一部分。如果有想进一步了解的同学,请参展expect的帮助手册和tcl语言的相关教程。

06. November 2011 · 1 comment · Categories: Other · Tags:

pixelpost是一个开源的图片博客程序,和大部分主流的博客程序一样,基于php和mysql。
这个是我自己搭建的照片博客:流光色影,就是基于pixelpost。

这个程序相当的短小精悍,源代码的压缩包只有600多k,但是功能非常丰富,外观也很专业。风格也和wordpress很相近。
所有动态的内容都采用标签来实现的。所以自己修改起来也不是特别的困难。
你可以到template目录中根据你自己的需要直接修改页面的样式,基本上不了解php也没有关系,只要了解一些html和css的基础知识就可以了。

照片的上传也比较简洁。选择文件,添加说明,“upload”,over。

安装piexlpost的系统要求(翻译自帮助文件):
- 空间主机,域名等等。
- Apache Webserver 或者 Windows IIS。大部分的空间主机应当都是apache吧。
- PHP, version 4.3.0 或者更高
- PHP with GD-lib (with JPG-support), 创建压缩图需要这个。
- MySQL version 3.24.58 或者更高
- 一个已经运行的mysql数据库。而且你需要提供一个可访问的host。这个比较重要,安装的时候需要。

安装步骤:
1、解压缩你下载的安装包,这里有个下载的页面:下载
2、用ftp,将解压后得到的代码上传到你的空间上。一定是一个网络可达的目录。因为你之后还需要用浏览器访问这个目录来安装pixelpost
3、创建pixelpost需要的database和对应的数据用户名,密码。一般cpanel里都能找到主机商提供的添加数据库和用户的链接。
4、用你的浏览器访问安装文件。例如,我的域名是photo.iceshell.net,我已经将这个域名同刚刚我上传代码的路径绑定过了(同样在Cpanel里)。所以我需要访问的网址就是photo.iceshell.net/admin/install.php
5、之后只要安装页面的要求,输入各种信息就行了。主要就是数据库的host和用户名密码。一般数据库的host就是localhost。当然你可以向你的主机空间提供商询问。每一步的安装,都会有对应的检查。输入错误了,立即就会给你提示的。

上传图片需要进入管理界面,一般就是在你的域名后面加上/admin就可以了。

恭喜你,下面就是漫长的折腾开始的时间了!哈哈。

17. July 2011 · Write a comment · Categories: Ubuntu · Tags:

使用了一段时间gnome3之后,感觉很舒服,但是今天更新系统之后,发生了输入法卡顿的现象。
主要的表现是大部分的应用程序是没有问题的,输入法较为正常。但是,当切换为终端,或者输入验证密码的对话框时,会发生严重的卡顿,输入丢字非常严重。
google之,无结果。不过意外中看到一篇博文介绍,gtk3同x-input可能存在一些不兼容的现象。给出的修正方案是重新编译输入法,加入enable-gtk3的选项。
当然了,对于我这种懒人,这太麻烦了,好在搜索以下ibus时,发现了有个ibus-gtk3.安装之,搞定!
兼容性真的很让人头疼

02. February 2011 · Write a comment · Categories: Things · Tags:

享受3g生活,无线精彩无限~

23. November 2010 · 3 comments · Categories: C, Programing · Tags:

今儿说点儿一般书上不说的——内存对齐问题。

一般来说,教材上从来不提这件事情,但是在实际应用的时候往往又无法忽视这个问题。实在想不通,为什么那些著书的“贤人们”为什么都回避这个问题。
最早遇到这个问题,是当年企图用C语言生成位图文件的时候。按照位图的标准规范成功的生成位图之后,居然无法用图片软件打开……经过千辛万苦的调试之后,终于发现,用于生成位图头部的结构体中存在几个bit的空洞。导致了生成的位图头部无效。这几个bit的产生,就要归结为传说中的内存对齐了。从这件事情上,也再次证明,无论你看了多少教材,都是浮云。就好像看了再多遍的辟邪剑谱,你不练,也是得不了神功的。

先来个小例子吧:

#include <stdio.h>

typedef struct {
    char a;
    int b;
    short c;
    float d;
} Node;

int main() {
    printf("%d\n", sizeof (char));  // 1
    printf("%d\n", sizeof (int));   // 4
    printf("%d\n", sizeof (short)); // 2
    printf("%d\n", sizeof (float)); // 4
    printf("%d\n", sizeof (Node));  // 16
    return 0;
}

入门的教材中一般会说,结构体的大小等于组成结构体的个个属性的大小之和。那么让我们看看实际上是怎么样的呢?
上面的这段程序在gcc编译之后,得到的结果是16,而如果简单的把个个属性的大小加在一起,大小应当是1+4+2+4=11
这其中的差距,就在于内存对齐时导致的结构体中存在无法被利用的用于补位的空bit。

原则上,内存的分配应当是对程序员透明的,编译器会替你计算好所有的内存大小及各属性的偏移量。但是,如果你要把这段内存保存到文件或者通过网络发送到别的主机上,这些编译器自作主张的小动作就不能被忽视了。否则你将无法逆向地还原出结构体原本的内容。

当然,编译器不会做无意义的事情,内存对齐是有它特殊的目的的,大体上来说,是为了平台兼容性和执行效率优化。详细情况,稍后再讲。

现来看一看内存对齐的规则:
1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照对齐系数(#pragma pack指定的数值)和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照对齐系数(#pragma pack指定的数值)和结构(或联合)最大数据成员长度中,比较小的那个进行。

上面的规则比较难理解,我个人的理解是对于结构体的任意一个属性来说,如果把该结构体转换成该类型的数组,那么该属性应当正好位于其中某一个数组下标确定的位置上。(如果存在对齐系数,情况会复杂些,超过对齐系数的类型,认为是对齐系数大小的类型的数组)
换一句话说,任何一个属性的起始位置,都应当是其所占内存大小的整数倍。当然,对齐系数提供了另外一个用于参考的虚拟的默认类型大小(实际上大部分的机器上,这个值默认是8)。

用#pragma pack(n)可以指定对齐系数。当该系数为1时,根据前面的规则,将不进行对齐操作,因为任何类型的大小都不会小于1。

#pragma pack(1)
typedef struct {
    char a;
    int b;
    short c;
    float d;
} Node;
#pragma pack()

结构体的大小应当为11(gcc,验证通过)

当对齐系数为2的时候

#pragma pack(2)
typedef struct {
    char a;
    int b;
    short c;
    float d;
} Node;
#pragma pack()

a的大小为1,小于对齐系数(2),offset=0;
b的大小为4,大于2,于是起始位置为2的整倍数对齐,offset=2;
c的大小为2,等于2,于是起始位置为2的整倍数对齐,offset=6;
d的大小为4,大于2,于是起始位置为2的整倍数对齐,offset=8;
借鉴网上另外以为仁兄的写法,内存中的样子应当是这样(x表示对齐用的内存):
0x|0000|00|0000
01|2345|67|8901

一共12byte

看看开始的例子,默认对齐系数为8

#pragma pack(8)
typedef struct {
    char a;
    int b;
    short c;
    float d;
} Node;
#pragma pack()

a的大小为1,小于对齐系数(8),offset=0;
b的大小为4,小于8,于是起始位置为4的整倍数对齐,offset=4;
c的大小为2,小于8,于是起始位置为2的整倍数对齐,offset=8;
d的大小为4,小于8,于是起始位置为4的整倍数对齐,offset=12;
内存中的样子:
0xxx|0000|00xx|0000
0123|4567|8901|2345

一共16byte

好,现在对齐的规则讲完了,可能还是比较难懂,看大家自己理解了。

下面看一看内存对齐的原因:
通常大家的理解,内存是1byte,1byte的,离散的。但是,了解些硬件知识的朋友都应当知道,实际上内存是分块的。而cpu读取内存,必须整块整块地读。
对齐操作,实际上是为了让cpu尽量少的进行内存读写。
因为,如果一个属性所对应的内存,恰好跨越了两块的交界处,要读取这个属性的内容,cpu必须进行两次内存读写。了解“内存墙“概念的人,应当知道,内存读写的速度,和cpu读取缓存的速度差距至少有100倍,无疑cpu会浪费很多时间用于等待内存读写。
至于兼容性?嘿嘿,并不是所有的cpu都愿意读两次内存的……

参考文章:
http://www.cppblog.com/snailcong/archive/2009/03/16/76705.html
http://bigwhite.blogbus.com/logs/2005/08/1347304.html

Switch to our mobile site

site tracking with Asynchronous Google Analytics plugin for Multisite by WordPress Expert at Web Design Jakarta.