这是 perlfilterp 命令,可以使用我们的多个免费在线工作站之一在 OnWorks 免费托管服务提供商中运行,例如 Ubuntu Online、Fedora Online、Windows 在线模拟器或 MAC OS 在线模拟器
程序:
您的姓名
perlfilter - 源过滤器
商品描述
这篇文章是关于 Perl 一个鲜为人知的特性,叫做 资源 过滤器. 源过滤器
在 Perl 看到之前改变模块的程序文本,就像 C 预处理器改变
在编译器看到之前的 C 程序的源文本。 这篇文章告诉你更多
关于源过滤器是什么、它们如何工作以及如何编写自己的过滤器。
源过滤器的最初目的是让您将程序源加密到
防止随意盗版。 这不是他们能做的全部,您很快就会了解到。 但首先,
基本。
概念
在 Perl 解释器可以执行 Perl 脚本之前,它必须首先从文件中读取它
存入内存进行解析和编译。 如果该脚本本身包含其他脚本
“use”或“require”语句,那么每个脚本都必须从它们的
相应的文件也是如此。
现在将 Perl 解析器和单个文件之间的每个逻辑连接视为一个
资源 流. 当 Perl 解析器打开一个文件时会创建一个源流,它会继续
在源代码被读入内存时存在,并在 Perl 完成时销毁
解析文件。 如果解析器在源代码中遇到“require”或“use”语句
流,为该文件创建一个新的和不同的流。
下图表示单个源流,其中源流来自 Perl
将左侧的脚本文件放入右侧的 Perl 解析器中。 这是 Perl 通常的方式
运作。
文件 -------> 解析器
有两个重要的点需要记住:
1. 尽管在任何给定时间可能存在任意数量的源流,
只有一个是活跃的。
2. 每个源流只与一个文件相关联。
源过滤器是一种特殊的 Perl 模块,它拦截和修改源
流到达解析器之前。 源过滤器像这样改变我们的图表:
文件----> 过滤器----> 解析器
如果这没有多大意义,请考虑类比命令管道。 说你有
存储在压缩文件中的 shell 脚本 试用版. 下面的简单管道命令
运行脚本而无需创建临时文件来保存未压缩的文件。
gunzip -c trial.gz | 嘘
在这种情况下,来自管道的数据流可以表示如下:
trial.gz ----> gunzip ----> sh
使用源过滤器,您可以存储压缩的脚本文本并使用源
过滤器为 Perl 的解析器解压缩它:
压缩枪
Perl 程序 ---> 源过滤器 ---> 解析器
使用 滤波器
那么如何在 Perl 脚本中使用源过滤器呢? 上面,我说一个源过滤器是
只是一种特殊的模块。 像所有 Perl 模块一样,源过滤器通过
使用声明。
假设您想在执行之前通过 C 预处理器传递 Perl 源代码。 作为它
发生时,源过滤器分发带有一个 C 预处理器过滤器模块,称为
过滤器::cpp。
下面是一个示例程序“cpp_test”,它使用了这个过滤器。 行号
已添加以允许轻松引用特定行。
1:使用过滤器::cpp;
2:#define TRUE 1
3:$a = 真;
4: 打印 "a = $a\n";
当您执行此脚本时,Perl 会为该文件创建一个源流。 在解析器之前
处理文件中的任何行,源流如下所示:
cpp_test ---------> 解析器
第 1 行,“use Filter::cpp”,包括并安装“cpp”过滤器模块。 全部来源
过滤器以这种方式工作。 use语句在编译时编译执行,之前
读取更多文件,并将 cpp 过滤器附加到后面的源流
场景。 现在数据流看起来像这样:
cpp_test ----> cpp 过滤器 ----> 解析器
当解析器从源流中读取第二行和后续行时,它会提供那些
在处理它们之前通过“cpp”源过滤器。 “cpp”过滤器简单
将每一行通过真正的 C 预处理器。 C 预处理器的输出是
然后由过滤器插入回源流。
.-> cpp --.
| |
| |
| <-'
cpp_test ----> cpp 过滤器 ----> 解析器
然后解析器会看到以下代码:
使用过滤器::cpp;
$ a = 1;
打印 "a = $a\n";
让我们考虑当过滤后的代码包含另一个使用的模块时会发生什么:
1:使用过滤器::cpp;
2:#define TRUE 1
3:使用弗雷德;
4:$a = 真;
5: 打印 "a = $a\n";
“cpp”过滤器不适用于 Fred 模块的文本,仅适用于
使用它的文件(“cpp_test”)。 虽然第 3 行的 use 语句会通过
cpp 过滤器,被包含的模块(“Fred”)不会。 源流看起来像
在第 3 行被解析之后和在第 4 行被解析之前:
cpp_test ---> cpp 过滤器 ---> 解析器(未激活)
Fred.pm ----> 解析器
如您所见,已经创建了一个新流,用于从“Fred.pm”读取源代码。 这个
流将保持活动状态,直到所有“Fred.pm”都被解析。 源流为
“cpp_test”将仍然存在,但处于非活动状态。 一旦解析器完成读取
Fred.pm,与之关联的源流将被销毁。 源流为
“cpp_test”然后再次变为活动状态,解析器从
“cpp_test”。
您可以对单个文件使用多个源过滤器。 同样,您可以重用
在任意数量的文件中使用相同的过滤器。
例如,如果您有一个 uuencoded 和压缩的源文件,则可以堆叠一个
uudecode 过滤器和解压缩过滤器,如下所示:
使用过滤器::uudecode; 使用过滤器::解压缩;
M'XL(".H<US4''V9I;F%L')Q;>7/;1I;_>_I3=&E=%:F*I"T?22Q/
M6]9*
...
处理完第一行后,流程将如下所示:
文件 ---> uudecode ---> 解压缩 ---> 解析器
过滤器过滤器
数据流经过滤器的顺序与它们在源文件中出现的顺序相同。 uudecode
过滤器出现在解压缩过滤器之前,因此源文件将在之前进行 uudecoded
它是未压缩的。
写作 A 源 FILTER
可以通过三种方式编写自己的源过滤器。 你可以用 C 编写它,使用一个
外部程序作为过滤器,或者用 Perl 编写过滤器。 我不会介绍前两个
任何伟大的细节,所以我会先把它们弄出来。 用 Perl 编写过滤器是
最方便,所以我会花最多的篇幅。
写作 A 源 FILTER IN C
三种可用技术中的第一种是完全用 C 编写过滤器。
外部模块,您直接使用提供的源过滤器钩子创建接口
珀尔。
这种技术的优点是您可以完全控制实现
你的过滤器。 最大的缺点是增加了编写
filter - 你不仅需要了解源过滤器钩子,还需要一个
Perl 胆量的合理知识。 值得去这个麻烦的少数几次之一
是在编写源加扰器时。 “解密”过滤器(它解密源
在 Perl 解析它之前)包含在源过滤器分发中是 C 的一个例子
源过滤器(请参阅下面的解密过滤器)。
解密 筛选
所有解密过滤器的工作原理都是“通过隐匿实现安全”。
不管你写的解密过滤器有多好,你的加密有多强
算法是,任何有足够决心的人都可以检索原始源代码。 这
原因很简单——一旦解密过滤器将源解密回
它的原始形式,它的片段将作为 Perl 存储在计算机的内存中
解析它。 源可能只在内存中很短的一段时间,但任何人
拥有调试器、技能和大量耐心最终可以重建您的
程序。
也就是说,可以采取许多措施来使生活变得困难
潜在的饼干。 最重要的是:用 C 编写你的解密过滤器和
将解密模块静态链接到 Perl 二进制文件中。 如需进一步的提示
潜在的破解者生活艰难,请参阅文件 解密文件 在源
过滤器分布。
创作 A 源 FILTER AS A 分离 可执行
用 C 编写过滤器的另一种方法是在
您选择的语言。 单独的可执行文件从标准输入中读取,执行任何操作
处理是必要的,并将过滤后的数据写入标准输出。 “过滤器:: cpp”是
作为单独的可执行文件实现的源过滤器的示例 - 可执行文件是
C 预处理器与您的 C 编译器捆绑在一起。
源过滤器分发包括两个简化此任务的模块:
“过滤器:: exec”和“过滤器:: sh”。 两者都允许您运行任何外部可执行文件。 两者都使用一个
协处理来控制数据流入和流出外部可执行文件。 (为了
有关协进程的详细信息,请参阅 Stephens, WR, "Advanced Programming in the UNIX
环境。”Addison-Wesley,ISBN 0-210-56317-7,第 441-445 页。)
它们是“Filter::exec”直接生成外部命令,而“Filter::sh”
产生一个 shell 来执行外部命令。 (Unix 使用 Bourne shell;NT 使用
cmd shell。)生成一个 shell 允许您使用 shell 元字符和
重定向设施。
这是一个使用“Filter::sh”的示例脚本:
使用 Filter::sh 'tr XYZ PQR';
$ a = 1;
打印 "XYZ a = $a\n";
执行脚本时您将获得的输出:
PQR = 1
将源过滤器编写为单独的可执行文件工作正常,但性能较差
产生罚款。 例如,如果你执行上面的小例子,一个单独的
将创建子进程以运行 Unix "tr" 命令。 过滤器的每次使用都需要
它自己的子进程。 如果在您的系统上创建子进程的成本很高,您可能需要
考虑创建源过滤器的其他选项之一。
写作 A 源 FILTER IN PERL
可用于创建自己的源过滤器的最简单、最便携的选项是
完全用 Perl 编写。 为了将其与前两种技术区分开来,我将
称之为 Perl 源过滤器。
为了帮助理解如何编写 Perl 源过滤器,我们需要一个例子来学习。 这是
执行 rot13 解码的完整源过滤器。 (Rot13 是一个非常简单的加密
Usenet 帖子中使用的方案来隐藏攻击性帖子的内容。 它每移动一次
字母向前十三位,使 A 变成 N,B 变成 O,Z 变成 M。)
包 Rot13;
使用 Filter::Util::Call;
子进口{
我的 ($type) = @_;
我的 ($ref) = [];
filter_add(祝福 $ref);
}
子过滤器{
我的 ($self) = @_;
我的 ($status);
tr/n-za-mN-ZA-M/a-zA-Z/
如果 ($status = filter_read()) > 0;
$状态;
}
1;
所有 Perl 源过滤器都实现为 Perl 类并且具有相同的基本结构
如上面的例子。
首先,我们包含了“Filter::Util::Call”模块,它导出了许多函数
进入过滤器的命名空间。 上面显示的过滤器使用其中两个函数,
“filter_add()”和“filter_read()”。
接下来,我们创建过滤器对象并将其与源流相关联,方法是定义
“导入”功能。 如果你足够了解 Perl,你就会知道“import”被称为
每次模块包含在 use 语句中时自动。 这使得“导入”
创建和安装过滤器对象的理想场所。
在示例过滤器中,对象 ($ref) 就像任何其他 Perl 对象一样受到祝福。 我们的
示例使用匿名数组,但这不是必需的。 因为这个例子
不需要存储任何上下文信息,我们可以使用标量或哈希
参考也一样。 下一节演示上下文数据。
过滤器对象和源流之间的关联是通过
“filter_add()”函数。 这将过滤器对象作为参数(在这种情况下为 $ref)和
将其安装在源流中。
最后,还有实际执行过滤的代码。 对于这种类型的 Perl 源
filter,所有的过滤都是在一个叫做“filter()”的方法中完成的。 (也可以
使用闭包编写 Perl 源过滤器。 请参阅“Filter::Util::Call”手册页了解
更多细节。)每当 Perl 解析器需要另一行源代码时,它就会被调用
过程。 反过来,“filter()”方法使用
“filter_read()”函数。
如果源流中有一行可用,“filter_read()”返回一个状态值
大于零并将该行附加到 $_。 状态值为零表示结束
文件,小于零表示错误。 过滤器函数本身应该返回它的
status 以同样的方式,并将它想要写入源流的过滤行放入
$_。 $_ 的使用说明了大多数 Perl 源过滤器的简洁性。
为了使用 rot13 过滤器,我们需要某种方式对源文件进行编码
rot13 格式。 下面的脚本“mkrot13”就是这样做的。
除非@ARGV,否则“使用 mkrot13 文件名\n”;
我的 $in = $ARGV[0];
我的 $out = "$in.tmp";
open(IN, "<$in") or die "Cannot open file $in: $!\n";
open(OUT, ">$out") 或死“无法打开文件 $out: $!\n”;
打印出“使用 Rot13;\n”;
尽管 ( ){
tr/a-zA-Z/n-za-mN-ZA-M/;
打印;
}
逼近;
关闭
取消链接 $in;
重命名 $out, $in;
如果我们用“mkrot13”加密:
打印“你好弗雷德\n”;
结果将是这样的:
使用 Rot13;
cevag "uryyb serq\a";
运行它会产生以下输出:
你好弗雷德
使用 背景: “ DEBUG FILTER
rot13 示例是一个微不足道的示例。 这是另一个演示,展示了一些
更多功能。
假设您想在开发过程中在 Perl 脚本中包含大量调试代码,
但您不希望它在已发布的产品中可用。 源过滤器提供了一种解决方案。
为了使示例保持简单,假设您希望调试输出为
由环境变量“DEBUG”控制。 如果变量是启用调试代码
存在,否则禁用。
两个特殊的标记行将调试代码括起来,如下所示:
## 调试开始
如果($年> 1999){
警告“调试:$year 年的千年错误\n”;
}
##调试_END
过滤器确保 Perl 解析和“DEBUG_END”
仅当存在“DEBUG”环境变量时才标记。 这意味着当“调试”
确实存在,上面的代码应该不变地通过过滤器。 标记线
也可以按原样传递,因为 Perl 解析器会将它们视为注释行。
当未设置“DEBUG”时,我们需要一种禁用调试代码的方法。 一个简单的实现方式
也就是将两个标记之间的行转换为注释:
## 调试开始
#if ($year > 1999) {
# 警告“调试:$year 年的千年错误\n”;
#}
##调试_END
这是完整的调试过滤器:
包调试;
用严格;
使用警告;
使用 Filter::Util::Call;
使用常量 TRUE => 1;
使用常量 FALSE => 0;
子进口{
我的 ($type) = @_;
我的(%上下文)=(
启用 => 定义 $ENV{DEBUG},
InTraceBlock => FALSE,
文件名 => (调用者)[1],
行号 => 0,
最后开始 => 0,
);
filter_add(祝福 \%context);
}
子模{
我的 ($self) = shift;
我的 ($message) = shift;
我的 ($line_no) = shift || $self->{LastBegin};
die "$message at $self->{Filename} line $line_no.\n"
}
子过滤器{
我的 ($self) = @_;
我的 ($status);
$status = filter_read();
++ $self->{LineNo};
# 先处理EOF/error
如果($状态<= 0){
$self->Die("DEBUG_BEGIN 没有 DEBUG_END")
如果 $self->{InTraceBlock};
返回 $status;
}
如果($self->{InTraceBlock}){
如果 (/^\s*##\s*DEBUG_BEGIN/ ) {
$self->Die("Nested DEBUG_BEGIN", $self->{LineNo})
} elsif (/^\s*##\s*DEBUG_END/) {
$self->{InTraceBlock} = FALSE;
}
# 当过滤器被禁用时,注释掉调试行
s/^/#/ 如果 ! $self->{启用};
} elsif ( /^\s*##\s*DEBUG_BEGIN/ ) {
$self->{InTraceBlock} = TRUE;
$self->{LastBegin} = $self->{LineNo};
} elsif ( /^\s*##\s*DEBUG_END/ ) {
$self->Die("DEBUG_END 没有 DEBUG_BEGIN", $self->{LineNo});
}
返回 $status;
}
1;
这个过滤器和前面的例子最大的区别是上下文数据的使用
在过滤器对象中。 过滤器对象基于散列引用,用于保持
调用过滤器函数之间的各种上下文信息。 除了两个之外的所有
散列字段用于错误报告。 这两个中的第一个 Enabled 被使用
过滤器以确定是否应将调试代码提供给 Perl 解析器。 这
第二,InTraceBlock,当过滤器遇到“DEBUG_BEGIN”行时为真,但
尚未遇到以下“DEBUG_END”行。
如果忽略大多数代码所做的所有错误检查,过滤器的本质
如下:
子过滤器{
我的 ($self) = @_;
我的 ($status);
$status = filter_read();
# 先处理EOF/error
如果 $status <= 0,则返回 $status;
如果($self->{InTraceBlock}){
如果 (/^\s*##\s*DEBUG_END/) {
$self->{InTraceBlock} = FALSE
}
# 当过滤器被禁用时,注释掉调试行
s/^/#/ 如果 ! $self->{启用};
} elsif ( /^\s*##\s*DEBUG_BEGIN/ ) {
$self->{InTraceBlock} = TRUE;
}
返回 $status;
}
请注意:正如 C 预处理器不了解 C 一样,调试过滤器也不了解 Perl。
它很容易被愚弄:
打印 <
##DEBUG_BEGIN
EOM
撇开这些事情不谈,你可以看到,用适量的代码可以实现很多目标。
结论
您现在对源过滤器是什么有了更好的了解,您甚至可能有一个
对他们的可能用途。 如果您想使用源过滤器但需要一些
灵感,这里有一些您可以添加到调试过滤器的额外功能。
首先,一个简单的。 与其调试全有或全无的代码,不如
能够控制获得哪些特定的调试代码块更有用
包括。 尝试扩展调试块的语法以允许识别每个块。 这
然后可以使用“DEBUG”环境变量的内容来控制获取哪些块
包括在内。
一旦您可以识别单个块,请尝试允许它们嵌套。 那不是
也难。
这是一个不涉及调试过滤器的有趣想法。 目前 Perl
子程序对形式参数列表的支持相当有限。 您可以指定
参数数量及其类型,但您仍然必须手动将它们从
@_ 数组自己。 编写一个源过滤器,允许您拥有一个命名参数列表。
这样的过滤器会变成这样:
子 MySub ($first, $second, @rest) { ... }
进入这个:
子我的子($$@){
我的 ($first) = shift;
我的 ($second) = shift;
我的 (@rest) = @_;
...
}
最后,如果您觉得自己是个真正的挑战,请尝试编写一个完整的 Perl 宏
预处理器作为源过滤器。 借用 C 预处理器的有用特性,并
您知道的任何其他宏处理器。 棘手的一点是选择多少知识
您希望过滤器具有的 Perl 语法。
限制
源过滤器仅适用于字符串级别,因此其能力非常有限
动态更改源代码。 它无法检测注释、带引号的字符串、heredocs,它是
无法替代真正的解析器。 源过滤器唯一稳定的用法是
加密、压缩或字节加载器,将二进制代码转换回源代码。
参见例如 Switch 中的限制,它使用源过滤器,因此不
在字符串 eval 中工作,存在带有嵌入换行符的正则表达式
用原始的“/.../”分隔符指定并且没有修饰符“//x”是
与以除法运算符“/”开头的代码块无法区分。 作为一个
解决方法您必须使用“m/.../”或“m?...?” 对于这样的模式。 此外,存在
用原始“?...?”指定的正则表达式分隔符可能会导致神秘的错误。 解决方法
是使用“m?...?” 反而。 看http://search.cpan.org/perldoc?开关#限制>
当前“__DATA__”块的内容没有被过滤。
当前内部缓冲区长度仅限于 32 位。
事物 TO 看 OUT 用于
一些过滤器破坏了“DATA”句柄
一些源过滤器使用“DATA”句柄来读取调用程序。 使用时
这些源过滤器你不能依赖这个句柄,也不能指望任何特定的类型
对其进行操作时的行为。 基于 Filter::Util::Call 的过滤器(因此
Filter::Simple) 不改变“DATA”文件句柄,但另一方面完全
忽略“__DATA__”之后的文本。
参赛要件
源过滤器分发在 CPAN 上可用,在
CPAN/模块/按模块/过滤器
从 Perl 5.8 Filter::Util::Call(源过滤器的核心部分)开始
发行版)是标准 Perl 发行版的一部分。 还包括一个更友好的
Damian Conway 称为 Filter::Simple 的接口。
使用 onworks.net 服务在线使用 perlfilterp