使用 gnuplot 动态返回图片

嗯,终于按照我想的方式运行了…

大致的思路就是 perl 作为 CGI 程序调用 gnuplot 作图,动态的返回。动态产生的 HTML 如下:

<img src="/cg-bin/generating-image.pl?arguments">

这里 src 改成一个非图片的 URL,那么这个 CGI 程序必须担当其类似的角色。

我们知道一般 CGI 的 perl 程序,除了 use CGI qw/param/ ; 以外,头上会调用

print <<EOF ;
Content-Type: text/html

EOF

细心的人就知道如果把这个头换成 image/png 之类的,我们后面只需要 print 一个 png 文件的内容就可以了。于是我们需要产生一个 png 之类的图片,这个工具就是 gnuplot,我们知道一般来说画图的代码为

set terminal png transparent ;
set output "filename.png"
plot [x=0:2*pi] sin(x) ;

现在有几个地方我们需要注意,一个是输出,我们需要输出到 STDOUT,不要直接写个 STDOUT 在上面,不会 work 的 -,-b(弱智的我干了半天都没发现),这个直接去掉文件名就对了。

另一个是怎么将数据放在命令里面,一般来说如果是这种函数,gnuplot 会自己选择采样的点进行作图,有时候我们需要的数据散点,则必须用

plot "-" with styles
1 2 3
3 4 5
...
e

这种形式。

最后注意由于我们需要使用 echo 将字符串输出到 STDOUT 然后通过管道送给 gnuplot,所以中间 bash 会处理引号,我们需要在 gnuplot 的程序中对引号进行保护,前面加个 \(程序里面会转意,所以是 \\),然后

print `echo "$gnuplot_cmd" | gnuplot`

即可。

Advertisements
使用 gnuplot 动态返回图片

perl 包管理

perl 的 package 也是互相有依赖关系的,因此提供一个中心集散网站便于用户发布自己的 package 以及声明 package 之间的 dependence 是非常有必要的。CPAN 就是这样一个网站。

对于使用 perl 做开发的人员,如何利用这个资源就是一个非常实用的技术了。多数 perl 的发行版提供一个管理系统,比如 ActivePerl 提供的 ppm 命令。

在 debian 里面,由于多数情况下 perl 的 package 使用 dpkg 打包,依赖关系已经包含,因此在 aptitude/apt-get 系统里面安装某些 package 会自动的安装依赖的包。同时 debian 的 perl 还提供了 CPAN 相关的命令,如 cpan/cpanp 命令可以获得与 CPAN 交互的 shell。

如果用户决定自己安装一些包,而不是系统级别的,那么可以用环境变量 PERL5LIB/PERLLIB 指向对应的路径。比如在用户目录里面安装自己的 package,首先下载对应的 package 源代码,一般都是 CPAN 上面的 tar ball,解开后,用 perl 处理对应的 Makefile.PL 文件,这其实是一个 perl 脚本,通过 perl 的处理产生需要的 Makefile,之后 make 执行编译然后 make install 即可。

我们需要通过修改一些参数使得编译过程和我们预期一致,比如在 Sun 的主机上,perl 本身使用 Sun 的 C 编译器编译,使用 perl 处理 Makefile.PL 的时候,会利用 perl -V 的信息,比如使用 Sun 的编译器,可是 Solaris 上默认没有安装 cc,如果你自己安装了 gcc,可以用 perl Makefile.PL CC=`which gcc` CCCDLFLAGS=”-fPIC” OPTIMIZE=”-O3″ 更换编译器及其编译参数,另外,由于我们需要把目标路径换到 ~/,因此可以在后面加上 PREFIX=~/,这会将对应的库放到 ~/lib/site_perl 中,另外还有 ~/lib/man 中的文档,因此我们的 LIBPERL 应该加入 ~/lib/site_perl 这个目录。

除了修改环境变量外,还可以在源文件 .pl 的开始加上 BEGIN 的部分中添加该路径到 @INC list,这一般用 push 就可以了。

perl 包管理

python 与 perl 的对比学习(Part II)

学习 perl 比较有效的是阅读 perl 的文档,学习 python 的话也有对应的文档。在 debian 里面 perl-doc 含有很多 man page,直接 man 即可,比如 man perlintro,另外有一个 pod 类型的文档,这个一般是书写在 perl 的某些文件里面,然后用 perldoc 提取的,比如我们可以用 perldoc -f chomp 获得内置 perl 函数的帮助,perldoc -L CGI 获得 CGI 这个 module 的帮助。当然这些也有 html 版本。

