[24382] in Perl-Users-Digest

home help back first fref pref prev next nref lref last post

Perl-Users Digest, Issue: 6571 Volume: 10

daemon@ATHENA.MIT.EDU (Perl-Users Digest)
Sun May 16 09:10:46 2004

Date: Sun, 16 May 2004 06:10:10 -0700 (PDT)
From: Perl-Users Digest <Perl-Users-Request@ruby.OCE.ORST.EDU>
To: Perl-Users@ruby.OCE.ORST.EDU (Perl-Users Digest)

Perl-Users Digest           Sun, 16 May 2004     Volume: 10 Number: 6571

Today's topics:
    Re: Sort an array + more <usenet@morrow.me.uk>
    Re: Sort an array + more <odyniec-usenet@odyniec.net>
    Re: Sort an array + more (Anno Siegel)
    Re: Sort an array + more <krzys-iek@-----wp.pl>
    Re: Sort an array + more <krzys-iek@-----wp.pl>
    Re: Sort an array + more <usenet@morrow.me.uk>
        Digest Administrivia (Last modified: 6 Apr 01) (Perl-Users-Digest Admin)

----------------------------------------------------------------------

Date: Sat, 15 May 2004 23:38:17 +0000 (UTC)
From: Ben Morrow <usenet@morrow.me.uk>
Subject: Re: Sort an array + more
Message-Id: <c869l9$i1d$1@wisteria.csv.warwick.ac.uk>


Quoth "krzys-iek" <krzys-iek@-----wp.pl>:
>  Hi
> 
> I try to rewrite my AWK/BASH script (you can see it here:
> www.krionix.net/perl/ - aw file)
> This script reads passwd file (included there too) and displays only those
> people we want.
> They are placed in a nice, neat table.
> 
> You can run this script like that: ./aw 1000 1
> And 1000 is a group ID, we will see only people with GID=1000.
> 1 is a sorting rule (sort by login name, 2 sort by name, 3 sort by surname).
> 
> So we will see sorted (by login) people with GID 1000.
> 
> OK, now I would like to rewrite this script and use only PERL language no
> bash etc.
> 
> I have written something like this:
> 
> ------------------------------------------
> 
> #!/usr/bin/perl -w

use warnings is better than -w
You need use strict here too.

> 
> $file = 'passwd';

Are you sure this shouldn't be /etc/passwd? If you *are* reading the
system passwd database, you will probably be better off with setpwent
etc., and maybe the User::pwent module.

> open(INFO, $file);

Always check the return value of open.
It is better to use lexical FHs than barewords.
It is better to specify the mode to open the file in.

open my $INFO, '<', $file or die "can't open $file: $!";

You alse need

my @ludzie_z_1000;

since you're now using strictures. Why the weird name: wouldn't
something like @users be clearer?

