vendion has asked for the wisdom of the Perl Monks concerning the following question:

Hello all I'm trying to develop an in house ticket system for a student lead non-profit computer repair service at my college. I'm currently running into two major problems, don't know if they are related or not, which causes this program to be unusable.

The first problem happens when the program should check the input from the user. This process errors out with the following error Use of uninitialized value within @data in pattern match (m//) at repairs.pl line 212, <> line 1. When I comment out line 212 and 213 then the program just hangs. Here is a more details of what gets printed out:

Cleveland State CC Computer Repairs Version '2.0.0_3', Copyleft 2009-2010 Adam Jimerson & Andy Arp ---------------------------- Main Menu, To make a selection specify the number for that action 1. Add New Computer to Database 2. Edit Computer Status in Database 3. Remove a Computer from Database 4. Look up Computer Information 5. List All Repair Requests 6. Customer Checkout 7. Check Computer Status 8. Add a Comment 9. Add a Donation Amount 10. Quit Action: 10 Use of uninitialized value within @data in pattern match (m//) at repa +irs.pl line 212, <> line 1. Use of uninitialized value in pattern match (m//) at repairs.pl line 2 +13, <> line 1.^C
Here is my current code for the program:
#!/usr/bin/perl use strict; use warnings; use version; my $VERSION = qw('2.0.0_3'); use English qw( -no_match_vars ); while ( display_menu(), ( my $option = <> ) ) { $option = data_verify($option); if ( ( $option !~ m{/^\d$/}xms ) or ( $option <= 0 ) or ( $option > 1 +0 ) ) { display_menu_error("ERROR: illegal option: $option selected\n"); next; } if ( $option == 10 ) { $dbh->disconnect; print "Quitter...\n"; exit 0; } } sub clear_screen { # Clears the screen for both Windows and *nix systems and returns to +the caller if ( $^O =~ /MSWin32/ ) { system 'cls'; } else { system 'clear'; } return; } sub display_menu { # Displays the main menu #db_connect(); #Commented out for debugging my $menu = <<"END_MENU"; Cleveland State CC Computer Repairs Version $VERSION, Copyleft 2009-2010 Adam Jimerson & Andy Arp ---------------------------- Main Menu, To make a selection specify the number for that action 1. Add New Computer to Database 2. Edit Computer Status in Database 3. Remove a Computer from Database 4. Look up Computer Information 5. List All Repair Requests 6. Customer Checkout 7. Check Computer Status 8. Add a Comment 9. Add a Donation Amount 10. Quit Action: END_MENU print $menu; return; } sub data_verify { # takes in information and loops through testing the input for valid a +lphanumeric characters. my @data = shift; my $loop_count = 0; while (@data) { $data[$loop_count] =~ m{s/^\s*//}xms; $data[$loop_count] =~ m{s/\s*$//}xms; $loop_count++; } return @data; }
If for any reason anyone needs all of my code you can get it from vendion's scratchpad or from http://pastebin.org/385645 I thank you in advance for any help offered, and for any other suggestions.

Replies are listed 'Best First'.
Re: Returning and passing data to and from subroutines
by almut (Canon) on Jul 07, 2010 at 22:38 UTC
    while (@data) { $data[$loop_count] =~ m{s/^\s*//}xms; $data[$loop_count] =~ m{s/\s*$//}xms; $loop_count++; }

    In addition to what toolic said, your loop would never end, as the condition tests the number of elements in the array, which doesn't change in the loop never becomes zero/false.

    You probably want  (though I'm not really sure what the substitutions are meant to do — what's the idea behind the /xms?)

    for (@data) { # remove leading/trailing whitespace s/^\s*//; s/\s*$//; }

    The current value being iterated over is aliased to $_, which allows you to change the original values by modifying $_.

      Using xms switches and m{} instead of // with every regex is a suggestion by Damian Conway in his book 'Perl Best Practices'

      So vendion really wanted to write

      s{\A \s* }{}xms

      UPDATE: Removed a \Z that shouldn't have been there

        So vendion really wanted to write ...

        Well, just another speculation :)

        The OP said in the comment "...testing the input for valid alphanumeric characters".  What you're suggesting would remove all whitespace if there's nothing but whitespace in the string.

        Yes I was trying to follow the suggestion in the 'Perl Best Practices' book, but what I was wanting to go for was removing the whitespace before and after any kind of text. Sorry for the confusion

Re: Returning and passing data to and from subroutines
by ikegami (Patriarch) on Jul 07, 2010 at 22:50 UTC
    • The first error means the value against which you are matching is undef. The value against which you are matching is data_verify's first argument. That situation is not possible in the code you posted.

      There are numerous places where undefined could be passed to data_verify in the code on your scratchpad.

    • The second error is caused by your use of the $/ in your pattern while that variable is undefined. m{s/\s*$//}xms means

      • Match the two characters 's/',
      • followed by any number of spaces,
      • followed by the pattern held by $/,
      • followed by the character '/'.

      Obviously not what you wanted. For starters, you wanted to do a substitution rather than a match. m{s/\s*$//}xms should be just s/\s*$//.

      sub data_verify { my @data = shift; my $loop_count = 0; while (@data) { $data[$loop_count] =~ s/^\s*//; $data[$loop_count] =~ s/\s*$//; $loop_count++; } return @data; }

    By the way,

    sub data_verify { my @data = shift; my $loop_count = 0; while (@data) { $data[$loop_count] =~ s/^\s*//; $data[$loop_count] =~ s/\s*$//; $loop_count++; } return @data; }
    is equivalent to
    sub data_verify { my $data = shift; $data =~ s/^\s*//; $data =~ s/\s*$//; return $data; }

    Based on the code in your scratchpad, you want to process multiple values per call. What you really want is

    sub data_verify { my @data = @_; for (@data) { s/^\s*//; s/\s*$//; } return @data; }
Re: Returning and passing data to and from subroutines
by toolic (Bishop) on Jul 07, 2010 at 22:30 UTC
Re: Returning and passing data to and from subroutines
by ssandv (Hermit) on Jul 07, 2010 at 22:45 UTC

    It would have been helpful to point out that lines 212 and 213 are:

    my @data = shift; my $loop_count = 0; while (@data) { $data[$loop_count] =~ m{s/^\s*//}xms;#line 212 $data[$loop_count] =~ m{s/\s*$//}xms;#line 213 $loop_count++; }

    So first of all, shift gets _one scalar_ off the arguments. You can't pass arrays around like that in Perl, you pass lists. If you call a sub with an array as the argument, it comes in as a list. If you pass in *two* arrays, they get flattened into a single list of scalar arguments. Secondly, in Perl you can iterate over the elements of an array like so:

    foreach $element (@array) { do_stuff_to($element); }
    so you don't need a loop counter at all. If you *do* need a loop counter (i.e. to synchronize two array walks), you would be better off doing something like
    for $index (0..$#array) { # $#array is the last index of @array do_stuff_to($array[$index]); do_stuff_to($other_array[$index]); };

    Also your matches appear to be looking for some strange things. The s/// inside the m//, if it even works at all, is operating on $_, not on $data[$loop_count], I believe, which is unlikely to be what you want. m// should contain a regex, not code. s/// is code.

      So if I change the shift to @_ that would make it work for arrays but what if I need to pass something like a scalier or a hash? Is there a way to check/handle this other than making more than one sub that preforms the same task just taking different arguments? Sorry I was confused on the use of shift and I was under the impression that shift did exactly that.

        If you call a sub with a single scalar, it's the same as calling it with a 1-element list. @_ will contain the one scalar, and you can get it with shift. If you want to call a sub with multiple arrays or hashes and not have them flattened, you need to use references, and you probably want to look at something like perlreftut. If you call a sub with a single array and you want to get all the elements out at once, you can use:

        my @args=@_; # or my %args=@_ if you know it's going to be a hash
        or
        my ($arg1, $arg2, $arg3) = @_; # faster than (shift,shift,shift)
        the second form there assumes you know how many arguments you're going to get, or don't care if some of them come back undef.
Re: Returning and passing data to and from subroutines
by BrimBorium (Friar) on Jul 08, 2010 at 20:09 UTC
    In my understanding you want to process user input data by data_verify so it would be more efficient and less misleading to redesign and rename sub data_verify e.g. to process_input and call after each <>
    sub process_input{ my $data = shift; chomp($data); # cut off newline $data =~ s/^\s*//; # remove leading blanks $data =~ s/\s*$//; # remove trainilg blanks return $data; }
    I would store the given information in a hash rather than an array, but that's my personal preference.

    if ( ( $option !~ m{/^\d$/}xms ) or ( $option <= 0 ) or ( $option > 10 ) ) {
    should be better /^\d+$/ otherwise it will match only single digits and you will never be able to quit (10).

    seperate main_menu from db_connect to an extra sub would be better understandable

    ... I'm just thinking loudly ... oh, did I write that ?
Re: Returning and passing data to and from subroutines
by Khen1950fx (Canon) on Jul 08, 2010 at 01:17 UTC
    Just an observation about using version. You used
    use version; my $VERSION = qw('2.0.0_3').
    For dotted decimal notation, try
    use version 0.77; our $VERSION = qv("v2.0.0_3");