in reply to Looking to confirm a file against CVS as part of a loop

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.

Replies are listed 'Best First'.
Re^2: Looking to confirm a file against CVS as part of a loop
by Seventh (Beadle) on Dec 20, 2004 at 19:23 UTC
    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.

        I appreciate it. :D

        Here's what I came up with:
        #################################### ## READ LOOP ## #################################### if ($read) { my $fh2 = new IO::File("<ccrConfig.cfg"); return unless($fh2); my @state2 = <$fh2>; my $state2 = join("", @state2); foreach my $f (@state2) { print "File is $f\n"; my $logoutput = qx(cvs log $f); if ($logoutput eq "RCS") { `cvs co $f`; else { print "File $f is not in CVS, skipping\n"; } } }

        Running a cvs log on any of the files in the list always starts with "RCS file: ...", so what I'm trying to do is:

        "If a cvs log of the file returns a string that starts with RCS, then check it out, otherwise print out that it's missing".

        And I'm getting this error currently:
        $ Global symbol "$logoutput" requires explicit package name at ccrutil +.pl line 5 6. Global symbol "$logoutput" requires explicit package name at ccrutil.p +l line 57. syntax error at ccrutil.pl line 59, near "else" syntax error at ccrutil.pl line 63, near "}" Execution of ccrutil.pl aborted due to compilation errors.
        I must be screwing up the syntax, but from what I can tell, isn't "$string = "text" the proper syntax for "contains"?

        Thank you very much once again.
        Got it working - Thank you all very, very much for taking the time to help me out! =)