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

I have a problem with using an array inside a format when I try I get unitialized value error but I can use that same array in a print statement and it works just fine.

#!/usr/bin/perl use strict; use warnings; use DBI; #Declaire all variables now! my @customer_comp; #Structure for @customer_comp array is as follows: #comp. id, Cust. id, comp. man, comp. model, mod num, OS, error msg, s +ervic, probdesc, didDiagnostic, login pw my @customer; #Structure for @customer array is as follows: #id, first name, last name, email, phone, date my $sth; my @ref_Customer; my @ref_Computer; my $database = "Repairs"; #Specify the database to be used my $hostname = "192.168.1.111"; #Connect to the MySQL server my $port = '3306'; #MySQL port my $user = 'username'; #MySQL username my $password = 'password'; #MySQL password my $dsn; my $dbh; my $version = '1.1.9'; #Script version number main(); #break here sub main() { if ( $^O =~ /MSWin32/ ) { system('cls'); } else { system('clear'); } print "Cleveland State CC Computer Repairs\n\n"; print "Version $version\n", "Copyleft 2009 Adam Jimerson & Andy Arp\n"; print "----------------------------\n", "Main Menu, To make a selection specify the number for that ac +tion\n"; print "1. Add New Computer to Database\n"; print "2. Edit Computer Status in Database\n"; print "3. Remove a Computer from Database\n"; print "4. Look up Computer Information\n"; print "5. List All Repair Requests\n"; print "6. Print Final Report For Customer\n"; #this should be don +e to ensure we provide information on a repair #to the customer on paper print "7. Check Computer Status\n"; print "8. Quit the program\n"; print "Action: "; my $option = <STDIN>; chomp($option); if ( $option == 1 ) { print_form(); } elsif ( $option == 2 ) { print_edit(); } elsif ( $option == 3 ) { print_remove(); } elsif ( $option == 4 ) { print_lookup(); } elsif ( $option == 5 ) { print_all(); } elsif ( $option == 6 ) { print_report(); } elsif ( $option == 7 ) { print_checkstatus(); } elsif ( $option == 8 ) { exit 0; } else { print "Error: Option $option invalid\n"; <STDIN>; main(); } } sub print_form { print "system('date')"; print "Today's Date (YYYY-MM-DD): "; #Until a better way is found +to get the date, ask for it $customer[5] = <STDIN>; chomp($customer[5]); print "\nCustomer Information\n", "-----------------------\n"; #Begin the customer information f +orm print "Customer First Name: "; $customer[1] = <STDIN>; chomp($customer[1]); print "Customer Last Name: "; $customer[2] = <STDIN>; chomp($customer[2]); print "Customer Barcode, Structure 10 chars incl hyphen: "; $customer[0] = <STDIN>; chomp($customer[0]); $customer_comp[1] = $customer[0]; print "Customer Phone #: "; $customer[4] = <STDIN>; chomp($customer[4]); print "Customer Email: "; $customer[3] = <STDIN>; chomp($customer[3]); print "\nComputer Information\n", "-----------------------\n"; #Begin the computer information f +orm $customer_comp[0] = ''; print "Computer Manufacturer: "; $customer_comp[2] = <STDIN>; chomp($customer_comp[2]); print "Computer Model: "; $customer_comp[3] = <STDIN>; chomp($customer_comp[3]); print "Computer Model #: "; $customer_comp[4] = <STDIN>; chomp($customer_comp[4]); print "Error Message (if any): "; $customer_comp[6] = <STDIN>; chomp($customer_comp[6]); print "Operating System: "; $customer_comp[5] = <STDIN>; chomp($customer_comp[5]); print "Problem Description: "; $customer_comp[8] = <STDIN>; chomp($customer_comp[8]); print "Administrator/Root Login Password: "; $customer_comp[10] = <STDIN>; chomp($customer_comp[10]); print "Computer Service Needed: "; $customer_comp[7] = <STDIN>; chomp($customer_comp[7]); $customer_comp[9] = '0'; #$customer[5] = system('date'); db_add(); } sub db_add { #Insert values retreived from print_form() $dsn = "DBI:mysql:database=$database;host=$hostname;port=$port"; $dbh = DBI->connect($dsn, $user, $password, {RaiseError => 1}); $dbh->do("INSERT INTO computer (compid, custid, compManufacturer, +compModel, compModelNum, OS, errorMsg, serviceNeeded, problemDescript +ion, didDiagnostic, password) VALUES (?,?,?,?,?,?,?,?,?,?,?)", {}, @c +ustomer_comp); $dbh->do("INSERT INTO customers (id, firstname, lastname, email, p +hone, date) VALUES (?,?,?,?,?,?)", {}, @customer); $dbh->disconnect(); main(); } sub print_lookup { print "Please Scan System Barcode: "; #Get barcode information $customer[0] = <STDIN>; chomp($customer[0]); db_fetch(); } sub db_fetch() { $dsn = "DBI:mysql:database=$database;host=$hostname;port=$port"; $dbh = DBI->connect($dsn, $user, $password, {RaiseError => 1}); $sth = $dbh->prepare("SELECT * FROM customers WHERE id = '$custome +r[0]'"); eval { die "Prepare has failed"; }; $sth->execute(); eval { die "Execute has failed"; }; @ref_Customer = $sth->fetchrow_array(); eval { die "Fetchrow has failed"; }; print "Customer\n"; print "@ref_Customer\n"; $sth->finish; $sth = $dbh->prepare("SELECT * FROM computer WHERE custid = '$cust +omer[0]'"); eval { die "Prepare has failed"; }; $sth->execute(); eval { die "Execute has failed"; }; my @ref_Computer = $sth->fetchrow_array(); eval { die "Fetchrow has failed"; }; print "Computer\n"; print "@ref_Computer\n"; $sth->finish; $dbh->disconnect; <STDIN>; #Pause execution of the script so that the query output c +an be read main(); } sub print_all { $dsn = "DBI:mysql:database=$database;host=$hostname;port=$port"; $dbh = DBI->connect($dsn, $user, $password, {RaiseError => 1}); $sth = $dbh->prepare("SELECT * FROM customers"); eval { die "Prepare has failed"; }; $sth->execute(); eval { die "Execute has failed"; }; print "Customer\n"; while (@ref_Customer = $sth->fetchrow_array()) { eval { die "Fetchrow has failed"; }; print "@ref_Customer\n"; } $sth->finish; $sth = $dbh->prepare("SELECT * FROM computer"); eval { die "Prepare has failed"; }; $sth->execute(); eval { die "Execute has failed"; }; print "Computer\n"; while (@ref_Computer = $sth->fetchrow_array()) { eval { die "Fetchrow has failed"; }; print "@ref_Computer\n"; } $sth->finish; $dbh->disconnect; <STDIN>; #Pause execution so output can be read main(); } sub print_remove { print "Please Scan System Barcode: "; #Get Barcode information $customer[0] = <STDIN>; chomp($customer[0]); db_remove(); } sub db_remove { $dsn = "DBI:mysql:database=$database;host=$hostname;port=$port"; $dbh = DBI->connect($dsn, $user, $password, {RaiseError => 1}); $dbh->do("DELETE FROM customers WHERE id = '$customer[0]'"); $dbh->do("DELETE FROM computer WHERE custid = '$customer[0]'"); $dbh->disconnect(); main(); } sub print_edit { print "Please Scan Barcode For Entry To Edit:"; $customer[0] = <STDIN>; chomp($customer[0]); print "Is machine complete? (1 = True, 0 = False)"; my $status = <STDIN>; #God damn Andy stop making up new scaliers i +f there is a spot for it in the array chomp($status); if ( $status == 1 ) { db_edit(); } elsif ( $status == 0 ) { print "Error, Do not edit status until machine is complete.\n" +; <STDIN>; main(); } } sub db_edit { $dsn = "DBI:mysql:database=$database;host=$hostname;port=$port"; $dbh = DBI->connect($dsn, $user, $password, {RaiseError => 1}); $sth = $dbh->prepare("SELECT didDiagnostic FROM computer WHERE cus +tid = '$customer[0]'"); eval { die "Prepare has failed"; }; $sth->execute(); eval { die "Execute has failed"; }; @ref_Computer = $sth->fetchrow_array(); eval { die "Fetchrow has failed"; }; print "Current Status:"; if ( @ref_Computer == "0" ) { print "Not Completed\n"; } elsif ( @ref_Computer == "1" ) { print "Completed\n"; } else { print "Customer Not Found.\n"; } $sth->finish; if ( @ref_Computer == "0" ) { #$dbh->do ("INSERT INTO computer (didDiagnostic) VALUES ("0")"; print "Edit Successful"; <STDIN>; db_check(); } else { print "Either computer has already been completed or is not found. +"; <STDIN>; main(); } } sub print_checkstatus { print "System Barcode: "; $customer[0] = <STDIN>; chomp($customer[0]); db_check(); } sub db_check { $dsn = "DBI:mysql:database=$database;host=$hostname;port=$port"; $dbh = DBI->connect($dsn, $user, $password, {RaiseError => 1}); $sth = $dbh->prepare("SELECT didDiagnostic FROM computer WHERE cus +tid = '$customer[0]'"); eval { die "Prepare has failed"; }; $sth->execute(); eval { die "Execute has failed"; }; @ref_Computer = $sth->fetchrow_array(); eval { die "Fetchrow has failed"; }; print "Current Status:"; if ( @ref_Computer == "0" ) { print "Not Completed"; <STDIN>; } elsif ( @ref_Computer == "1" ) { print "Completed"; <STDIN>; } $sth->finish; main(); } sub print_report { print "This function designed to be used on front desk ONLY!\n"; print "Please enter barcode number:"; $customer[0] = <STDIN>; chomp($customer[0]); db_report(); } sub db_report { open(OUTFILE, ">>/tmp/repairs") or die $!; $dsn = "DBI:mysql:database=$database;host=$hostname;port=$port"; $dbh = DBI->connect($dsn, $user, $password, {RaiseError => 1}); $sth = $dbh->prepare("SELECT * FROM customers WHERE id = '$custome +r[0]'"); eval { die "Prepare has failed"; }; $sth->execute(); eval { die "Execute has failed"; }; @ref_Customer = $sth->fetchrow_array(); eval { die "Fetchrow has failed"; }; $sth->finish; $sth = $dbh->prepare("SELECT * FROM computer WHERE custid = '$cust +omer[0]'"); eval { die "Prepare has failed"; }; $sth->execute(); eval { die "Execute has failed"; }; my @ref_Computer = $sth->fetchrow_array(); eval { die "Fetchrow has failed"; }; print "Computer\n"; print "@ref_Computer[2..5]\n"; write; print OUTFILE @ref_Customer, "\n", @ref_Computer[2..5]; $sth->finish; $dbh->disconnect; <STDIN>; close(OUTFILE); # system("lpr -# 1 -o prettyprint -r /tmp/repairs"); #Prints out t +wo copies, formats the page with a banner, and removes /tmp/repairs w +hen done print "Would you like to remove this customer and computer? (y = Y +es, n = No)\n"; my $option = <STDIN>; chomp($option); if ( lc($option) eq "y" ) { db_remove(); } elsif ( lc($option) eq "n" ) { print "Did Not Remove\n"; <STDIN>; main(); } } format STDOUT_TOP= ID First Name Last Name Email Address Phone Number Dat +e Comp. Manufacturer Comp. Model Model Number OS ========== ========== ============ ================ ============ === +======= ================== =========== ============ ============= . format STDOUT= @<<<<<<<<< @<<<<<<<< @<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<< @>>>>>>>>>>> +>>>> @>>>>>>>>>> @<<<<<<<<<<<<<< @<<<<<<<<<<<<<<< @>>>>>>>>>> @<<<<<< +<<<<<< $ref_Customer[0], $ref_Customer[1], $ref_Customer[2], $ref_Customer[3] +, $ref_Customer[4], $ref_Customer[5], $ref_Computer[2], $ref_Computer +[3], $ref_Computer[4], $ref_Computer[5] .
This is the output that I get
Computer Dell Dimension 2350 Windows XP Use of uninitialized value in formline at /home/cscc/bin/repairs.pl li +ne 410, <STDIN> line 2. Use of uninitialized value in formline at /home/cscc/bin/repairs.pl li +ne 410, <STDIN> line 2. Use of uninitialized value in formline at /home/cscc/bin/repairs.pl li +ne 410, <STDIN> line 2. Use of uninitialized value in formline at /home/cscc/bin/repairs.pl li +ne 410, <STDIN> line 2. ID First Name Last Name Email Address Phone Number Dat +e Comp. Manufacturer Comp. Model Model Number OS ========== ========== ============ ================ ============ === +======= ================== =========== ============ ============= 000-000001 John Doe xxxxxxx@xxxxxxxx.net (xxx)xxx-xxxx 20 +09-09-15
Anyone know what I am missing to fix this?