Python 的话有很详细的一套 pdf 文件,比如有 tut(orial).pdf 等,这点和 R 提供的文档很类似,方便用户打印。python 有方便的交互式命令行,方便用户实验代码;另外 debian 里面的 python3.1-doc 就是对应的 html 版本。另外在命令行上直接用 dir(builtins) 可以获得内置函数的 list,我们可以看见很多和 perl 很像的函数,另外使用 help() 就可以获得在线帮助了。

函数

在 perl 里面定义函数叫 subroutine,使用 sub 定义,一般来说 perl 程序员不写参数列表,直接对输入的 list,也就是 @_ 进行操作,值得注意的是,如果调用的时候是 subroutine( @a, @b) 那么由于 list 放在一起会 flatten,其实 @_ 就是将 @a 和 @b 合并后的 list 了,有两种办法解决这个问题,一个是输入的时候使用 reference,如 subroutine( \@a, \@b),这时 @_ 就是长度为 2 的 list 了,另外一种是后面用的声明 prototype 的方法。一个 sub 可以有返回值,即使用 return 或者最后一个语句是一个 expression 即可。这种模式写变长参数是非常容易的。如果需要定义 prototype,需要知道 $@%&* 表示的意义,如 $ 表示 scalar、@ 表示 list、% 表示 hash,& 表示匿名函数,* 表示 typeglob。如果使用 \ 作为前缀,表示传入 reference,这点很重要,如果调用的时候写的 prototype 是 @$,那么其实 $ 对应的永远是 undef,因为全部都可以放到 list 里面,就算调用的时候写的是 ( @a, $b),$b 也放在 @_ 里面去了,和@a 的元素混在一起,因此需要使用 \@$,这样@_ 包含两个元素,第一个是 @a 的 reference,第二个是 $b。如果用 ; 分开 prototype,则表示后面的是可省略的参数。* 主要是用于处理文件句柄。perl 里面调用函数的括号不是必须的。perl 的匿名函数直接就是 sub 的返回值,这是一个 reference。

python 里面定义函数使用 def func(arg):,不过要注意 python 使用的是可以有参数名字的方式,所以,调用起来和 perl 不大一样。在 python 函数定义的 def 后可以加 “”” 引起来的作为该函数说明的字符串,这在使用 help() 获得该函数的帮助时非常有用,这点和 Matlab 比较相似。python 的参数列表里面可以用 = 表示默认值,如果默认值传递的是变量或者函数,那么默认值是在该处 evaluate 的 expression 的值。但是如果是 mutable 的对象作为默认值,那就是传递的引用,如 list、dictionary。如果需要使用变长参数,可以用 *arg 表示没有 keywords 的参数(对应 arg 是一个 list),用 **keywords 表示带 keywords 的参数,这是一个 dictionary。那么与 perl 不一样的是,perl 处理 fun( @a ) 和处理 fun( $a[0], $a[1] ) 是一样的(如果 @a == 2),而 python 的 list 如果直接作为参数传递是不会展开的,可以用 * 展开。(这点-mutable 对象通过引用传递-我们可以通过写一个简单的测试程序来检查)另外匿名函数 python 里面通过 lambda 表示,即 lambda args: value。

python 的 document string 一般第一行写函数简介,然后空一行,写详细的说明,可以看看一些系统函数的帮助。

作用范围

perl 的变量作用范围有几种标识,如 my、local、state 和使用 {my } 形式产生的几种。如果没有特殊说明,perl 的变量是全局使用的,加了 my 后在当前 scope 以内可以使用,local 主要是提供一些全局变量的临时值(在某个 scope 需要更改全局变量或者某个 hash 或者 list 中部分),state 提供的是类似 C 函数的 static 类型变量。另外一种方式在 {} 的 closure 中使用 my 声明变量,然后在该 closure 定义一个函数就可以使用了(这是 perl 的 GC 特性决定的)。

python 的变量主要通过封装,避免了多数情况下需要显示表示使用变量的 scope 的关键字,默认的情况 python 的变量使用的是最内层的 scope,即第一次使用该变量的 scope(和 perl 的 my 一样),可以用 global 或者nonlocal,前者表示全局变量的声明,比如我们可以用 globals() 返回一个 dictionary 表示全局变量及其值,而后者表示该变量处于上层 scope 中。

