Perl script to run a C executable with an argument while giving standard input through a file? - perl

I want to run and executable ./runnable on argument input.afa. The standard input to this executable is through a file finalfile. I was earlier trying to do the same using a bash script, but that does not seem to work out. So I was wondering whether Perl provides such functionality. I know I can run the executable with its argument using backticks or system() call. Any suggestions on how to give standard input through file.
_ UPDATE _
As I said I had written a bash script for the same. I not sure how to go about doing it in Perl. The bash script I wrote was:
#!/bin/bash
OUTFILE=outfile
(
while read line
do
./runnable input.afa
echo $line
done<finalfile
) >$OUTFILE
The data in standard input file is as follows, where each line correspond to one time input. So if there are 10 lines then the executable should run 10 times.
__DATA__
2,9,2,9,10,0,38
2,9,2,10,11,0,0
2,9,2,11,12,0,0
2,9,2,12,13,0,0
2,9,2,13,0,1,4
2,9,2,13,3,2,2
2,9,2,12,14,1,2

If I understood your question correctly, then you are perhaps looking for something like this:
# The command to run.
my $command = "./runnable input.afa";
# $command will be run for each line in $command_stdin
my $command_stdin = "finalfile";
# Open the file pointed to by $command_stdin
open my $inputfh, '<', $command_stdin or die "$command_input: $!";
# For each line
while (my $input = <$inputfh>) {
chomp($input); # optional, removes line separator
# Run the command that is pointed to by $command,
# and open $write_stdin as the write end of the command's
# stdin.
open my $write_stdin, '|-', $command or die "$command: $!";
# Write the arguments to the command's stdin.
print $write_stdin $input;
}
More info about opening commands in the documentation.

Perl code:
$stdout_result = `exescript argument1 argument2 < stdinfile`;
Where stdinfile holds the data you want to be passed through stdin.
edit
The clever method would be to open stdinfile, tie it via select to stdin, and then execute repeatedly. The easy method would be to put the data you want to pass through in a temp file.
Example:
open $fh, "<", "datafile" or die($!);
#data = <$fh>; #sucks all the lines in datafile into the array #data
close $fh;
foreach $datum (#data) #foreach singluar datum in the array
{
#create a temp file
open $fh, ">", "tempfile" or die($!);
print $fh $datum;
close $fh;
$result = `exe arg1 arg2 arg3 < tempfile`; #run the command. Presumably you'd want to store it somewhere as well...
#store $result
}
unlink("tempfile"); #remove the tempfile

Related

How to print result STDOUT to a temporary blank new file in the same directory in Perl?

I'm new in Perl, so it's maybe a very basic case that i still can't understand.
Case:
Program tell user to types the file name.
User types the file name (1 or more files).
Program read the content of file input.
If it's single file input, then it just prints the entire content of it.
if it's multi files input, then it combines the contents of each file in a sequence.
And then print result to a temporary new file, which located in the same directory with the program.pl .
file1.txt:
head
a
b
end
file2.txt:
head
c
d
e
f
end
SINGLE INPUT program ioSingle.pl:
#!/usr/bin/perl
print "File name: ";
$userinput = <STDIN>; chomp ($userinput);
#read content from input file
open ("FILEINPUT", $userinput) or die ("can't open file");
#PRINT CONTENT selama ada di file tsb
while (<FILEINPUT>) {
print ; }
close FILEINPUT;
SINGLE RESULT in cmd:
>perl ioSingle.pl
File name: file1.txt
head
a
b
end
I found tutorial code that combine content from multifiles input but cannot adapt the while argument to code above:
while ($userinput = <>) {
print ($userinput);
}
I was stucked at making it work for multifiles input,
How am i suppose to reformat the code so my program could give result like this?
EXPECTED MULTIFILES RESULT in cmd:
>perl ioMulti.pl
File name: file1.txt file2.txt
head
a
b
end
head
c
d
e
f
end
i appreciate your response :)
A good way to start working on a problem like this, is to break it down into smaller sections.
Your problem seems to break down to this:
get a list of filenames
for each file in the list
display the file contents
So think about writing subroutines that do each of these tasks. You already have something like a subroutine to display the contents of the file.
sub display_file_contents {
# filename is the first (and only argument) to the sub
my $filename = shift;
# Use lexical filehandl and three-arg open
open my $filehandle, '<', $filename or die $!;
# Shorter version of your code
print while <$filehandle>;
}
The next task is to get our list of files. You already have some of that too.
sub get_list_of_files {
print 'File name(s): ';
my $files = <STDIN>;
chomp $files;
# We might have more than one filename. Need to split input.
# Assume filenames are separated by whitespace
# (Might need to revisit that assumption - filenames can contain spaces!)
my #filenames = split /\s+/, $files;
return #filenames;
}
We can then put all of that together in the main program.
#!/usr/bin/perl
use strict;
use warnings;
my #list_of_files = get_list_of_files();
foreach my $file (#list_of_files) {
display_file_contents($file);
}
By breaking the task down into smaller tasks, each one becomes easier to deal with. And you don't need to carry the complexity of the whole program in you head at one time.
p.s. But like JRFerguson says, taking the list of files as command line parameters would make this far simpler.
The easy way is to use the diamond operator <> to open and read the files specified on the command line. This would achieve your objective:
while (<>) {
chomp;
print "$_\n";
}
Thus: ioSingle.pl file1.txt file2.txt
If this is the sole objective, you can reduce this to a command line script using the -p or -n switch like:
perl -pe '1' file1.txt file2.txt
perl -ne 'print' file1.txt file2.txt
These switches create implicit loops around the -e commands. The -p switch prints $_ after every loop as if you had written:
LINE:
while (<>) {
# your code...
} continue {
print;
}
Using -n creates:
LINE:
while (<>) {
# your code...
}
Thus, -p adds an implicit print statement.