Replies are listed 'Best First'.
Re: Trouble with an array inside a format call
by jethro (Monsignor) on Oct 14, 2009 at 20:28 UTC

    Pointing out line 410 would have been helpful.

    In your subroutine db_report you have a line "my @ref_Computer ...". This means @ref_Computer is local to that subroutine. The format line at the end of your program is outside of that sub, so all the $ref_Computer[x] there refer to the global variable @ref_Computer which is unused and empty. Solution: Move the format inside the subroutine.

    You shouldn't use global variables that have the same name as local variables. Even better, don't use global variables at all if you can avoid it.

      /me face palms I should have found that, and so getting rid of the "my @ref_Computers" on line 378 and using the my declaration on line 15 that way its not global (IIRC I would need to use "our" for that).

Re: Trouble with an array inside a format call
by Marshall (Canon) on Oct 14, 2009 at 23:16 UTC
    I think jethro's comments are on traget with your error. I'll make a couple of other suggestions. Not trying to be hyper critical, just helpful.

    1. When debugging stuff where you are getting run time messages to STDERR and printing stuff to STDOUT at the same time, often it is helpful to turn buffering off by putting $|=1; at the top of your code. STDERR is not buffered while STDOUT is and thus the order of the stuff on the screen won't reflect the actual time sequence of events unless you turn the buffering off.

    2. I believe that your command loop could be improved. You've set main up as a subroutine that you call when you want to get started again (happens in a bunch of places in the code). This approach has a problem, every time you call main(), you leave some stuff on the stack. To illustrate the issue, make these 2 small changes to your code:

    } elsif ( $option == 8 ) { return(0); ######change here } else { print "Error: Option $option invalid\n"; <STDIN>; main(); print "leftover\n"; ###change here <STDIN>; }
    Now run the program and select an invalid option a couple of times, then select 8, the exit. You will see that "leftover" gets printed a few times these are the exits from main() that never happened before. Now in this program, I doubt that this would become any big deal. But this probably would get to be a problem in a long lived program that called main() a lot.

    3. There are many ways to Rome on command loops. I wrote some quick code for you to show another way to do this - many variations are possible. A general philosophy of mine when dealing with user input: validate the heck out of it before trying to use it and be flexible in allowing users to enter whitespace around input.

    Each action does what it does and then returns to the main command loop. Then program just falls through any $option compare statements and goes back to reprompt. So when one of these "action" subroutines finishes, either with some error message or success message, just return(), don't call main() again!

    I tried to incorporate your idea of clearing the screen after each action. Although I remember answering your question about how to do this in XP, I personally would find that interface annoying and wouldn't do it that way. But this is your app, not mine! Go for it!

    Anyway, main shouldn't be a subroutine.

    3. I would move the database connection and authentication stuff to the top of program. Looks like every action depends upon DB. No need to do this multiple times and no need to bother user entering in a whole bunch of stuff only to find out later that operation is going to fail because you can't connect to the database.

    4. I don't know how your experience will work out with FORMAT, but when I first saw this, I thought how cool! I wrote one program with it. Then decided that it was more hassle than other ways. I find that printf, sprintf do a better job for me. Your mileage may vary.

      STDERR is not buffered while STDOUT is and thus the order of the stuff on the screen won't reflect the actual time sequence of events unless you turn the buffering off.

      I think this is misleading advice. STDOUT is line buffered. I skimmed all of the statements which print to STDOUT, and I don't believe your advice will have any effect.

        I certainly can't rule out some kind of OS or installation specific thing that makes your setup different than mine. I am using WinXP and ActiveState 5.10, although I know from experience this is the same on ActiveState 5.6.

        Here is a simple test program. Try it on your machine and see if you get the same results.

        #!/usr/bin/perl -w use strict; $|=1; #test was run with and without this statement #this turns buffering off print "this is a test\n"; print "this is a test2\n"; print STDERR "this is an error after line2\n"; print "this is a test3\n"; print "this is a test4\n"; __END__ Prints: without $|=1........ this is an error after line2 this is a test this is a test2 this is a test3 this is a test4 Prints: Now with $|=1 ...... this is a test this is a test2 this is an error after line2 this is a test3 this is a test4
        The STDERR message gets printed first because the lines from STDOUT haven't gone to the screen yet.

        Using $|=1 decreases I/O performance by maybe 30% or something like that. Normally of no consequence unless you are doing a lot of output or are trying to get the time sequence of things right.

        Update: deleted few lines about my version of tee.pl as there is a similar topic going on in another thread and I got them confused. Re^3: Getting user input with STDOUT tee to file.

      Thanks for the help first of all, also Marshall I like the way you have done it on your third suggestion and I was wondering if with it like that would #2 still be a problem?
      As for 4 format does its job and I can't argue with that, but my goal is to that the program is to have the program write format and its output to the /tmp/repairs file.

        I see that I inadvertently used #3 twice but, Yes, re-doing the command loop will solve the problem in #2.

        If you mean the DB authentication, I would authenticate early and this "my $dbh" (database handle) will have package scope, a my $var at the top package level which cannot be exported to other modules (because it is a "my"), but yet would be visible to any sub in the package.

        Even that you don't need to pass this to subs, I would pass the $dbh as a param to each "action" sub that needs it. The reason for that is to make it clear what each sub is using. You should wind up with a very small number of package level "real" variables. To further that I would suggest using the "constant" pragma.

        use constant database => "Repairs"; #the database name use constant hostname => "192.168.1.111"; #MySQL server IP address use constant port => '3306'; #MySQL port
        A constant is not a $variable and cannot be modified. You use it like a bareword (without any $ in front of it) in the actual code. I recommend this for package level things that are the "seldom changed, but important constants that might need to change in a different version of the program".

        You should wind up with a very small number of package scoped "real variables" that the program changes during execution.

        As far as Perl FORMAT goes, you will have to make up your mind about that as you write more code. It appears to work fine for strings, but when you want say exactly 2 decimal places for a currency number, this is not so good.

        BTW, for this heading thing, there is no need for a FORMAT statement. A simple way is to use what is called a "here is" document.

        #!/usr/bin/perl -w use strict; my $STDOUT_TOP= <<END_TOP; ID First Name Last Name Email Address Phone Number Dat +e Comp. Manufacturer Comp. Model Model Number OS ========== ========== ============ ================ ============ === + ======= ================== =========== ============ ============= END_TOP print $STDOUT_TOP;
Re: Trouble with an array inside a format call
by zwon (Abbot) on Oct 14, 2009 at 20:13 UTC

    First of all thanks for not posting database dump ;)

    It looks like @ref_Computer is empty. It's possible that John Doe doesn't have the computer. You can check content of @ref_Computer using Data::Dumper.

    P.S.: How do I post a question effectively?