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

Perl beginner, working on my first program as practice. At first, I thought that it worked, but it was quickly revealed that it was only functional under ideal circumstances.

The aim of the code is to concatenate the output of lsatter and ls -l on one line, without repeating the filename field, which is found in both. Good output looks like this:
-------------e-- drwxr-xr-x 2 root root 4096 Apr 24 12:02 cron.hourly
In which the first field is from lsattr and the rest from ls.

However, in some cases there will be files that present lsattr with errors, such as links. In which case, the output of lsattr was skipped because perl is not reading STDERR. So the entire output is wrong from that point on. What I would like to do is capture the STDERR and use an if statement to print N/A where lsattr is not relevant. However, using 2>&1 to catch the STDERR does not maintain to order in which the errors originally appeared, also ruining the output.

#!/usr/bin/perl $num_args = $#ARGV + 1; my @list = `ls -lU`; chomp (my @chattr = `lsattr 2>&1 | sort`); my $value = 0; my @chats; my $arg = $ARGV[0]; my $argVal = 0; my $index = 0; foreach my $item (@list) { @chats = split(/ /, $chattr[$value]); if(index($chats[0], "lsattr:") == -1) { print "$item $chats[0] "; } else { print "$item error"; } if (index($chats[0], $arg) != -1) { $argVal++; } $value++; } if ($num_args != 0) { print "$arg: $argVal"; } print "\n\n";

If you are confused regarding ARGV, the code also allows the user to input a chattr attribute and get a number of times that attribute appears.

My question then, of course, is how should I proceed with my code to correct the issues? I would like to stay away from cpan modules, if possible.

Replies are listed 'Best First'.
Re: STDERR, preserve order of
by kcott (Archbishop) on Sep 10, 2013 at 06:13 UTC

    G'day GillSans,

    Welcome to the monastery.

    I don't have the lsattr command on my OS; however, I can roughly emulate what I think you need. The ls -i command gives "inode filename"; I believe your lsattr command gives "attr filename". Those are close enough to show the technique. In the code below, I've just used splice to force a missing entry such as you get when you encounter a link.

    $ perl -Mstrict -Mwarnings -E ' chomp(my @lsl = qx{ls -l | tail -3}); chomp(my @lsi = qx{ls -i | tail -3}); splice @lsi, 1, 1; my $lsi_index = 0; for (@lsl) { my $file = (split / /)[-1]; my $pos = index $lsi[$lsi_index], $file; if ($pos == -1) { say " N/A ", $_; } else { say substr($lsi[$lsi_index++], 0, $pos), $_; } } ' 44060626 -rw-r--r-- 1 ken staff 0 10 Sep 15:02 zz1 N/A -rw-r--r-- 1 ken staff 0 10 Sep 15:02 zz2 44060628 -rw-r--r-- 1 ken staff 0 10 Sep 15:03 zz3

    I think you should be able to adapt that to your needs.

    Here's what those two commands would normally output on my OS:

    $ ls -l | tail -3; ls -i | tail -3 -rw-r--r-- 1 ken staff 0 10 Sep 15:02 zz1 -rw-r--r-- 1 ken staff 0 10 Sep 15:02 zz2 -rw-r--r-- 1 ken staff 0 10 Sep 15:03 zz3 44060626 zz1 44060627 zz2 44060628 zz3

    Update: I had extra whitespace. Removed "join " " =>" which got rid of the extra space, and serendipitously simplified the code example.

    -- Ken

Re: STDERR, preserve order of
by boftx (Deacon) on Sep 10, 2013 at 05:04 UTC

    This might be more of an off-the-wall approach (but I can't think of the right way to capture STDERR at the moment, so ...) but why not split the filename/dirname component out of the ls listing and use that as the key in a hash of hash refs and do the same with the output from the lsattr command?

    This is a very crude first approximation, but the output should give you some ideas. Any key that doesn't have an attr element can be presumed to have had STDERR output that you weren't using.

    #!/usr/bin/perl use strict; use warnings; use Data::Dumper; $Data::Dumper::Indent = 1; my @list = `ls -lU`; my @chattr = `lsattr`; my %combined; foreach my $attritem (@chattr) { chomp $attritem; my @attrdata = split(' ',$attritem); my $attritemname = pop(@attrdata); $attritemname =~ s{^\./}{}; $combined{$attritemname}{attr} = join(' ',@attrdata); } foreach my $item (@list) { chomp $item; my @lsdata = split(' ',$item); my $itemname = pop(@lsdata); $combined{$itemname}{ls} = join(' ',@lsdata); } print Dumper(\%combined); exit; __END__ $VAR1 = { 'fortest.pl' => { 'ls' => '-rwx------ 1 user group 547 Jun 26 22:57', 'attr' => '---------------' }, 'lsattrtest.pl' => { 'ls' => '-rwx------ 1 user group 587 Sep 9 23:10', 'attr' => '---------------' }, 'inctest.pl' => { 'ls' => '-rwx------ 1 user group 75 Aug 5 2012', 'attr' => '---------------' }, }, 'fiscaltest.pl' => { 'ls' => '-rwx------ 1 user group 247 Jul 17 15:34', 'attr' => '---------------' }, 'moosetest.pl' => { 'ls' => '-rwx------ 1 user group 72 Aug 15 00:53', 'attr' => '---------------' }, 'str2dt.pl' => { 'ls' => '-rwx------ 1 user group Jul 17 15:28', 'attr' => '---------------' } };

    UPDATE: corrected typo, added sample output.

Re: STDERR, preserve order of
by boftx (Deacon) on Sep 10, 2013 at 07:09 UTC

    I think the main thing to take away from the two responses you've had so far is that one must always remember TIMTOWTDI. Neither one of us answered your question directly, but instead, tried to tease out the actual objective and address that instead. I have little doubt there will be a few more that offer different ideas still, including one that might actually show how to correctly intersperse STDOUT with STDERR.

    That, in my opinion, is the essence of being a good software engineer: Find out what the end consumer really wants to accomplish and deliver that.

    I meant to address your comment that you want to avoid using a CPAN module. I presume this is because you want to learn how to do it yourself. That is an excellent idea at this point in time for you (if I can make certain assumptions.) Once you've experienced some of the royal PITAs it is easier to fully appreciate just what CPAN really does for us. :)

      I thank you both for your helpful responses. I will be analyzing both of the methods to see what I can learn from them. From running a quick trial of both, I can tell that there are many routes I could take. I've only been using Perl for two days, so it will take a bit to absorb all of what is going on.

      Yes, boxft, my aim is to start out away from cpan, mostly so that I can learn the ins and out of the language first, before adding all of that functionality. Start simple, build from there.

      Thanks