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

Hello, I am trying to take the code below and make it so that if it does not find the user $login instead of just ignoring it, it will put a null or X if you will, if it is not found in that particular /etc/passwd file onto the end of the line. The output should look something like this:

waj4356:server1:server2:X:server4:server5:X:X:server8:server9 zaw8732:X:server2:server3:server4:X:server6:X:server8:X cvf6609:X:X:X:X:server5:server6:server7:X:X
Instead of like this:
waj4356:server1:server2:server4:server5:server8:server9 zaw8732:server2:server3:server4:server6:server8 cvf6609:server5:server6:server7
So that when I split via : delimited I have a standardized type form of 9 servers, X's for the ones the ID is not present in and the server name for the ones in which it is. Instead of using if exists maybe an elseif or other loop type condition would be better, I would appreciate any suggestions. Thanks Again

#!/usr/bin/perl -w use strict; my %USERS; foreach my $file (<passwds.*>) { open(PASSWD,$file); my $strip=$file; #strips passwd. from passwd.server $strip =~ s/passwds\.//; while(<PASSWD>) { my($login,$gcos) = (split(':',$_))[0,4]; if(exists $USERS{$login}) { push(@{$USERS{$login}},$strip); } else { $USERS{$login} = [$strip]; } } close(PASSWD); } open(NEWFILE,">file3") || print "Can't open file3: $!\n"; foreach my $login (sort keys %USERS) { print NEWFILE "$login:".shift(@{$USERS{$login}}).":"; print NEWFILE join(':',@{$USERS{$login}})."\n"; } close(NEWFILE); print "the Active Servers listing is done\n";

Replies are listed 'Best First'.
How to ask for help
by Limbic~Region (Chancellor) on Nov 24, 2003 at 17:02 UTC
    tux242,
    You have been working on this problem at least since 30 October. A lot of monks have spent a great deal of time both in replying to your posts and in the CB. I am more than willing to help you and even avoid modules if you will take the time to explain in clear and concise requirements what your ultimate goal is.

    Here is an example of what I mean, which may or may not be what you are after.

    EXAMPLE
    I have been given the daunting task of ensuring uniformity of the user accounts and passwords across multiple *nix systems. I have already extracted a copy of all the /etc/passwd files from each of the servers and have used the following syntax for identifying them - passwd.hostname

    I am trying to avoid using modules due to a number of reasons. The two biggest reasons is that my employer has made it a requirement and I need to be able to explain the code line by line. I do not mind "borrowing" ideas from modules, but ultimately the code needs to be my own.

    So the type information I need to get out of my program is as follows:

  • Account x does not exist on hostname1 or hostname2
  • Account y's UID on hostname1 is #, but is # on hostname2
  • Account j's password on hostname1 is not the same as all the other machines.

    So basically, I need to compare every field against every other field as well as make provisions for the possibility the field won't exist at all on certain servers. My first hurdle is that the encrypted password is not stored in /etc/passwd but in /etc/shadow. I am using shadow.hostname syntax for that.

    Here is what I have come up with so far:

    #!/usr/bin/perl -w use strict; my @hosts = qw(zeus mercury athena); my (@p_files, @s_files); my %data; for my $host ( @hosts ) { push @p_files , 'passwd.' . $host; push @s_files , 'shadow.' . $host; } # Process /etc/passwd files FILE: for my $file ( @p_files ) { if ( ! open (PASSWD, $file) ) { warn "Unable to open $file : $!"; next FILE; } LINE: while ( <PASSWD> ) { my @fields = split /:/; if ( @fields != 7 ) { warn "$_ in file $file is corrupt"; next LINE; } $data{$fields[0]}->{$file} = \@fields; } } # Process /etc/shadow files FILE: for my $file ( @s_files ) { if ( ! open (SHADOW, $file) ) { warn "Unable to open $file : $!"; next FILE; } LINE: while ( <SHADOW> ) { # Not sure what to do here # but I know I need to modify # $data{account}->{$file}[1] } } # Need code help here to do all the comparisons I need
    Additionally, any help or insight would be appreciated. I am rather new to Perl so I understand this might not be the best approach. If you do choose to suggest an alternative approach, keep in mind my requirements I stated above
    END EXAMPLE

    I can guarantee tux242 that if you word take the time to pose a question like this you will be much more likely to get answers that you are seeking.

    Cheers - L~R

Re: Standardized Template
by dragonchild (Archbishop) on Nov 24, 2003 at 15:52 UTC
    Why are you hand-parsing this stuff? Check out CPAN for better solutions. A quick search found AnyData::Format::Passwd and Parse::Passwd as two very promising possibilities. Others might include Unix::PasswdFile or Passwd::DB, though I haven't actually read their POD.

    Once you use a module for the actual parsing, I suspect your issue will disappear.

    ------
    We are the carpenters and bricklayers of the Information Age.

    The idea is a little like C++ templates, except not quite so brain-meltingly complicated. -- TheDamian, Exegesis 6

    ... strings and arrays will suffice. As they are easily available as native data types in any sane language, ... - blokhead, speaking on evolutionary algorithms

    Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Re: Standardized Template
by Roy Johnson (Monsignor) on Nov 24, 2003 at 16:47 UTC
    The reason you are not getting all the entries is that some entries don't exist in some of the files. If you want all of the logins to be handled for all of the files, you will need to keep a global inventory of all the files and all the logins.

    While using one of the modules designed to parse this data might be a good idea, it does not solve your problem.

    Here's an untested rewrite:

    my %USERS; my @all_strips = (); ## <-- new foreach my $file (<passwds.*>) { open(PASSWD,$file) or warn "Could not open $file:$!\n", next; my $strip=$file; #strips passwd. from passwd.server $strip =~ s/passwds\.//; push(@all_strips, $strip); ## <-- new while(<PASSWD>) { my($login,$gcos) = (split(':',$_))[0,4]; $USERS{$login}{$strip} = 1; ## This way you keep track of ## which login/strip combinations ## have been seen } close(PASSWD); } open(NEWFILE,">file3") or die "Can't open file3: $!\n"; foreach my $login (sort keys %USERS) { print NEWFILE "$login:", # Substitute X in where no strip was found join(':', map { $USERS{$login}{$_} ? $_ : 'X' } @all_strips), "\n"; } close NEWFILE; print "the Active Servers listing is done\n";