Perl: CGI和DBI模块中的变量作用域问题

3

我遇到了一个似乎是变量作用域问题,这是我以前没有遇到过的。我正在使用Perl的CGI模块和对DBI的do()方法的调用。这是简化后的代码结构:

use DBI;
use CGI qw(:cgi-lib);
&ReadParse;
my $dbh = DBI->connect(...............);
my $test = $in{test};
$dbh->do(qq{INSERT INTO events VALUES (?,?,?)},undef,$in{test},"$in{test}",$test);

#1 占位符变量的值似乎未初始化。其他两个占位符变量可用。

问题:为什么在do()语句中无法使用%in哈希,除非我将其用双引号括起来(#2占位符)或将其重新分配给新变量(#3占位符)?

我认为这与CGI模块的ReadParse()函数如何将作用域分配给%in哈希有关,但我不了解Perl作用域的情况,无法理解为什么%in可以从顶层访问,但无法从我的do()语句中访问。

如果有人了解作用域问题,是否有更好的处理方法?将所有%in引用都用双引号括起来似乎有点混乱。为每个查询参数创建新变量是不现实的。

需要明确的是,我的问题是关于变量作用域问题的。我知道ReadParse()不是使用CGI获取查询参数的推荐方法。

我正在使用Perl 5.8.8、CGI 3.20和DBI 1.52。提前感谢阅读此内容的任何人。

@Pi和@Bob,感谢你们的建议。预先声明%in的范围没有效果(而且我总是使用strict)。结果与以前相同:在数据库中,col1为null,而cols 2和3设置为预期值。

供参考,以下是ReadParse函数(见下文)。它是CGI.pm的标准函数。我理解的方式是,除了满足strict之外,我不需要初始化%in哈希表来设置作用域,因为该函数似乎已经处理了这个问题:

sub ReadParse {
    local(*in);
    if (@_) {
      *in = $_[0];
    } else {
    my $pkg = caller();
      *in=*{"${pkg}::in"};
    }
    tie(%in,CGI);
    return scalar(keys %in);
}

我想知道在do()的上下文中获取%in哈希表的最佳方式是什么?再次感谢!我希望这是提供原始问题附加信息的正确方式。
@Dan: 我听到你关于&ReadParse语法的意见。通常我会使用CGI::ReadParse(),但在这种情况下,我认为最好按照CGI.pm文档的要求来做。

在你调用do函数的上下文中声明它为my。你也没有将其分配给任何东西。为什么你只需要一个哈希,却使用了*in?那是一个非常复杂的子程序。也许我可以与你合作,以清理或更好地理解你的需求。你能展示一下你调用这个子程序的地方吗? - Bob_Gneu
12个回答

4

根据文档描述,您似乎并没有按照要求使用它:

https://metacpan.org/pod/CGI#COMPATIBILITY-WITH-CGI-LIB.PL

如果必须使用它,则似乎CGI::ReadParse();更为合理且语法更简洁。虽然在这种情况下我看不出有什么区别,但它是一个绑定变量,所以谁知道它在做什么呢 ;)

您不能使用更常见的$cgi->param('foo')语法吗?它更加简洁,并且以一种更可预测的方式污染了您的命名空间。


我正在更新遗留代码,ReadParse / %in 这些东西肯定要取消 :)只是因为有一些关于变量作用域的基本概念我不太清楚.. 另外,我尝试了CGI::ReadParse(); .. 没有区别,但感谢你指出这个。 - Wick
关于不使用 $query->param() 语法,我在原帖中已经提到了。虽然这样可以绕过问题,但并不能解释发生了什么,这正是我的问题所在。无论代码好坏,我都想理解正在发生的事情,以便在其他情况下不再出现类似问题。 - Wick
真的很遗憾这个答案被顶起来了,因为1)函数调用语法的差异不是问题,2)我的问题明确地不是关于获取查询参数的最佳方法。完全没有冒犯丹的意思 - 只是希望得到更多相关的答案。 - Wick

3

use strict;。始终如此。

尝试声明

our %in;

如果这样不起作用,可以尝试使用strict模式以获得更有用的错误提示。


这对于初学者程序员来说是很好的建议,但我已经超越了那个阶段。我的错,我在问题中没有清楚表明“简化一点”意味着我省略了严格编译指示所需的基本声明。 - Wick

3
我不知道出了什么问题,但我可以告诉你一些不是问题的事情:
  • 这不是作用域问题。如果是的话,则 $in{test} 的所有实例都不会起作用。
  • 这不是过时的 & 调用语法。(虽然它不是“正确”的,但在这种情况下无害。)
ReadParse 是个很糟糕的代码。它混淆符号表以在调用包中创建全局变量 %in。更糟糕的是,它是一个绑定变量,因此访问它可能(理论上)做任何事情。查看 CGI.pm 的源代码,FETCH 方法只是调用 params() 方法获取数据。我不知道为什么在 $dbh->do() 中的获取没有起作用。

我还没有获得提升您的答案的能力,否则我会这样做,它似乎是目前为止最接近的。如果没有其他人能够添加任何内容,我会稍等一下并将其选为答案。再次感谢您的时间,我很感激。 - Wick

2

从您提供的示例来看,这不是作用域问题,否则没有一个参数会起作用。

看起来像是DBI(或DBD,不确定绑定参数在哪里使用)没有遵守绑定魔法。 解决方法是将传递给它的内容字符串化或复制,就像第二个和第三个参数所做的那样。

使用SQLite和DBI 1.53进行的简单测试显示它可以正常工作:

