1

我对 Perl 很陌生。我编写了一个脚本来显示Linux passwd 文件中的用户名。它显示用户名列表,但随后它还显示用户 ID(我目前不尝试显示),最后它显示“用户 ID 和名称列表:”它应该在显示名称列表之前显示。知道为什么它会这样吗?

 #!/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;
 }
4

3 回答 3

5

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.

于 2013-07-20T19:30:34.130 回答
3

为了读取系统数据库,您应该使用适当的系统函数:

use feature qw(say);

while (
    my ($name,    $passwd, $uid, $gid,   $quota,
        $comment, $gcos,   $dir, $shell, $expire
    )
    = getpwent
    )
{
    say "$uid $name";
}
于 2013-07-20T20:42:50.720 回答
0

如果您正在扫描整个密码文件,您可以使用getpwent()

while( my @pw = getpwent() ){
  print "@pw\n";
}

perldoc -f getpwent

于 2013-07-20T20:50:30.547 回答