Capture the output of Perl's 'system()'

I need to run a shell command with system() in Perl. For example,
system('ls')
The system call will print to STDOUT, but I want to capture the output into a variable so that I can do future processing with my Perl code.
That's what backticks are for. From perldoc perlfaq8:
Why can't I get the output of a command with system()?
You're confusing the purpose of system() and backticks (``). system()
runs a command and returns exit status information (as a 16 bit value:
the low 7 bits are the signal the process died from, if any, and the
high 8 bits are the actual exit value). Backticks (``) run a command
and return what it sent to STDOUT.
my $exit_status = system("mail-users");
my $output_string = `ls`;
See perldoc perlop for more details.
IPC::Run is my favourite module for this kind of task. Very powerful and flexible, and also trivially simple for small cases.
use IPC::Run 'run';
run [ "command", "arguments", "here" ], ">", \my $stdout;
# Now $stdout contains output
Simply use similar to the Bash example:
$variable=`some_command some args`;
That's all. Notice, you will not see any printings to STDOUT on the output because this is redirected to a variable.
This example is unusable for a command that interact with the user, except when you have prepared answers. For that, you can use something like this using a stack of shell commands:
$variable=`cat answers.txt|some_command some args`;
Inside the answers.txt file you should prepare all answers for some_command to work properly.
I know this isn't the best way for programming :) But this is the simplest way how to achieve the goal, specially for Bash programmers.
Of course, if the output is bigger (ls with subdirectory), you shouldn't get all output at once. Read the command by the same way as you read a regular file:
open CMD,'-|','your_command some args' or die $#;
my $line;
while (defined($line=<CMD>)) {
print $line; # Or push #table,$line or do whatever what you want processing line by line
}
close CMD;
An additional extended solution for processing a long command output without extra Bash calling:
my #CommandCall=qw(find / -type d); # Some example single command
my $commandSTDOUT; # File handler
my $pid=open($commandSTDOUT),'-|'); # There will be an implicit fork!
if ($pid) {
#parent side
my $singleLine;
while(defined($singleline=<$commandSTDOUT>)) {
chomp $line; # Typically we don't need EOL
do_some_processing_with($line);
};
close $commandSTDOUT; # In this place $? will be set for capture
$exitcode=$? >> 8;
do_something_with_exit_code($exitcode);
} else {
# Child side, there you really calls a command
open STDERR, '>>&', 'STDOUT'; # Redirect stderr to stdout if needed. It works only for child - remember about fork
exec(#CommandCall); # At this point the child code is overloaded by an external command with parameters
die "Cannot call #CommandCall"; # Error procedure if the call will fail
}
If you use a procedure like that, you will capture all procedure output, and you can do everything processing line by line. Good luck :)
I wanted to run system() instead of backticks because I wanted to see the output of rsync --progress. However, I also wanted to capture the output in case something goes wrong depending on the return value. (This is for a backup script). This is what I am using now:
use File::Temp qw(tempfile);
use Term::ANSIColor qw(colored colorstrip);
sub mysystem {
my $cmd = shift; # "rsync -avz --progress -h $fullfile $copyfile";
my ($fh, $filename) = tempfile();
# http://stackoverflow.com/a/6872163/2923406
# I want to have rsync progress output on the terminal AND capture it in case of error.
# Need to use pipefail because 'tee' would be the last cmd otherwise and hence $? would be wrong.
my #cmd = ("bash", "-c", "set -o pipefail && $cmd 2>&1 | tee $filename");
my $ret = system(#cmd);
my $outerr = join('', <$fh>);
if ($ret != 0) {
logit(colored("ERROR: Could not execute command: $cmd", "red"));
logit(colored("ERROR: stdout+stderr = $outerr", "red"));
logit(colored("ERROR: \$? = $?, \$! = $!", "red"));
}
close $fh;
unlink($filename);
return $ret;
}
# And logit() is something like:
sub logit {
my $s = shift;
my ($logsec, $logmin, $loghour, $logmday, $logmon, $logyear, $logwday, $logyday, $logisdst) = localtime(time);
$logyear += 1900;
my $logtimestamp = sprintf("%4d-%02d-%02d %02d:%02d:%02d", $logyear, $logmon+1, $logmday, $loghour, $logmin, $logsec);
my $msg = "$logtimestamp $s\n";
print $msg;
open LOG, ">>$LOGFILE";
print LOG colorstrip($msg);
close LOG;
}

Send perl variables to a command and capture the output

I'm attempting to pass perl variables into a system command and then capture the output for later usage, here's my current code:
my $updatedCmd = "|svn diff --summarize $svnOldFull $svnNewFull";
my $updatedUrls = '';
open UPDATES, $updatedCmd or die "Can't get updates";
while(<UPDATES>) {
print $_;
}
print "THIS_SHOULD_OUTPUT_AT_THE_END\n";
The problem with this is that I get the output:
THIS_SHOULD_OUTPUT_AT_THE_END
A /test
A /test2
A /deployment.txt
I would like to be able to capture all of the command output before allowing my perl script to go any further however.
More modern way to do this is the following:
my #cmd = qw(svn diff --summarize), $svnOldFull, $svnNewFull;
open my $pipe, '-|', #cmd or die "oops: $!";
while (<$pipe>) { ... }
Advantages
no globals
open mode separated from file/command
command as array, so there is no need in shell quoting
You placed the pipe on the wrong end of your command. Try this:
my $updatedCmd = "svn diff --summarize $svnOldFull $svnNewFull|";

Cannot find argument passed to program called using Perl "system" command

I'm writing a Perl script to run an external program on every file in a directory. This program converts files from one format to another. Here's the deal...
When I run the program from the command line, everything works as it should:
computer.name % /path/program /inpath/input.in /outpath/output.out
converting: /inpath/input.in to /outpath/output.out
computer.name %
Here's the code I wrote to convert all files in a directory (listed in "file_list.txt"):
#!/usr/bin/perl -w
use warnings;
use diagnostics;
use FileHandle;
use File::Copy;
# Set simulation parameters and directories
#test_dates = ("20110414");
$listfile = "file_list.txt";
$execname = "/path/program";
foreach $date (#test_dates)
{
# Set/make directories
$obs_file_dir = "inpath";
$pred_file_dir = "outpath";
mkdir "$pred_file_dir", 0755 unless -d "$pred_file_dir";
# Read input file names to array
$obs_file_list = $obs_file_dir . $listfile;
open(DIR, $obs_file_list) or die "Could not open file!";
#obs_files = <DIR>;
close(DIR);
# Convert and save files
foreach $file (#obs_files)
{
$file =~ s/(\*)//g;
$infile = $obs_file_dir . $file;
$outfile = $pred_file_dir . $file;
$outfile =~ s/in/out/g;
print $infile . "\n";
#arg_list = ($execname, $infile, $outfile);
system(#arg_list);
}
}
The output shows me the following error for every file in the list:
computer.name % perl_script_name.pl
/inpath/input.in
converting: /inpath/input.in to /outpath/output.out
unable to find /inpath/input.in
stat status=-1
error while processing the product
I verified every file is in the proper place and have no idea why I am getting this error. Why can't the files be found? When I manually pass the arguments using the command line, no problem. When I pass the arguments through a variable via a system call, they can't be found even though the path and file names are correct.
Your advice is greatly appreciated!
Your list of files (#obs_files) comes from reading in a file via #obs_files = <DIR>;
When you do that, each element of array will be a line from a file (e.g. directory listing), with the line being terminated by a newline character.
Before using it, you need to remove the newline character via chomp($file).
Please note that s/(\*)//g; does NOT remove that trailing newline!

Use a perl script to parse a file then update /etc/hosts

Im working on one last perl script to update my /etc/hosts file, but am stuck and wondered if someone can help please?
I have a text file with an IP in it, and need to have my perl script read this, which iv done, but now im stuck on updating the /etc/hosts file.
here is my script so far:
#!/usr/bin/perl
use strict;
my $ip_to_update;
$ip_to_update = `cat /web_root/ip_update/ip_update.txt | awk {'print \$5'}` ;
print "ip = $ip_to_update";
I then need to find an entry in /etc/hosts like
remote.host.tld 192.168.0.20
so i know i need to parse it for remote.host.tld and then replace the second bit, but because the ip wont be the same i cant just do a straight replace.
Can anyone help with the last bit please as im stuck :(
Thankyou!
Your substitution will look like this:
s#^.*\s(remote\.host\.tld)\s*$#$ip_to_update\t$1#
Replacement can be done in one line:
perl -i -wpe "BEGIN{$ip=`awk {'print \$5'} /web_root/ip_update/ip_update.txt`} s#^.*\s(remote\.host\.tld)\s*$#$ip\t$1#"'
Ok, I updated my script to include the file edit etc all in one. Might not be the best way to do it, but it works :)
#!/usr/bin/perl
use strict;
use File::Copy;
my $ip_to_update; # IP from file
my $fh_r; # File handler for reading
my $fh_w; # File handler for writing
my $file_read = "/etc/hosts"; # File to read in
my $file_write = "/etc/hosts.new"; # File to write out
my $file_backup = "/etc/hosts.bak"; # File to copy original to
# Awks the IP from text file
$ip_to_update = `/bin/awk < /web_root/ip_update/ip_update.txt {'print \$5'}` ;
# Open File Handlers
open( $fh_r, '<', $file_read ) or die "Can't open $file_read: $!";
open( $fh_w, '>', $file_write ) or die "Can't open $file_write: $!";
while ( my $line = <$fh_r> )
{
if ( $line =~ /remote.host.tld/ )
{
#print $fh_w "# $line";
}
else
{
print $fh_w "$line";
}
}
chomp($ip_to_update); # Remove newlines
print $fh_w "$ip_to_update remote.host.tld\n";
# Prints out new line with new ip and hostname
# Close file handers
close $fh_r;
close $fh_w;
move("$file_read","$file_backup"); # Moves original file to .bak
move("$file_write","$file_read"); # Moves new file to original file loaction