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

Hi all,

I have a script that I'm working on - that thanks to all of you fine people here, works really well for what I'm looking to do. :D

What it does is read the current directory structure, strip out the CVS files and spit out a list to a file. It then reads that file back in, and does a 'cvs co $f' on each line.

The way I'm looping it is as such:

foreach my $f (@state2) { print "File is $f\n"; # swap this with cvs co command `cvs co $f`; }
What I'd like to add to this is to have the loop run a 'cvs log filename.xx' on each file read, checkout the file if it exists in CVS, and spit back a small blurb saying "File: filename.xx does not exist in CVS tree".

If I do a 'cvs log ccrutil.pl' (the script i'm writing) I get the following:
$ cvs log ccrutil.pl RCS file: /home/cvs/d600/ccrtest/ccrutil.pl,v Working file: ccrutil.pl

I say this every time, but it needs mentioning: I'm very, very new at this and 99% of what I know comes google, O'Reilley books and the slew of questions that you guys have been kind enough to help me out with. :)

Any insight is as always very, very much appreciated. Thanks!

Replies are listed 'Best First'.
Re: Looking to confirm a file against CVS as part of a loop
by revdiablo (Prior) on Dec 20, 2004 at 18:07 UTC

    There are a few different ways to execute a program and capture its output. Which you want depends on what you're doing. In this case, it looks like you're using trusted data, so simply using qx (which is the generic form of backticks) would be the easiest. Here's a simple example:

    for (glob "*.txt") { my $lsoutput = qx(ls -l $_); if ($lsoutput =~ /$ENV{USER}/) { print "File matches current username.\n"; } }

    In your case, the filename variable is probably safe. But if it came from user input (say, through a web form), then using qx would be a security problem. That's because qx will run your external program from a shell, and the shell will interpret any meta-characters. So if someone gives the filename as "`rm -rf /home`", then the shell would see the backtick metacharacters, and execute the program between them. This is obviously a bad thing.

    The other common way to read output from an external program is to use the piping form of open. Just for completeness, here's an equivalent snippet using open:

    for (glob "*.txt") { open my $lsfh, "-|", 'ls', '-l', $_ or die "Cannot open 'ls': $!\n"; while (<$lsfh>) { if ($lsoutput =~ /$ENV{USER}/) { print "File matches current username.\n"; } } }

    This one takes slightly more work to write, but one major benefit is safety. The list form of open avoids the metacharacter problem, because it doesn't call a shell at all.

      The list that it's reading from is indeed trusted, so data security isn't an issue - But I'll try to use the more secure method as best practice.

      I think I may have been a bit unclear - I already have a directory list that I'm reading in from a file, and for each entry in that list, I'm running a 'cvs co filename.xx' on it.

      The list is generated by the end user, and will as such have files that aren't project specific in it. (Scratch files, ideas, temp files, etc).

      What I'm looking to do is before I run the 'cvs co filename.xx', is to somehow check the 'cvs log filename.xx' command and capture the result of that, then have something along the lines of:

      If: (CVS Log returns that the file is valid, version info, etc) - cvs co (the file).

      Else: Print "That file's not in the tree, and Seventh asks too many silly questions on Perlmonks.org'.

      Here's the whole script, what I'm looking to do is edit the read loop:
      #!/usr/bin/perl -w # # Author: Chris Q # Date: 12/2004 # Desc: Common CR master util # # Key: [ ] - Not yet started # [%] - Partially complete, (known to be broken) # [!] - Complete, but undebugged # [X] - Complete # [?] - Thinking on it (don't know how yet) # [-] - Hell with it. # ToDo: # [ ] - Single character switches # [X] - Help Link # [X] - Empty Variable # # Bugs: # [ ] - # # Questions: # [ ] - use Getopt::Long; use strict; use Data::Dumper; use IO::File; my ($read,$generate,$help); # option switches GetOptions ( "help" => \$help, "read" => \$read, "generate" => \$generate, ); if (!$read && !$help && !$generate) { print "\nMissing Switch. Run with -help for options.\n"; } #################################### ## READ LOOP ## #################################### if ($read) { my $fh2 = new IO::File("; my $state2 = join("", @state2); foreach my $f (@state2) { print "File is $f\n"; # swap this with cvs co command `cvs co $f`; } } ################################### ## GEN LOOP ## ################################### if ($generate) { system ("clear"); print "Building project config.\n\n"; sub find { my ( $dir ) = @_; my $dh; my @theseFiles; + opendir $dh, $dir or return; while(my $file = readdir $dh ) { next if $file =~ /^\.{1,2}$/; my $full_file = "$dir/$file"; if ( -d $full_file ) { push(@theseFiles,find($full_file)); } else { push(@theseFiles,$full_file); } } closedir $dh; return @theseFiles; } my @files = find('.'); my @noCvsDirs; foreach my $file (@files) { push(@noCvsDirs,$file) unless(($file =~ /\/CVS\//) || ($file = +~ /\~/)); } my $state = join("\n",map { s{^\./}{}; $_} @noCvsDirs); my $fh = new IO::File(">ccrConfig.cfg"); die("Checkpoint failed for ccrConfig.cfg - $!\n") unless($fh); my $bytes = $fh->syswrite($state, length($state)); $fh->close(); unless($bytes == length($state)) { die("Checkpoint failed, incomplete write for ccrConfig.cfg\n") +; } else { print "Saved $bytes bytes into file: ccrConfig.cfg\n"; } exit; } ############################### ## Help Loop ## ############################### if ($help) { system ("clear"); print "Common Code Repository Utility v1.1\n\n"; print "|| ccrutil -generate ||\n\n"; print "Generates a project configuration file named\n"; print "ccrConfig.cfg to be included in your project.\n"; print "This file contains a list of all project files\n"; print "and related CCR includes, omitting temporary\n"; print "files and CVS control files.\n\n"; print "|| ccrutil -read ||\n\n"; print "Reads in a ccrConfig.cfg file from a project,\n"; print "and checks out any associated files.\n\n"; }
      Does that make sense?

      Thank you very much once again - I really appreciate the help and apologize in advance if I'm missing something blatantly obvious as a result of my inexperience. :)
        Does that make sense?

        It makes perfect sense, and I understood the original question. I didn't want to give you the answer directly, though. I wanted to give you the tools to help get the answer on your own. If you're unsure how to use the tools I gave you, please ask, but I'm not going to do your work for you. :-)

        PS: As a hint, you are probably going to want to use one of the mothods I showed you to capture the output, then check if the output looks like a success or a failure. How exactly you do that is up to you.