【问题标题】:How to properly use Global variables in perlperl中如何正确使用全局变量
【发布时间】:2013-04-16 11:05:31
【问题描述】:

我是 perl 的新手。我试图通过编写一些程序来理解它。 perl 中的作用域让我很难过。

我写了以下内容:

use 5.16.3;
use strict;
use Getopt::Long;

Getopt::Long::Configure(qw(bundling no_getopt_compat));
&ArgParser;
our ($sqluser,$sqlpass);

$sqluser="root";
$sqlpass="mypassword";

sub ArgParser {
    print "Username is ".$sqluser." Password is ".$sqlpass."\n";
    my $crt='';
    my $delete='';
    GetOptions ('create|c=s' => \$crt,
        'delete|d=s' => \$delete
    );
    if ($crt) {
        &DatabaseExec("create",$crt);   
    } elsif ($delete) {
        &DatabaseExec("delete",$delete);    
    } else {
    print "No options chosen\n";
    }
}

sub DatabaseExec {
    use DBI;
    my $dbname=$_[1];
    print "Username is ".$sqluser." Password is ".$sqlpass."\n";
    my $dbh = DBI->connect("dbi:mysql:", $sqluser,$sqlpass);
    my $comand=$_[0];
    if ($_[0] eq "create") {
        my $db_com="create database ".$dbname;
        print 1 == $dbh->do($db_com) ? "Database created\n":"An error occured while creating database. Maybe it exists?\n";
        #print "Executing: ".$db_com."\n";
    } elsif ($_[0] eq "delete") {
        my $db_com="DROP DATABASE ".$dbname;
        #print "Executing: ".$db_com."\n";
        print 1 == $dbh->do($db_com) ? "Database deleted\n":"An error occured while creating database. Maybe it exists?\n";
    }
}

据我了解,我们会将这些声明为全局变量,以供主代码和子程序使用。然而,这给出了以下输出:

#~/perlscripts/dbtest.pl -c hellos
Use of uninitialized value $sqluser in concatenation (.) or string at /root/perlscripts/dbtest.pl line 20.
Use of uninitialized value $sqlpass in concatenation (.) or string at /root/perlscripts/dbtest.pl line 20.
Username is  Password is
Use of uninitialized value $sqluser in concatenation (.) or string at /root/perlscripts/dbtest.pl line 44.
Use of uninitialized value $sqlpass in concatenation (.) or string at /root/perlscripts/dbtest.pl line 44.
Username is  Password is
DBI connect('','',...) failed: Access denied for user 'root'@'localhost' (using password: NO) at /root/perlscripts/dbtest.pl line 45.
Can't call method "do" on an undefined value at /root/perlscripts/dbtest.pl line 50.

我不想将这些作为参数传递给 sub,而是将它们用作全局变量。有人可以帮我确定我对范围界定的误解吗?

【问题讨论】:

  • 您是否在不知道自己在做什么的情况下以 root 身份运行 perl 脚本? :)
  • 嗯,是的,但这是一个 VPS,除了测试 perl 和下载种子外,我什么也不做。 :)
  • 不过,创建具有有限权限的新用户是一个小的安全预防措施。

标签: perl


【解决方案1】:

当你的子程序被调用时,你的变量没有被声明:

&ArgParser;                 # subroutine call
our ($sqluser,$sqlpass);    # declaration

$sqluser="root";            # assignment
$sqlpass="mypassword";

为了在子程序内部使用这些全局变量,请将子程序放在变量声明之后。

但是,使用全局变量是一件坏事,您应该尽可能避免使用它。您可以改为这样做,例如:

my $sqluser = "root";
my $sqlpass = "mypass";

ArgParser($sqluser, $sqlpass);    # you should not use & in subroutine calls

然后在子程序里面:

sub ArgParser {
    my ($sqluser, $sqlpass) = @_;
    ...

这样,您的变量被很好地封装并且不会被意外操作。

关于子程序调用中的与符号&,这在perldoc perlsub中有记录:

To call subroutines:

NAME(LIST);   # & is optional with parentheses.
NAME LIST;    # Parentheses optional if predeclared/imported.
&NAME(LIST);  # Circumvent prototypes.
&NAME;        # Makes current @_ visible to called subroutine.

【讨论】:

  • 如果我不使用 & 符号,我会得到:“在 ./dbtest.pl 第 13 行使用“strict subs”时不允许使用裸字“ArgParser”。”。
  • @Droidzone 那是因为您没有阅读我如此深思熟虑地粘贴到我的答案中的文档。 ArgParser; 不在调用子例程的有效方法列表中,除非您预先声明了子例程。使用带括号的ArgParser()
  • 对不起,我现在明白了。一段时间以来,我一直在使用 subs 而不声明它们。
  • @Droidzone 您只需要预先声明是否希望能够使用不带括号的 sub。所以,这没有错。
  • 即使您不想切换到传递参数(并且您确实应该进行更改),您仍然可以将您的子例程调用从 &ArgParser 更改为 ArgParser()
【解决方案2】:

Perl 没有全局 变量。 Perl 有:

  • 包变量。
  • 词法范围的变量。

包是一个命名空间。在 Perl 中,命名空间有时称为 。您的默认包名称是main。例如。这是完全合法的:

use strict;
use warnings;

$main::variable = "What? Where's my 'our' or 'my' declaration?";

print "Look, I can print $main::variable without using 'my' or 'our'!";

我只是在我的包变量名称前加上一个包,然后哇!它们存在!

这让我很震惊:

use strict;
use warnings;

$variable = "What? Where's my 'our' or 'my' declaration?";

print "I'm not going to print 'cause you're going to get a compilation error";

使用use strict;,您必须将变量声明为ourmy,或者在其前面加上它所在的包的名称

包变量最容易理解。包变量实际上存储在 Perl 变量结构中,因此一旦声明它们就始终可用:

use strict;
use warnings;

if ( 1 == 1 ) {  #Yes, I know this is always true
    our $foo = "I have a value!";
}

say "Looks like $foo has a value";

词法范围的变量更难理解。基本上,一个词法范围的变量在它定义的 block 的范围内,但是一旦你离开那个块就超出了范围。它也可以在子块中使用:

use strict;
use warnings;

my $foo = "Foo has a value";

if ( $foo ) {   #Always true
    my $bar = "bar has a value";
    print "$foo\n";    # $foo has a value. This is a sub-block
    print "$bar\n";    # $bar has a value. It was defined in this block
}

print "$foo\n";    # $foo still has a value.
print "$bar\n";    # You'll get en error here. $bar out of scope here

这里有一些建议:

  • 您不需要预先声明子程序。那只是自找麻烦。
  • 如果您在程序的开头定义变量,则可以使用my 变量,它们将在您的子例程中可用,因为它们仍在范围内。
  • & 离开子程序调用。它们会导致子例程的工作方式发生细微的变化,而这些细微的变化可能不是您想要的。标准只是调用子程序。
    • 避免使用? ... : ...,尤其是在您不使用空格的情况下。它使您的程序更难阅读,并且不会节省任何执行时间。
    • 调用子程序后立即将子程序参数放入变量中。
  • Perl 为您插入变量。 Perl 有很多问题。它没有真正的面向对象。它不是面向异常的语言。它有很多杂物。最大的优势之一是您不必通过各种诡计来打印变量值。当您与 Python 粉丝在一起时,使用它并自豪地抬起头来。
  • 使用空格使您的代码更易于阅读。
  • 也许您真正想要的是常量my 变量会起作用,但常量可以保证这些值不会在您的程序中被意外更改。

这是您重写的代码。请注意,常量前面没有印记。这些通常不能插入到字符串中。但是,如果你用@{[...]} 包围它们,你也可以插入它们。我做了以下两种方式:

use 5.16.3;
use strict;
use Getopt::Long;

use constant {
    SQL_USER => "root",
    SQL_PASS => "mypassword",
};

Getopt::Long::Configure qw(bundling no_getopt_compat);

sub ArgParser {
    print "Username is " SQL_USER . " Password is " . SQL_PASS . "\n";
    my $crt;
    my $delete;
    GetOptions (
        'create|c=s' => \$crt,
        'delete|d=s' => \$delete,
    );
    if ( $crt ) {
        DatabaseExec( "create", $crt );   
    }
    elsif ( $delete ) {
        DatabaseExec( "delete", $delete );    
    }
    else {
        print "No options chosen\n";
    }
}

sub DatabaseExec {
    use DBI;

    my $comand = shift;
    my $dbname = shift;

    print "Username is @{[SQL_USER]} Password is @{[SQL_PASS]}\n";

    my $dbh = DBI->connect(
        "dbi:mysql:",
        SQL_USER,
        SQL_PASS
    );

    if ( $command eq "create" ) {
        my $db_com = "create database $dbname";
        if ( $dbh->do( $db_com ) ) {
            print "Database created\n"
        }
        else {
            print "An error occured while creating database. Maybe it exists?\n";
        }
    } elsif ( $command eq "delete" ) {
        my $db_com = "DROP DATABASE $dbname";
        #print "Executing: ".$db_com."\n";
        if ( $dbh->do($db_com) ) {
            print "Database deleted\n";
        }
        else {
            print "An error occured while creating database. Maybe it exists?\n";
        }
    }
}

【讨论】:

  • 很棒的解释。非常感谢您提供正确的编程指南!
【解决方案3】:

我推荐阅读的关于变量作用域的经典资源是 Mark-Jason Dominus 的 Coping with scoping:它描述了 Perl 变量(包和词法变量)系列的基本划分,并警告初学者可能会遇到的一些不良做法发生。

【讨论】:

  • 谢谢,不过是不是有点过时了?它没有提及较新的“我们的”。
  • 与其过时,我只想说它已经过时了:它缺乏关于 Perl 的最新新东西,但我认为它的实质仍然有效。值得一读的东西,让我们这么说吧。
  • our 并不是什么新东西,它一直在 Perl 中 since 5.6.0 back in 2000
猜你喜欢
  • 1970-01-01
  • 2017-08-02
  • 1970-01-01
  • 1970-01-01
  • 2012-03-11
  • 2013-10-06
  • 2011-12-22
  • 2023-01-11
  • 2013-01-04
相关资源
最近更新 更多