前面提到有一类很重要 perl 的变量类型就是 typeglob,我们知道 $a 表示 scalar,@a 是 list 而 %a 是 hash,但事实上 perl 允许存在三个这种变量而互不一样。perl 在某个 scope 里面搜索变量的时候是通过几个 hash 表,我们可以看见这里至少有三个,即 scalar、list 和 hash 使用不同的 hash 记录自己的变量。typeglob 是 * 开头的变量,它表示的意思是比如 *b = *a,那么所有叫 a 的变量都可以用 b 表示,即 $b 就是 $a 了,这个感觉和 reference 比较类似,但是使用更加简单,因为从形式上就是提供了一个别名,也就是说原来写 a 的地方现在换成 b 完全意义不变。那么 local 的 typeglob 就是表达临时改变的意思,而不能使用 my 修饰。另外,使用 typeglob 传递不可以传递 lexical variable(即 my 定义的),而只能传递全局变量。事实上,typeglob 对文件句柄以及、常量进行替换,另外指定引用就会获得 selective aliasing 的效果。一个函数的 reference 可以用 $&funref 执行,如果 *fun = $funref,则可以直接 fun() 调用。可以用 \* 创建 typeglob 的引用,但往往不用。注意 file handle 是 global 的,因此可以用 typeglob 传递。另外,全局的这个符号表可以用 %main:: 或者 %:: 访问,因此 *foo=*bar 等价于 $main::{foo}=$main::{bar},注意此处的 {} 省去就变成了对变量 $foo 和 $bar 的操作了。*{$glob}{PACKAGE} 和 *{$glob}{NAME} 分别可以获得对应的 package 和 name。

模块化设计

perl 的模块一般写作 .pm,我们通常用 use、require 和 do 三个命令读入一个外部的文件,但是读入的方式是不一样的。do 和 eval `cat file.pl` 类似,每次都是读入再解析,因此整体速度慢,如果这个 pl 文件还在变化,这是比较合适的方式,比起山寨的 eval 来说,do 要更快,返回错误也更加方便处理。如果是读入 pm 文件最好用 require 或者 use,这会查询 @INC 里面目录还有当前目录下面对应的 pm 文件,写法是 Subdir::Subdir::…::module,其中的 :: 对应目录结构,最后的 module 对应 module.pm 文件,和 do 不同的是,相同的 do 调用会多次读入该文件并 parse,而这两个不会。这个读入的文件需要返回 1(文件最后会写一个 1 ;)。与 require 不同,use 会调用这个 module 的 import 方法读入需要的 subroutines,这是因为每个 module 在读入后里面的函数需要通过 module::subroutine() 来调用,为了简化,可以继承 Exporter 类在该 module 里面通过 @EXPORT 来定义一个 list 表示可供外部直接调用的函数列表。如果使用了 use strict,这部分(@EXPORT 和 @ISA)需要在 BEGIN{} 中申明,对应的有一个 END{},这是从 awk 里面学来的两个函数。另外还有三个 block,UNIT_CHECK(在每个编译单元完成后执行),CHECK(所有编译完成主程序开始执行前)和 INIT(主程序开始执行的第一个步骤)。

perl 的 module 有一个 package 结构,需要用 package pkgname ; 指明(对应了一个 __PACKAGE__ 变量),且用 _END__ 结束。同样需要返回 1。另外有一套 perl 的文档方式,也就是所谓的 POD,我们会在后面慢慢介绍。

python 的设计更加简洁,因为任意的 py 文件本身就可以作为一个 module,执行这个 py 文件的时候我们注意 __name__ 为 ‘__main__’,否则就是该文件的文件名,这使得我们可以把这个 module 的测试代码放在这个 module 里面,通过直接执行进行测试,而使用 import 使用该 module 里面的函数、类等。如果需要和 use 类似,可以用 from module import fun 的方式读入。

perl 和 python 一般都有一个系统搜索路径用来搜索这些模块,perl 是 @INC,还可以用相关的环境变量指明,多放在 /usr/lib/perl 下面的子目录中,而 python 放在 sys.path 里面,一般会包括 /usr/lib/python* 目录。

python 与 perl 的对比学习(Part II)