http://qs321.pair.com?node_id=906319

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

I am attempting to set up a program that will perform a set of user defined steps. Each step will be a seperate program, which could be anything (e.g. shell script, unix command, c++ program, perl program). My program will execute the specified command and check the return code. If the return code is 0, my program will move onto the next user defined step.

Here is my problem, any one of the specified steps could be a program that needs input from the user. What I want to do is have my program turn over control to the executed program (allowing the user to specify any necessary termial input) and return control back to me when it finishes. All I need from this is the return code of the executed process.

I've tried just using system() call, but that won't get STDIN from the user.

Any ideas?

Here is my program so far (ScriptEngine.pl)

#!/usr/bin/perl $scriptName = ''; $logFileName = ''; $numberOfSteps = ''; $restart = 'Y'; @step = (); $error = 0; foreach $_ (@configFile) { next if ($_ =~ /^\s*$/); next if ($_ =~ /^\s*#/); chop(); if ($_ =~ /^\s*ScriptName:\s+\S+\s*$/) { ($tag,$scriptName) = split(); } elsif ($_ =~ /^\s*LogFileName:\s+\S+\s*$/) { ($tag,$logFileName) = split(); } elsif ($_ =~ /^\s*NumberOfSteps:\s+[0-9]+\s*$/) { ($tag,$numberOfSteps) = split(); } elsif ($_ =~ /\s*Restart:\s+\S+\s*$/) { ($tag,$restart) = split(); } elsif ($_ =~ /^\s*Step\s+[0-9]:/) { $stepNum = $_; $stepNum =~ s/^\s*Step\s+([0-9]+):.*$/$1/; $command = $_; $command =~ s/^\s*Step [0-9]:+\s+//; $step[$stepNum] = $command; } else { print STDERR "Error: unknown config file line:\n"; print STDERR " $_\n"; $error = 1; } } for ($i=1;$i<=$numberOfSteps;$i++) { print "$i: $step[$i]\n"; $ret = system($step[$i]); if ($ret) { print STDERR "\nError: last step failed.\n"; exit(1); } }

Here is a sample script to call this program

#!/bin/ksh ScriptEngine.pl << ! ScriptName: testScript LogFileName: boing NumberOfSteps: 4 Restart: N Step 2: ls -l Step 1: $HOME/bin/gettime Step 4: ps -elf | grep wam Step 3: /tmp/thing !

Here is the /tmp/thing script that asks for input

#!/bin/ksh print "This is the output." print "You need to enter a value: " read ans print "You entered the value of $ans." exit 1

When it runs the /tmp/thing script I am not given the chance to enter the requested input

Replies are listed 'Best First'.
Re: How to "transfer control" to child process?
by John M. Dlugosz (Monsignor) on May 23, 2011 at 17:36 UTC
    How about posting a trivial example of a program that calls system to execute another program which reads input? I would think that it should work, so let's see the actual code to diagnose the problem.

    Update: I see you amended the original post.

Re: How to "transfer control" to child process?
by John M. Dlugosz (Monsignor) on May 23, 2011 at 19:39 UTC
    Well, the first thing anyone will tell you is
    use warnings; use strict;
    and see if Perl itself starts telling about anything odd or finding any mistakes.

    I see that you invoke the program with a line containing << !. That changes STDIN to read from something else (I'm not familiar enough with ksh to know if ! has a special meaning or is the name of a file). That stays in effect through all the programs called down through system, so the use of STDIN won't prompt the user but will keep reading from ! (whatever that is).

    What is the purpose of that? I don't see your main script reading anything from STDIN itself.

Re: How to "transfer control" to child process?
by John M. Dlugosz (Monsignor) on May 23, 2011 at 20:09 UTC
    I have some comments on the script itself, even though that's not what you were posting about.

    I already mentioned use strict; use warnings. But also, what version of Perl are you using? If it is reasonably up to date, you can use the new "given/when" construct.

    You are not declaring your variables. This will be caught by strict, and mis-spelled variables is one of the easiest mistakes to make and find automatically.

    You are using explicit variable name in constructs that use $_ implicitly, but still using $_ for the variable. If you are going to use a name, use a named local variable. Or if you use $_, leave it off. For serious programming (as opposed to quick one liners or one-time-use scripts) you use the explicit form as you did, but use a name.

    foreach my $line (@configFile) { next if $line =~ /^\s*$/; ...
    You don't need the parens around the condition in the suffix (statement modifier) form. I don't see where @configFile is coming from. It will be empty! So there might be stuff you're not showing in the example.

    Now the cool up-to-date stuff. If you use Perl 5.10 or higher, you can use given/when. But, that works with a foreach as well, so I'll revert to using the implicit $_ form:

    foreach (@configFile) { chomp; # careful of last line in file next if /^\s*$/; # empty line next if /^\s*#/; # comment line when (/^\s*ScriptName:\s+\S+\s*$/) { ($tag,$scriptName) = split(); } when (/^\s*LogFileName:\s+\S+\s*$/) { ($tag,$logFileName) = split(); } when (/^\s*NumberOfSteps:\s+[0-9]+\s*$/) { ($tag,$numberOfSteps) = split(); } when (/\s*Restart:\s+\S+\s*$/) { ($tag,$restart) = split(); } when (/^\s*Step\s+[0-9]:/) { $stepNum = $_; $stepNum =~ s/^\s*Step\s+([0-9]+):.*$/$1/; $command = $_; $command =~ s/^\s*Step [0-9]:+\s+//; $step[$stepNum] = $command; } default { print STDERR "Error: unknown config file line:\n"; print STDERR " $_\n"; $error = 1; } }
    Now the use of split splits on whitespace, but you already went through the string with the regexp and found the space. So use the results of the regexp instead of splitting again:
    when (/^\s*(ScriptName:)\s+(\S+)\s*$/) { $tag= $1; $scriptName= $2; }
    Note that you are requiring a space after the colon and before the name. If that is an artefact of your use of the simple split, you don't need that this way. Change the + to a * if you like.

    Note finally that [0-9] is common enough to have an optimized abbreviation: \d for digit.

    The stuff for Step is rather convoluted. In the same mold that I just showed, try:

    when (/^\s*Step\s+(\d):\s*(.*)/) { $stepNum = $1; $command = $2; $step[$stepNum] = $command; }
    But there is really no reason to note the $tag in these lines, as it was just a byproduct of your use of split. This limits you to 10 steps (0 through 9) as single digits. If you used \d+ it would be open ended.

    But your following loop starts at 1, not zero. So even though you accept a step 0, you never use it.

    There's really no need to specify the "number of steps". Just use whatever is in the array when you've read it:

    foreach my $command (@step) { next unless defined $command; # skip unused numbers my $ret= system ($command); ... }

      Thank you for taking the time to look this over. The problem was in the script that I was using to invoke my program. When I called my program using the -C option to specify the configuration file, instead of reading it from STDIN, it worked as I expected. (reading the file was from a section of code I didn't include)

      The version of Perl I am using is 5.8.4, which is the standard version for Solaris 10. Using 5.10 is not an option.

      My coding style is not as tight a some people. I have a tendancy to be more verbose in my coding than others, but this is born out of a necessity to make the code understandable to people who will have to maintain it after me.

      Yes, there some bugs in the section reading the configuration file which I had not yet fully corrected. My focus was on trying to figure out why the system call wasn't working as I expected it to work. Thanks for pointing those out.

      Thanks again for your help and for your time.