$ perl -MDBI -we'sub TIEHASH { bless {} } sub FETCH { "42" } tie %x, "main" or die; my $dbh = DBI->connect("dbi:SQLite:dbname=dbfile","",""); $dbh->do("create table foo (bar char(80))"); $dbh->do("insert into foo values (?)", undef, $x{foo}); print "got: " . $dbh->selectrow_array("select bar from foo") . "\n"; $dbh->do("drop table foo")'
got: 42

您使用哪个数据库?


2

首先,这不是do的上下文/范围。它仍然在主要或全局的上下文中。只有在与Perl中的子例程或不同的“类”相关的 {} 中输入时,您才会离开上下文。在()括号内,您没有离开范围。

您提供的示例未初始化哈希表。正如Pi所建议的那样,使用strict肯定会避免发生这些情况。

您能否给我们一个更具代表性的代码示例?您在哪里设置%IN,以及如何设置?


2

那里出了些严重的问题。Perl的作用域相对来说很简单,除非你不理智地做一些事情,否则你不太可能遇到像那样奇怪的问题。如建议所述,开启严格的编译指示(pragma)(还有警告,事实上,你应该同时使用两者)。

如果没有看到%in是如何定义的(它是否与那个看起来很糟糕的ReadParse调用有关?顺便问一下,为什么你要用前导&调用它?那种语法已经过时了),很难确定发生了什么。我建议发布更多代码,以便我们了解情况。


我希望我碰巧发现了一些奇怪的东西 :) 在发布之前,我尝试消除所有愚蠢的东西。记录一下:strict和warnings已经开启。%in在ReadParse()中定义和管理,它是CGI.pm的一部分(标准Perl模块,不是我的代码)。 - Wick

2
您使用的是哪个版本的DBI?从查看DBI变更日志来看,似乎1.00之前的版本不支持属性参数。我怀疑“未初始化”的$in {test} 实际上是您传递给$dbh->do()undef。请注意保留HTML标签。

我在原始帖子中隐藏了这个,但是: 我正在使用Perl 5.8.8、CGI 3.20和DBI 1.52。 - Wick
我认为可以肯定地说这不是发生的原因,因为如果我尝试切换占位符变量顺序:$dbh->do(qq{INSERT INTO testing VALUES (?,?,?)},undef,"$in{test}",$in{test},$test);..行中的第二列将获得空值。 - Wick
我认为你之前的想法是正确的,可能是由于混淆的符号表/绑定变量导致了问题。如果我放弃ReadParse()调用并将其替换为: my %in = ('test','hello world'); ...则数据库中的所有三列都会被正确设置。 - Wick
这个回答本应该只是一个对问题的评论。在它被投票降低之前,我会将其删除。 - Brad Gilbert
错过了版本号,抱歉。如果交换引用/不引用的参数会移动问题,则FETCH出现问题。 - Michael Carman
我不确定这是否有帮助,但使用标准的CGI param()方法: $dbh->do(qq{INSERT INTO testing VALUES (?,?,?)},undef,$query->param('test'),....); ..同样可以正常工作,第一列被设置为预期值。这就是为什么我认为问题出在ReadParse和%in哈希表的管理方式上。 - Wick

1

根据DBI文档:绑定一个tied变量目前无效。

DBI在内部相当复杂,不幸的是需要进行一些优化导致了问题。我赞同其他人的观点,建议不要再使用旧的cgi-lib样式代码。没有漂亮的框架(如Catalyst),做CGI已经够烦人的了,更别说过时十年的东西了。


绑定是使用Perl别名进行低级别的操作。每当从数据库中提取一行时,$var_to_bind似乎会自动更新,因为它现在引用与相应列值相同的内存位置。绑定绑定变量无法正常工作。 - Wick
不错的发现。记录一下,这是来自http://search.cpan.org/~timb/DBI/DBI.pm#bind_col - Wick

0

由于这看起来像是一个 tie() 问题,尝试以下实验。将其保存为 foo.pl 并运行 perl foo.pl "x=1"

use CGI;

CGI::ReadParse();
p($in{x}, "$in{x}");

sub p { my @a = @_; print "@a\n" }

它应该打印1 1。如果没有,我们就找到了罪魁祸首。


抱歉耽搁了,我被分心了。它会打印出1 1。唉,如果有帮助的话,我可以发布整个测试文件,它不比示例代码多多少。 - Wick
如果有帮助的话,完整的测试文件在这里: http://www.carcomplaints.com/test/test.pl.txt - Wick
此外,我在办公室有一个运行 Perl 5.10 和最新版本的 CGI/DBI 的盒子,明天我会尝试一下,以防万一。谢谢。暂时先这样,再次感谢,祝一切顺利。 - Wick
那一定是DBI里的问题。在调试器中运行您的程序,进入do()函数(您需要先通过CGI的FETCH函数),并检查参数的值。 - Michael Carman
我实际上从未使用过Perl调试器。可能是我该用它的时候了。 - Wick

0

我刚刚尝试了您从http://www.carcomplaints.com/test/test.pl.txt获取的测试代码,它在我的电脑上立即运行,没有问题。我得到了三个预期值。我没有使用CGI运行它,而是使用:

...
use CGI qw/-debug/;
...

我在控制台上写了一个变量(test=test),你的脚本可以顺利插入。

但是,如果你省略了这个变量,它将插入一个空字符串和两个NULL。这是因为你将一个值插入到一个字符串中。这将创建一个值为$in{test}的字符串,该值目前为undefundef转换为一个空字符串,这就是插入到数据库中的内容。


嗨,彼得,感谢您的评论,但您所描述的并不是发生的情况(插入空字符串和两个NULL)。为了转述我的原始问题,只有$in{test}参数无法插入预期的值。其他两个参数正常工作,并插入提供的任何值作为测试。 - Wick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接