> while (<INFO>)
> {
> chomp;

It is more usual to write perl programs like

while (<$INFO>) {

and you should *definitely* indent here. See perldoc perlstyle.

> (m/^([a-zA-Z0-9_]*):x:[0-9]+:$ARGV[0]:/) && (push @ludzie_z_1000, $_);

You want split for this (perldoc -f split).

my ($name, undef, $uid, $gid, $gecos) = split /:/;
my ($forename, $surname) = split ' ', $gecos, 2;

In this case since you are discarding the end of the line anyway the
chomp wasn't necessary.

Now you want to build a proper data structure. See perldoc perlreftut,
perldoc perldsc and perldoc perllol (probably in that order).

    push @ludzie_z_1000, {
        name     => $name,
        uid      => $uid,
        gid      => $gid,
        forename => $forename,
        surname  => $surname,
    };

> }
> 
> # @ludzie_z_1000 = sort @ludzie_z_1000;

my $key = ('name', 'forename', 'surname')[ $ARGV[1] ];

@ludzie_z_1000 = 
    sort { $a->{$key} <=> $b->{$key} }
    grep { $_->{gid} == $ARGV[0] }
    @ludzie_z_1000;

See perldoc -f sort and perldoc -f grep, and perldoc perlop for <=>. (I
may have the sort backwards: I never can remember which way it goes...)

> foreach (@ludzie_z_1000)
> {
> m/^([a-zA-Z0-9_]*):x:[0-9]+:$ARGV[0]:([^:,]*).*/;
> print "$1 | ";
> $_=$2;
> m/^([^ ]*) (.*)/;
> print "$1 | $2\n";
> }

my $i;
printf "| %-3d | %10s | %11s | %15s\n",
    ++$i, $_->{name}, $_->{forename}, $_->{surname}
    for @ludzie_z_1000;

I think this is the output format you want... your printf line below
doesn't match your given example output. See (surprise!) perldoc -f
printf, and perlop for ++. As a matter of style, I would probably have
put the grep/sort above in the same line, thus

printf "...",
    ++$i, ...,
    for sort { ... }
        grep { ... }
        @ludzie_z_1000;
        
but you probably find it clearer split into two statements.

Modifications I would consider that are trivial with Perl but perhaps
difficult in shell are allowing the group and the sort key to be
specified by name instead of number.

Ben

-- 
Every twenty-four hours about 34k children die from the effects of poverty.
Meanwhile, the latest estimate is that 2800 people died on 9/11, so it's like
that image, that ghastly, grey-billowing, double-barrelled fall, repeated
twelve times every day. Full of children. [Iain Banks]         ben@morrow.me.uk


------------------------------

Date: 16 May 2004 01:50:23 +0200
From: Michal Wojciechowski <odyniec-usenet@odyniec.net>
Subject: Re: Sort an array + more
Message-Id: <871xlldzcw.fsf@odyniec.odyniec.net>

"krzys-iek" <krzys-iek@-----wp.pl> writes:

> I do not how to sort it with perl sort function ( I must sort it by
> login or name or surname!).

[...]

> @ludzie_z_1000 = sort @ludzie_z_1000;
> this solves only sorting by login but what about name and surname?:/

If you do want to use an array for this task, see "perldoc -q sort",
"How do I sort an array by (anything)". However, I would recommend
using an array of hashes, since that would be easier to manage and
sort (see "How do I sort a hash (optionally by value instead of key)",
same doc).

> Next question, How can I do something like that:
> 
> printf "| %3s %2s %10s %2s %11s %2s %15s %2s \n", ile+1, "|", $1, "|", $2,
> "|", $3, "|"; ile+=1;}

Pretty much the same. Perl has printf.

