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

Dear Monks,
I have written a PERL script to automate some repetitive Linux (BASH) commands (I suppose I could have written a shell script but I have had little experience doing that). What it is supposed to do is read in every file in a directory rename each file and then execute it (using a program called MODELLER). I am aware that lots of file overwriting is going to take place and am fine about this (I have used cp -f to force copying). Unfortunately no copying is taking place in the specified directory (or anywhere else that I can see) can anyone suggest why?
do { opendir(DIR, $dirname) or die "can't opendir $dirname: $!"; while (defined($file = readdir(DIR))) { system("/bin/cp -f $dirname/$file malign.top"); system("/bin/cp -f $dirname/$file get-model.top"); system("/usr/local/modeller6v2/bin/mod6v2 $dirname/malign.top"); system("/usr/local/modeller6v2/bin/mod6v2 $dirname/get-model.top"); $m++; } } until ($m==2); #want the whole process to occur twice + closedir(DIR); exit;
Humbly submitted

Replies are listed 'Best First'.
Re: automating linux commands in perl
by wirrwarr (Monk) on Sep 05, 2003 at 13:38 UTC
    You copy the files to your current directory, whatever that is at the moment. You probably should use
    "/bin/cp -f $dirname/$file $dirname/malign.top".

    daniel.
Re: automating linux commands in perl
by bobn (Chaplain) on Sep 05, 2003 at 13:38 UTC

    system() has a return code. You should always test it after a command.

    --Bob Niederman, http://bob-n.com

    All code given here is UNTESTED unless otherwise stated.

Re: automating linux commands in perl
by Roger (Parson) on Sep 05, 2003 at 13:51 UTC
    You could try the following shell script:
    #!/bin/bash do_modeling() { FILES=`ls ./`; for f in $FILES;; do echo $f cp -f $f malign.top cp -f $f get-model.top /usr/local/modeller6v2/bin/mod6v2 malign.top /usr/local/modeller6v2/bin/mod6v2 get-model.top done } do_modeling do_modeling
Re: automating linux commands in perl
by wufnik (Friar) on Sep 05, 2003 at 16:36 UTC
    hola.

    i know why your script does not work.

    it is not "system", not cp, not the other program that is used.
    tho the advice pertaining to these is good, they are not the culprit. the culprit is readdir

    here is why.

    the first two 'files' returned by readdir are "." and "..". when you use readdir, you should probably do something like:
    @fils = grep { -f $dirname . $_ } readdir DIR;
    as it is, you start with .
    you increment m after a failed cp:
    you go on to the second file, .., and increment m again
    and before you know it, your counter has reached the limit at which your do loop is broken out of.

    if you use the construct above, all will be well.

    best wishes,

    ...wufnik

    -- in the world of the mules there are no rules --
      I think what you meant to suggest was:
      @fils = grep { -f "$dirname/$_" } readdir DIR;
      (I suspect that the value of $dirname does not end in a slash, so just concatenating it with the file name would be wrong.)
Re: automating linux commands in perl
by blokhead (Monsignor) on Sep 05, 2003 at 15:31 UTC
    You should only use the single-argument form of system if you have complete control over the strings that are interpolated in the system call. On the practical side, your current script won't work if there are spaces, quotation marks, or other special characters in the filenames.

    But maybe more importantly, passing unchecked data directly to the shell is a security no-no. In your case, anyone who can create a file within the working directories of this script can execute any command as the user executing the script. So for example, if you plan to use this script from root's crontab and parse through some data files in joe's home directory, joe could very quickly do some nasty things (i.e, touch "; cat /etc/shadow | mail joe ;")

    OK, maybe this example is a little far fetched for this innocuous script, but it's a good habit to avoid single-argument system... if only for the reason that spaces and quotation marks in filenames won't Just Work. Better to use the multi-argument form which bypasses the shell, solving all of these problems:

    system("/bin/cp", "-f", "$dirname/$file", "malign.top"); system("/bin/cp", "-f", "$dirname/$file", "get-model.top"); system("/usr/local/modeller6v2/bin/mod6v2", "$dirname/malign.top"); system("/usr/local/modeller6v2/bin/mod6v2", "$dirname/get-model.top");
    Now the arguments get directly passed via exec, and not through the shell, so the filenames can contain any stuff at all (including spaces & quotes) and the script still works as you'd expect (without doing anything unexpected/insecure).

    blokhead

Re: automating linux commands in perl
by graff (Chancellor) on Sep 06, 2003 at 03:09 UTC
    You say you "want the whole process to occur twice", which I assume means that you want two iterations over this sequence:
    • open the directory
    • make two copies of every file
    • run "mod6v2" on each copy

    That actually looks like you're running the one process four times on each file (each set of data) in $dirname. Okay, whatever.

    But if this is really what you want, you should move the "$m++" so that it is outside the while loop. Also, it would probably look more sensible if you moved the "closedir(DIR)" so that it is inside the do ... until loop.

    (update: Forgot to mention: please use proper indentation -- it really does help.)

Re: automating linux commands in perl
by blue_cowdawg (Monsignor) on Sep 05, 2003 at 14:41 UTC

    First I would suggest that instead of using the system call that you use File::Copy instead to copy the files.

    Secondly as pointed out elsewhere in the thread check the return code generated by system where you are executing /usr/local/modeller6v2/bin/mod6v2 for errors.


    Peter L. Berghold -- Unix Professional
    Peter at Berghold dot Net
       Dog trainer, dog agility exhibitor, brewer of fine Belgian style ales. Happiness is a warm, tired, contented dog curled up at your side and a good Belgian ale in your chalice.