最近遇到的一个任务是要将散落在系统各个文件中的 use lib '/opt/mysoft/lib'; 这样的绝对路径引用改成相对路径, 以便整个系统能够轻易地移动到其他的目录中。系统是工作在Apache2 + mod_perl 下的,cgi程序位于 /opt/mysoft/cgi-bin 下。

初看起来似乎很容易:

use lib '../lib';    # 错误!

但实际尝试一下就会发现,这里的 ‘..’ 似乎找错了位置。当前目录并不是cgi程序所在的 /opt/mysoft/cgi-bin。 经过一番实验后,发现在 use lib 时,当前目录居然是在 / 下!这样这条路就走不通了。

另一个想法是通过 $0 获取到CGI程序的路径,再根据此路径来找到 lib 目录不就可以了么?

$dir = dirname $0;
use lib "$dir/../lib";     # 错误!

print "$_\n" for @INC;

实际运行就会发现,这一招也行不通。原因是use lib是在编译时执行的。编译时代码并没有被执行, 因此 $dir 变量为空(甚至都不存在),于是就成了 use lib "/../lib",当然不正确了。 这样看来 use lib 似乎必须要书写完整路径了。

CPAN文档的解释如下:

lib - manipulate @INC at compile time

实际上 use lib 可以理解为 BEGIN { unshift @INC, '/opt/mysoft/lib'; }。 BEGIN段的内容在这一段编译结束后立即执行。

回到本来的问题上,如何能避免在系统中使用绝对路径? 其实使用mod_perl的功能一次性地指定好@INC,那么在CGI和pm模块中就无需再use lib了。

在mod_perl的官方文档中有关于如何调整@INC的说明。

一种方法是在 startup.pl 中书写完整路径:

use lib '/opt/mysoft/lib';

另一种方法是直接在httpd.conf中修改@INC

PerlSwitches -I/opt/mysoft/lib

这样虽然不能完全避免绝对路径,但至少在产品代码中可以不再使用 use lib 了。


同样地,use 也是在编译时执行的。(参考)use Module;相当于 BEGIN { require Module; Module->import( LIST ); }

程序代码中的所有use指令都会在代码执行前被执行,因此use不论放在哪里,效果都是一样的。因此 if ($use_first_lib) { use FirstLib; … } else { use SecondLib; … }

这样的代码是毫无用处的,不论$use_first_lib的值如何,FirstLib和SecondLib都会被加载。