HTH,
-- 
Michal Wojciechowski : for(<>){/\s/,$l{$m=$`}=$'}$_ : 10 PRINT "Yet another"
odyniec()odyniec;net : =$l{$c},/O\s/?$c=$'-1:y/"//d : 20 PRINT "Perl hacker"
 http://odyniec.net  : ,/T\s/?print$':0while$c++<$m : 30 GOTO 10


------------------------------

Date: 16 May 2004 00:28:43 GMT
From: anno4000@lublin.zrz.tu-berlin.de (Anno Siegel)
Subject: Re: Sort an array + more
Message-Id: <c86cjr$69k$1@mamenchi.zrz.TU-Berlin.DE>

krzys-iek <krzys-iek@-----wp.pl> wrote in comp.lang.perl.misc:
>  Hi
> 
> I try to rewrite my AWK/BASH script (you can see it here:
> www.krionix.net/perl/ - aw file)
> This script reads passwd file (included there too) and displays only those
> people we want.
> They are placed in a nice, neat table.
> 
> You can run this script like that: ./aw 1000 1
> And 1000 is a group ID, we will see only people with GID=1000.
> 1 is a sorting rule (sort by login name, 2 sort by name, 3 sort by surname).
> 
> So we will see sorted (by login) people with GID 1000.
> 
> OK, now I would like to rewrite this script and use only PERL language no
> bash etc.

The language is called 'Perl'.

> I have written something like this:
> 
> ------------------------------------------
> 
> #!/usr/bin/perl -w
> 
> $file = 'passwd';
> open(INFO, $file);
> 
> while (<INFO>)
> {
> chomp;
> (m/^([a-zA-Z0-9_]*):x:[0-9]+:$ARGV[0]:/) && (push @ludzie_z_1000, $_);
> }
> 
> # @ludzie_z_1000 = sort @ludzie_z_1000;
> 
> foreach (@ludzie_z_1000)
> {
> m/^([a-zA-Z0-9_]*):x:[0-9]+:$ARGV[0]:([^:,]*).*/;
> print "$1 | ";
> $_=$2;
> m/^([^ ]*) (.*)/;
> print "$1 | $2\n";
> }
> 
> ----------------------------------------
> 
> You can run it like that:  ./script 1000

What is the parameter (1000) doing?  The script doesn't look at it.

> This perl script works but I got stuck...
> 
> I do not how to sort it with perl sort function ( I must sort it by login or
> name or surname!).

What specific problem do you have with the sort function?  Have you read
what "perldoc -f sort" says?

> Can you help me how to do that? I can not use linux sort function (as in my
> ./aw script... damn)
> 
> @ludzie_z_1000 = sort @ludzie_z_1000;
> this solves only sorting by login but what about name and surname?:/
> 
> Next question, How can I do something like that:
> 
> printf "| %3s %2s %10s %2s %11s %2s %15s %2s \n", ile+1, "|", $1, "|", $2,
> "|", $3, "|"; ile+=1;}

Perl has a sprintf function.  You will need to parse the lines of the
password file into different variables.  There are modules on CPAN that
do that, Parse::Passwd is one.

> with perl language? You know, now my table does not look nice...
> 
> rafit | Rafal | Tomczak
> serwik | S | Serwik
> sq6elt | Pawel | Jarosz
> tyciu | Mateusz | Tykierko
> 
> This is a neat table/array I want :)
> 
> rafit    | Rafal      | Tomczak
> serwik | S           | Serwik
> sq6elt | Pawel     | Jarosz
> tyciu   | Mateusz  | Tykierko

That doesn't look much better in a monospace font.

You could use a couple of modules from CPAN to help you along.
Parse::Passwd has been mentioned.  Text::Table can build the table
without a sprintf format to maintain.  It remains to split the
gcos field into a first and last name, and to sort according to
the input parameter.  The following script does that:

    #!/usr/local/bin/perl
    use strict; use warnings; $| = 1;
    use Parse::Passwd;
    use Text::Table;

    # get arguments
    my $f = shift || '/etc/passwd';
    my $sort_field = shift || 1;
    $sort_field --;

    # extract username, first name, last name
    my $p = Parse::Passwd->file( $f) or
        die "Can't parse password file '$f'";
    my @ludzie_z_1000 =
        map [ $_->{ username}, split( ' ', $_->{ gcos_field}, 2) ], @$p;

    # build table
    my $tb = Text::Table->new( ( '',  \ ' | ') x 2, '');
    $tb->load(
        sort { $a->[ $sort_field] cmp $b->[ $sort_field] } @ludzie_z_1000,
    );
    print $tb;
    __END__

For a slightly more fancy table, with titles and a separator line,
create the table as

    my $tb = Text::Table->new( 'User',  \' | ', 'First', \' | ', 'Last');

and print it as

    print $tb->title;
    print $tb->rule( '-', '+');
    print $tb->body;

Anno


------------------------------

Date: Sun, 16 May 2004 13:10:44 +0200
From: "krzys-iek" <krzys-iek@-----wp.pl>
Subject: Re: Sort an array + more
Message-Id: <c87ich$86b$1@nemesis.news.tpi.pl>

Ben Morrow <usenet@morrow.me.uk> napisal nam:

>> $file = 'passwd';
>
> Are you sure this shouldn't be /etc/passwd? If you *are* reading the
> system passwd database, you will probably be better off with setpwent
> etc., and maybe the User::pwent module.

It should but I test it on my lockal PC and I use
www.krionix.net/perl/passwd file
because my own passwd is so small :)

>
>> open(INFO, $file);
>
> Always check the return value of open.
> It is better to use lexical FHs than barewords.
> It is better to specify the mode to open the file in.
>
> open my $INFO, '<', $file or die "can't open $file: $!";

Oh yes, I have forgotten!
It also solves problem with "passwd file access denyed"?
(when a regular user calls my script and he has no passwd access rights)

>
> since you're now using strictures. Why the weird name: wouldn't
> something like @users be clearer?

It would, I agree.

> and you should *definitely* indent here. See perldoc perlstyle.

OK

> You want split for this (perldoc -f split).

great function! thx

> my $key = ('name', 'forename', 'surname')[ $ARGV[1] ];
>
> @ludzie_z_1000 =
>     sort { $a->{$key} <=> $b->{$key} }

Hmm it did not work (at all) but perldoc -f sort and...
It works now :) [cmp instead of <=>]

> may have the sort backwards: I never can remember which way it
> goes...)

A -> Z
OK

>
> but you probably find it clearer split into two statements.

definitely (I am a noob...)

So final (?) script looks like that (questions at the end):

*------------------------------------------------------------*

#!/usr/bin/perl

$file = 'passwd';
open my $INFO, '<', $file or die "can't open $file: $!";

my @users;

while (<$INFO>) {
    my ($name, undef, $uid, $gid, $gecos) = split /:/;
    my ($forename, $surname_a) = split (/[ ]/, $gecos, 2);
    my ($surname) = split (/[,]/,$surname_a,2);

    push @users, {
        name     => $name,
        uid      => $uid,
        gid      => $gid,
        forename => $forename,
        surname  => $surname,
    };
}

my $key = ('name', 'forename', 'surname')[ $ARGV[1] ];

@users =
    sort { $a->{$key} cmp $b->{$key} }
    grep { $_->{gid} == $ARGV[0] }
    @users;

my $i;
printf " --------------------------------------------------\n";
printf "| #   |    login   |   forename  |     surname     |\n";
printf " --------------------------------------------------\n";
printf "| %-3d | %10s | %11s | %15s |\n",
    ++$i, $_->{name}, $_->{forename}, $_->{surname}
    for @users;
printf " --------------------------------------------------\n";

*------------------------------------------------------------------------*

#!/usr/bin/perl
I get *many* errors with perl -w switch ;[
Is it ok?!

Use of uninitialized value in split at ./ww line 11, <$INFO> line 15.
Use of uninitialized value in split at ./ww line 11, <$INFO> line 16.
Use of uninitialized value in split at ./ww line 11, <$INFO> line 18.

Use of uninitialized value in string comparison (cmp) at ./ww line 24,
<$INFO> line 1002.
Use of uninitialized value in printf at ./ww line 35, <$INFO> line 1002.

How do I get rid of that?
Now I use perl without switches but does it solve the problem in a proper
way...?

Thank you Ben :-)






------------------------------

Date: Sun, 16 May 2004 13:18:30 +0200
From: "krzys-iek" <krzys-iek@-----wp.pl>
Subject: Re: Sort an array + more
Message-Id: <c87in1$11b$1@atlantis.news.tpi.pl>

Anno Siegel <anno4000@lublin.zrz.tu-berlin.de> napisal nam:


> The language is called 'Perl'.
>

ok:)

>
> What is the parameter (1000) doing?  The script doesn't look at it.
>

$ARGV[0]?

> What specific problem do you have with the sort function?  Have you
> read
> what "perldoc -f sort" says?

Well, I am new to Perl and I read web tutorials. I didn't know that command.

> Perl has a sprintf function.  You will need to parse the lines of the
> password file into different variables.  There are modules on CPAN
> that
> do that, Parse::Passwd is one.
>

http://www.cpan.org/ ok I found it but... This task, as you can guess, is my
homework, using external modules other then those on "boss" linux is
forbidden :(

And: "Can't locate Parse/Passwd.pm in @INC..."
shi*...

> That doesn't look much better in a monospace font.
>

example :P

> You could use a couple of modules from CPAN to help you along.
> Parse::Passwd has been mentioned.  Text::Table can build the table
> without a sprintf format to maintain.  It remains to split the
> gcos field into a first and last name, and to sort according to
> the input parameter.  The following script does that:
>
<cut>

Anno, thank You, I have installed 3 modules and it works but as I said
no external modules :(

Bens sort trick did what I wanted,
in the future I will use ready modules because it is easy :)



------------------------------

Date: Sun, 16 May 2004 12:51:21 +0000 (UTC)
From: Ben Morrow <usenet@morrow.me.uk>
Subject: Re: Sort an array + more
Message-Id: <c87o49$2ej$1@wisteria.csv.warwick.ac.uk>


Quoth "krzys-iek" <krzys-iek@-----wp.pl>:
> #!/usr/bin/perl

You've left out the

use strict;
use warnings;

> $file = 'passwd';

  ^^ my $file
as your using strict.

> open my $INFO, '<', $file or die "can't open $file: $!";
> 
> my @users;
> 
> while (<$INFO>) {
>     my ($name, undef, $uid, $gid, $gecos) = split /:/;
>     my ($forename, $surname_a) = split (/[ ]/, $gecos, 2);
>     my ($surname) = split (/[,]/,$surname_a,2);

Personally I would use a pattern match here, rather than two splits:

my ($forename, $surname) = $gecos =~ /^(.*?) (.*?),/;

but it doesn't matter much. See perldoc perlretut "Matching repetitions"
for the meaning of *?, which you probably aren't familiar with. Also,
there's no need to use a character class in those regexes: / / and /,/
work just fine.

>     push @users, {
>         name     => $name,
>         uid      => $uid,
>         gid      => $gid,
>         forename => $forename,
>         surname  => $surname,
>     };
> }
> 
> my $key = ('name', 'forename', 'surname')[ $ARGV[1] ];
> 
> @users =
>     sort { $a->{$key} cmp $b->{$key} }
>     grep { $_->{gid} == $ARGV[0] }
>     @users;
> 
> my $i;
> printf " --------------------------------------------------\n";

Don't use printf when you could use print: it makes extra work for perl.
Also, it would probably be clearer to use a here-doc here (I can't find
where these are documented, except for a cursory mention in perlop:
anyone?)

print <<HEADING;
 --------------------------------------------------
| #   |   login    |  forename   |     surname     |
 --------------------------------------------------
HEADING

Or, as Anno said, you could use Text::Table, which would make the whole
thing much easier.

> printf "| #   |    login   |   forename  |     surname     |\n";
> printf " --------------------------------------------------\n";
> printf "| %-3d | %10s | %11s | %15s |\n",
>     ++$i, $_->{name}, $_->{forename}, $_->{surname}
>     for @users;
> printf " --------------------------------------------------\n";
> 
> *------------------------------------------------------------------------*
> 
> #!/usr/bin/perl
> I get *many* errors with perl -w switch ;[
> Is it ok?!
> 
> Use of uninitialized value in split at ./ww line 11, <$INFO> line 15.
> Use of uninitialized value in split at ./ww line 11, <$INFO> line 16.
> Use of uninitialized value in split at ./ww line 11, <$INFO> line 18.
> 
> Use of uninitialized value in string comparison (cmp) at ./ww line 24,
> <$INFO> line 1002.
> Use of uninitialized value in printf at ./ww line 35, <$INFO> line 1002.

These will be from entries which have fields missing, I expect. If you
are happy to have such entries display and sort as blanks then you can
put

no warnings 'uninitialized';

at the top just after the

use warnings;

line. See perldoc perllexwarn.

Ben

-- 
I must not fear. Fear is the mind-killer. I will face my fear and it will pass
through me, and there will be nothing. Only I will remain.
ben@morrow.me.uk                                          Frank Herbert, 'Dune'


------------------------------

Date: 6 Apr 2001 21:33:47 GMT (Last modified)
From: Perl-Users-Request@ruby.oce.orst.edu (Perl-Users-Digest Admin) 
Subject: Digest Administrivia (Last modified: 6 Apr 01)
Message-Id: <null>


Administrivia:

#The Perl-Users Digest is a retransmission of the USENET newsgroup
#comp.lang.perl.misc.  For subscription or unsubscription requests, send
#the single line:
#
#	subscribe perl-users
#or:
#	unsubscribe perl-users
#
#to almanac@ruby.oce.orst.edu.  

NOTE: due to the current flood of worm email banging on ruby, the smtp
server on ruby has been shut off until further notice. 

To submit articles to comp.lang.perl.announce, send your article to
clpa@perl.com.

#To request back copies (available for a week or so), send your request
#to almanac@ruby.oce.orst.edu with the command "send perl-users x.y",
#where x is the volume number and y is the issue number.

#For other requests pertaining to the digest, send mail to
#perl-users-request@ruby.oce.orst.edu. Do not waste your time or mine
#sending perl questions to the -request address, I don't have time to
#answer them even if I did know the answer.


------------------------------
End of Perl-Users Digest V10 Issue 6571
***************************************


home help back first fref pref prev next nref lref last post