user2026794 user2026794 - 6 months ago 102
Perl Question

Perl script giving un predictable results

I am very new to Perl. I wrote a script to display user name from Linux passwd file.
It displays list of user name but then it also display user ids (which I am not trying to display at the moment) and at the end it displays "List of users ids and names:" which it should display before displaying list of names.
Any idea why it is behaving like this?

#!/usr/bin/perl
@names=system("cat /etc/passwd | cut -f 1 -d :");
@ids=system("cat /etc/passwd | cut -f 3 -d :");
$length=@ids;
$i=0;
print "List of users ids and names:\n";
while ($i < $length) {
print $names[$i];
$i +=1;
}

Answer Source

Short answer: system doesn't return output from a command; it returns the exit value. As the output of the cut isn't redirected, it prints to the current STDOUT (e.g. your terminal). Use open or qx// quotes (aka backticks) to capture output:

@names = `cat /etc/passwd | cut -f 1 -d :`;

As you are still learning Perl, here is a write-up detailing how I'd solve that problem:

First, always use strict; use warnings; at the beginning of your script. This helps preventing and detecting many problems, which makes it an invaluable help.

Next, starting a shell when everything could be done inside Perl is inefficient (your solution starts six unneccessary processes (two sets of sh, cat, cut)). In fact, cat is useless even in the shell version; just use shell redirection operators: cut ... </etc/passwd.

To open a file in Perl, we'll do

use autodie; # automatic error handling
open my $passwd, "<", "/etc/passwd";

The "<" is the mode (here: reading). The $passwd variable now holds a file handle from which we can read lines like <$passwd>. The lines still contain a newline, so we'll chomp the variable (remove the line ending):

while (<$passwd>) {  # <> operator reads into $_ by default
  chomp; # defaults to $_
  ...
}

The split builtin takes a regex that matches separators, a string (defaults to $_ variable), and a optional limit. It returns a list of fields. To split a string with : seperator, we'll do

my @fields = split /:/;

The left hand side doesn't have to be an array, we can also supply a list of variables. This matches the list on the right, and assigns one element to each variable. If we want to skip a field, we name it undef:

my ($user, undef, $id) = split /:/;

Now we just want to print the user. We can use the print command for that:

print "$user\n";

From perl5 v10 on, we can use the say feature. This behaves exactly like print, but auto-appends a newline to the output:

say $user;

And voilĂ , we have our final script:

#!/usr/bin/perl
use strict; use warnings; use autodie; use feature 'say';

open my $passwd, "<", "/etc/passwd";

while (<$passwd>) {
  chomp;
  my ($user, undef, $id) = split /:/;
  say $user;
}

Edit for antique perls

The autodie module was forst distributed as a core module with v10.1. Also, the feature 'say' isn't available before v10.

Therefore, we must use print instead of say and do manual error handling:

#!/usr/bin/perl
use strict; use warnings;

open my $passwd, "<", "/etc/passwd" or die "Can't open /etc/passwd: $!";

while (<$passwd>) {
  chomp;
  my ($user, undef, $id) = split /:/;
  print "$user\n";
}

The open returns a false value when it fails. In that case, the $! variable will hold the reason for the error.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download