6

I am a novice to perl. I'm trying to understand it by writing some programs. Scoping in perl is giving me a hard time.

I wrote the following:

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";
    }
}

It was my understanding that our would declare these as global variables for the use of the main code, and subroutines. However this gives the following output:

#~/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.

I would not like to pass these as arguments to the sub, and would rather use them as global variables. Could someone help me determine my misunderstanding of scoping?

TLP
  • 66,756
  • 10
  • 92
  • 149
Joel G Mathew
  • 7,561
  • 15
  • 54
  • 86

3 Answers3

11

Your variables are not declared when your subroutine is called:

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

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

In order to use these global variables inside the subroutine, put the subroutine after the variable declaration.

However, using global variables is a bad thing, and you should avoid it whenever possible. You can do this instead, for example:

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

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

And then inside the subroutine:

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

This way, your variables are nicely encapsulated and safe from being manipulated accidentally.

Regarding the ampersand & in your subroutine call, this is documented in 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.
TLP
  • 66,756
  • 10
  • 92
  • 149
  • If I dont use the ampersand, I get this: "Bareword "ArgParser" not allowed while "strict subs" in use at ./dbtest.pl line 13.". – Joel G Mathew Apr 16 '13 at 12:18
  • 1
    @Droidzone That is because you did not read the documentation that I so thoughtfully pasted into my answer for you. `ArgParser;` is not in the list of valid ways to call a subroutine unless you predeclare the subroutine. Use `ArgParser()`, with parentheses. – TLP Apr 16 '13 at 12:35
  • Sorry, I understand now. I've been using subs without declaring them for some time. – Joel G Mathew Apr 16 '13 at 12:40
  • 1
    @Droidzone You only need to predeclare if you want to be able to use the sub without parentheses. So, that's not wrong. – TLP Apr 16 '13 at 12:44
  • Even if you don't want to switch to passing arguments (and you really should make that change), you can still change your subroutine call from `&ArgParser` to `ArgParser()`. – Dave Cross Apr 16 '13 at 15:16
  • "put the subroutine after the variable declaration", I think it's the calls to the sub that need to be after the declaration... – Déjà vu Apr 01 '21 at 02:59
8

Perl does not have global variables. What Perl has are:

  • Package variables.
  • Lexically scoped Variables.

A package is a namespace. In Perl, a namespace is sometimes called a package. Your default package name is main. For example. This is perfectly legal:

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'!";

I simply prefix my package variable names with a package, and wham! They exist!

This causes me consternation:

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";

With the use strict;, you must either declare a variable as our or my, or prefix it with the name of the package it is in.

Package variables are easiest to understand. Package variables are actually stored in a Perl variable structure, so they're always available once declared:

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";

Lexically scoped variables are harder to understand. Basically, a lexically scoped variable is in scope in the block it's defined, but out of scope once you leave that block. It's also available in sub-blocks:

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

Here's a few suggestions:

  • You don't need to predeclare subroutines. That's just asking for trouble.
  • If you define your variables at the beginning of your program, you can use my variables, and they'll be available in your subroutines since they'll still be in scope.
  • Leave the & off of subroutine calls. They cause subtle changes in the way the subroutine works and these subtle changes are probably not what you want. Standard is just to call the subroutine.
    • Avoid the ? ... : ..., especially if you're not using spaces. It makes your program harder to read, and doesn't save any execution time.
    • Pull subroutine parameter into variables as soon as you call your subroutine.
  • Perl interpolates variables for you. There's a lot of problems with Perl. It doesn't have real object orientation. It's not a exception oriented language. It's got lots of cruft. One of the big advantages is that you don't have to go through all sorts of machinations to print a variable value. Use it and hold up your head in pride when you hang around Python fanboys.
  • Use spaces to make your code easier to read.
  • Maybe what you really want are constants. my variables will work, but constants assure you that these values won't be accidentally changed in your program.

Here's your code rewritten. Note that constants don't have a sigil in front. These normally can't be interpolated in strings. However, if you surround them with @{[...]}, you can interpolate them too. I did it both ways below:

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";
        }
    }
}
David W.
  • 105,218
  • 39
  • 216
  • 337
0

A classic resource on variable scoping, which I recommend reading, is Coping with scoping by Mark-Jason Dominus: it describes the fundamental partition in families of Perl variables (package and lexical variables) and warns about some bad practices a beginner might incur in.

larsen
  • 1,431
  • 2
  • 14
  • 26
  • 1
    Thank you, but isnt it a bit outdated? It makes no reference to the newer 'our'. – Joel G Mathew Apr 16 '13 at 12:10
  • 1
    Rather than outdated, I'd simply say it's old: it lacks latest new things about Perl, but I think it's still valid in his substance. Something it's worth reading, let's say so. – larsen Apr 16 '13 at 12:35
  • [`our`](http://perldoc.perl.org/functions/our.html "perldoc -f our") isn't really that new, It has been in Perl [since 5.6.0 back in 2000](http://perldoc.perl.org/perlhist.html "perldoc perlhist"). – Brad Gilbert Apr 16 '13 at 22:38