How can I capture the complete commandline in Perl? - perl

Suppose the call was
/usr/local/bin/perl verify.pl 1 3 de# > result.log
Inside verify.pl I want to capture the whole call above and append it to a log file for tracking purposes.
How can I capture the whole call as it is?

$0 has the script name and #ARGV has the arguments, so the whole commandline is:
$commandline = $0 . " ". (join " ", #ARGV);
or, more elegantly (thanks FMc):
$commandline = join " ", $0, #ARGV;
I don't however, know how to capture the redirection (> result.log)

There is way (at least on unix-systems) to get whole command line:
my $cmdline = `ps -o args -C perl | grep verify.pl`;
print $cmdline, "\n";
e: Cleaner way using PID (courtesy of Nathan Fellman):
print qx/ps -o args $$/;

$commandline = join " ", $0, #ARGV; does not handle the case that command line has quotes such as ./xxx.pl --love "dad and mom"
A Quick Solution:
my $script_command = $0;
foreach (#ARGV) {
$script_command .= /\s/ ? " \'" . $_ . "\'"
: " " . $_;
}
Try to save the following code as xxx.pl and run ./xxx.pl --love "dad and mom":
#!/usr/bin/env perl -w
use strict;
use feature qw ( say );
say "A: " . join( " ", $0, #ARGV );
my $script_command = $0;
foreach (#ARGV) {
$script_command .= /\s/ ? " \'" . $_ . "\'"
: " " . $_;
}
say "B: " . $script_command;

Here virtually same Linux-only variants (of course, after shell intervention):
pure perl
BEGIN {
my #cmd = ( );
if (open(my $h, "<:raw", "/proc/$$/cmdline")) {
# precisely, buffer size must be at least `getconf ARG_MAX`
read($h, my $buf, 1048576); close($h);
#cmd = split(/\0/s, $buf);
};
print join("\n\t", #cmd), "\n";
};
using File::Slurp:
BEGIN {
use File::Slurp;
my #cmd = split(/\0/s, File::Slurp::read_file("/proc/$$/cmdline", {binmode => ":raw"}));
print join("\n\t", #cmd), "\n";
};

See In Perl, how do I get the directory or path of the current executing code?

Example which handles more special cases (and without adding own name) and with processing the line with GetOpt:
#!perl
use strict;
use warnings;
use Getopt::Long qw(GetOptionsFromString);
use feature qw ( say );
# Note: $0 is own name
my $sScriptCommandLine;
my #asArgs = ();
my $iRet;
my $sFile = '';
my $iN = -1;
my $iVerbose = 0;
# ============================================================================
my %OptionsHash = (
"f=s" => \$sFile,
"n=i" => \$iN,
"v:+" => \$iVerbose);
$sScriptCommandLine = join( ' ', #ARGV ); # useless for argument with spaces
say 'A: ' . $sScriptCommandLine;
$sScriptCommandLine = '"' . join( '" "', #ARGV ) . '"'; # all arguments in "", but not suitable for arguments with '"' (given as '\"')
say 'B: ' . $sScriptCommandLine;
$sScriptCommandLine = '';
foreach (#ARGV) {
$sScriptCommandLine .= ' ' if ($sScriptCommandLine);
$_ =~ s/\\/\\\\/g; # needed for GetOptionsFromString
$_ =~ s/\"/\\\"/g;
if (/\s/) {
$sScriptCommandLine .= '"'.$_.'"';
}
else {
$sScriptCommandLine .= $_;
}
}
say 'C: ' . $sScriptCommandLine;
my ($iRet,$paArgs);
($iRet,$paArgs) = GetOptionsFromString($sScriptCommandLine,%OptionsHash);
# remaining parameters in $$paArgs[0] etc.
if (!$iRet) {
# error message already printed from GetOptionsFromString
print "Invalid parameter(s) in: \"$sScriptCommandLine\"\n";
}
say 'D: ' . '<<' . join( '>> <<', #{$paArgs} ) . '>>';
say 'f=s: "'.$sFile.'"';
say 'n=i: '.$iN;
say 'v:+: '.$iVerbose;
# eof

Related

Duplicated output with for each loop

My code loops through multiple files in a directory, parses each file and appends the parsed content of each file to FinalVariantfile.txt.
The code works, but duplicates the content of each file.
When I ran the code with two files the output contained 4 files. Could someone please explain why this is happening and how to fix this?
#!/usr/bin/perl -w
use strict;
#directory structure
my $home = "/data/";
my $tsvdirectory = $home . "test_all_runs/" . $ARGV[0];
my $tsvfiles = $home . "test_all_runs/" . $ARGV[0] . "/tsv_files.txt";
my $FinalVariants = $home . "test_all_runs/" . $ARGV[0] . "/FinalVariantfile.txt";
my #tsvfiles = ();
my #currentlines = ();
my $currentline = '';
my $currentCNVline = '';
my #currentCNVlines = ();
my #HotSpotLines = ();
my #CNVLines = ();
# command to produce the vcf_files.txt file stored in each individual run
# directory; the file list includes solely vcf files which have not been
# previously prepared and/or annotated
my $cmd = `ls $tsvdirectory/FOCUS*\.tsv > $tsvfiles`;
# print "$cmd";
my $cmda = "ls $tsvdirectory/FOCUS*\.tsv > $tsvfiles";
# print "$cmda";
# this code opens the vcf_files.txt file and passes each line into an array for
# indidivudal manipulation
open( TXT2, "$tsvfiles" );
while ( <TXT2> ) {
push( #tsvfiles, $_ );
}
close(TXT2);
foreach ( #tsvfiles ) {
chop($_);
}
# this code then parses each of the files listed by name in the tsvfiles array
foreach ( #tsvfiles ) {
my $currenttsvfile = "$_"; # establishes the current file being manipulated
my $MDLfinaltsvfile = $currenttsvfile;
$MDLfinaltsvfile =~ s/\.tsv/_prepared\.txt/g;
# this series of variable calls names the various intermediate or
# final output files
my $MDLlinestsvfile = $currenttsvfile;
$MDLlinestsvfile =~ s/\.tsv/_withCNV\.txt/g;
my $Variantlinestsvfile = $currenttsvfile;
$Variantlinestsvfile =~ s/\.tsv/_HotSpot\.txt/g;
my $MDLtsvfile = $currenttsvfile;
$MDLtsvfile =~ s/\.tsv/_FilteredAllcolumns\.txt/g;
my $MDLsampleid = $currenttsvfile;
$MDLsampleid =~ s/\-oncogene.tsv//g;
print "The currentVCFis############# " . $currenttsvfile . "\n";
my #SampleID = ();
#SampleID = split /\//, $MDLsampleid;
print "The sampleIDis##############" . $SampleID[4] . "\n";
my $CNVdata = $currenttsvfile;
$CNVdata =~ s/\.tsv/_cnv\.txt/g;
my $FinalCNVdata = $currenttsvfile;
$FinalCNVdata =~ s/\.tsv/_finalcnv\.txt/g;
my $cmd2 = `fgrep -v "#" $currenttsvfile > $MDLlinestsvfile`;
print "$cmd2"; # this code extracts from the current vcf file all of the
# lines of data and outputs them into a separate file
my $cmd5 = `grep -vwE "(CNV|intronic|synonymous|utr_3|utr_5)"
#removes lines that contain CNV/intronic/synonymous/utr_3/utr_5"
$MDLlinestsvfile > $Variantlinestsvfile`;
print "$cmd5";
open( my $fh_in, '<', $Variantlinestsvfile )
or die "cannot open $Variantlinestsvfile: $!\n";
#removes lines that contain 0/0 and ./. genotypes from field 70.
open( my $fh_out, '>', $MDLtsvfile )
or die "cannot open $MDLtsvfile: $!\n";
while ( my $line = <$fh_in> ) {
# tab/field-based:
my #fields = split( /\s+/, $line );
print $fh_out $line unless ( $fields[70] =~ m|([0.])/\1| );
}
close($fh_in);
close($fh_out);
#open each filtered file with all columns and pushes it into array.
open( TXT2, "$MDLtsvfile" );
while (<TXT2>) {
push( #HotSpotLines, $_ );
}
close(TXT2);
foreach (#HotSpotLines) {
chop($_);
my #HotSpotEntries = ();
my $currentMDLline = $_;
#HotSpotEntries = split( /\t/, $currentMDLline );
my $chr = $HotSpotEntries[9];
my $position = $HotSpotEntries[10];
my $cosmicids = $HotSpotEntries[21];
my $refforward = $HotSpotEntries[67];
my $genotype = $HotSpotEntries[70];
my $altforward = $HotSpotEntries[77];
my $altreverse = $HotSpotEntries[78];
my $cDNA = $HotSpotEntries[81];
my $exon = $HotSpotEntries[83];
my $conseq = $HotSpotEntries[84];
my $location = $HotSpotEntries[88];
my $geneclass = $HotSpotEntries[92];
my $aachange = $HotSpotEntries[98];
my $transcript = $HotSpotEntries[100];
$currentline
= $SampleID[4] . "\t"
. $chr . "\t"
. $position . "\t"
. $cosmicids . "\t"
. $refforward . "\t"
. $refreverse . "\t"
. $genotype . "\t"
. $altforward . "\t"
. $altreverse . "\t"
. $cDNA . "\t"
. $exon . "\t"
. $conseq . "\t"
. $location . "\t"
. $geneclass . "\t"
. $aachange . "\t"
. $transcript;
# print "The currentVCFlineis ".$currentline."\n";
push( #currentlines, $currentline );
}
my $i;
for ( $i = 0; $i < #currentlines; $i += 1 ) {
my $currentguiline = $currentlines[$i];
my $cmd5 = `echo "$currentguiline" >> $FinalVariants`;
print "$cmd5";
#my $cmd9 = `sed -i '1i$SampleID[4]' $FinalVariants`; print $cmd9;
}
}
There is no need to start so many new shell subprocesses to do such basic operations. ls, fgrep, grep and echo all have equivalents in Perl, and especially calling echo for each line of text is a very poor way of copying one file to another
I suspect that your problem is because of the line
my $cmd5 = `echo "$currentguiline" >> $FinalVariants`;
which will append each element of #currentlines to the end of the file. So the first time you run your program it will contain a single copy of the result, but every subsequent run will just add more data to the end of your file and it will keep growing
I hate to offer a hack to get things working, but it would take me ages to understand what your program is doing behind all the confusion and write a proper concise version. You can fix it temporarily by adding the line
unlink $FinalVariants or die $!;
before the foreach ( #tsvfiles ) { ... } loop. This will delete the file and ensure that a new version is created for each execution of your program.
Okay, I've studied your code carefully and I think this will do what you intend. Without any data or even file name samples I've been unable to test it beyond making sure that it compiles, so it will be a miracle if it works first time, but I believe it's the best chance you have of getting a coherent solution
Note that there's a problem with $refreverse that you use in your own code but never declare or define it, so there's no way that the code you show will create the problem you say it does because it dies during compilation with the error message
Global symbol "$refreverse" requires explicit package name
I've guessed that it's right after $ref_forward at index 68
Please report back about how well this functions
#!/usr/bin/perl
use strict;
use warnings 'all';
my $home = "/data";
my $tsv_directory = "$home/test_all_runs/$ARGV[0]";
my $final_variants = "$tsv_directory/final_variant_file.txt";
open my $out_fh, '>', $final_variants
or die qq{Unable to open "$final_variants" for output: $!};
my #tsv_files = glob "$tsv_directory/FOCUS*.tsv";
for my $tsv_file ( #tsv_files ) {
print "The current VCF is ############# $tsv_file\n";
$tsv_file =~ m|([^/]+)-oncogene.tsv$| or die "Cant extract Sample ID";
my $sample_id = $1;
print "The sample ID is ############## $sample_id\n";
open my $in_fh, '<', $tsv_file
or die qq{Unable to open "$tsv_file" for input: $!};
while ( <$in_fh> ) {
next if /^#/;
next if /\b(?:CNV|intronic|synonymous|utr_3|utr_5)\b/;
my #fields = split;
next if $fields[70] eq '0/0' or $fields[70] eq './.';
my #wanted = ( 9, 10, 21, 67, 68, 70, 77, 78, 81, 83, 84, 88, 92, 98, 100 );
my $current_line = join "\t", #fields[#wanted];
print $out_fh $current_line, "\n";
}
}

\n not working. new line not working in Perl script

Here I write Perl code but in if condition, used \n new line character not match.
#!/usr/bin/perl
use strict;
#use warnings;
use Cwd;
use File::Basename;
use File::Copy;
my $path = getcwd;
#print $path."\n";
opendir( INP, "$path\/" );
my #out = grep( /.(xml)$/, readdir(INP) );
close INP;
#print #out;
open( F6, ">Log.txt" );
foreach my $f1 (#out) {
open( FF, "<$path\/$f1" ) or die "Cannot open file: $out[0]";
my $data1 = join( "", <FF> );
my #FILE_KA_ARRAY = split( /\n/, $data1 );
my $file_ka_len = #FILE_KA_ARRAY;
#print F6 $file_ka_len."\n";
#print F6 $f."\t".$file_ka_len."\n";
print F6 $f1 . "\n";
for ( my $x = 1; $x < $file_ka_len; $x++ ) {
my $y = $x + 1;
my $temp_file_arr = "";
$temp_file_arr = $FILE_KA_ARRAY[$x];
#print F6 $temp_file_arr."\t$x\n";
my $temp1 = $temp_file_arr;
if ( $temp1
=~ m#(<list .*? depth="(\d+)">)\n?(<list .*? depth="(\d+)">)#gs )
{
my $list3 = $1;
print F6 "\t\t\t\t\t\t\t\t" . $y . "\t\t" . $list3 . "\n";
}
}
}
Assuming your problem line is this:
if($temp1=~m#(<list .*? depth="(\d+)">)\n?(<list .*? depth="(\d+)">)#gs)
Then the problem here is here:
my #FILE_KA_ARRAY = split(/\n/, $data1);
Because your split is removing the linefeeds and putting each line into the array. And so when you do:
$temp_file_arr = $FILE_KA_ARRAY[$x];
my $temp1=$temp_file_arr;
You have no linefeeds in there, because you have no linefeeds in your source.
Additionally though:
Don't turn off warnings. IF you have warnings FIX THEM.
This looks like XML. Use a parser. (Although I'd avoid XML::Simple - it's nasty)
indenting your code is a good thing, because it helps clarify your code.
if you use glob ( "$path/*.xml" ) instead of readdir and grep you get a list of paths built in.

STDOUT from Pidgin plugin script

Yesterday, I wrote a perl plugin script for Pidgin 2.10.9, running on Windows 7, and using Strawberry Perl 5.10.1.5
Basically, on the receipt of an IM, it uses backticks to call a console application (written in .NET) and returns the console output to the sender as an IM.
I had to reboot this morning, but ever since I rebooted, it has stopped working.
So, I changed the backticks to use "capture". That didn't work either, but it at least gave me this error:
(15:00:33) Plugin: Error: Error in IPC::System::Simple plumbing: "Can't dup STDOUT" - "Bad file descriptor" at (eval 12) line 53
I have no idea what's changed from yesterday to today, and wondered if anybody knew what might be causing the error?
Thanks
Edit: Thought I'd add my code
use Purple;
#use IPC::System::Simple qw(system systemx capture capturex);
use IPC::System::Simple qw(capture capturex);
%PLUGIN_INFO = (
perl_api_version => 2,
name => "PlugIn",
version => "0.1",
summary => "AutoResp",
description => "PlugIn",
author => "Mark Watkin",
url => "http://",
load => "plugin_load",
unload => "plugin_unload"
);
sub plugin_init {
return %PLUGIN_INFO;
}
sub plugin_load {
my $plugin = shift;
Purple::Debug::info("PlugIn", "plugin_load()\n");
$data = "";
$conversation_handle = Purple::Conversations::get_handle();
Purple::Signal::connect($conversation_handle, "received-im-msg", $plugin, \&signal_chat_callback, $data);
}
sub plugin_unload {
my $plugin = shift;
Purple::Debug::info("PlugIn", "plugin_unload()\n");
}
sub signal_chat_callback {
# The signal data and the user data come in as arguments
my ($account, $sender, $message, $conv, $flags) = #_;
Purple::Debug::info("PlugIn", "Account Alias \"" . $account->get_alias() . "\"\n");
if( $account->get_alias() eq "PlugIn" )
{
Purple::Debug::info("PlugIn", "Request: \"" . $message . "\"\n");
if(!$conv)
{
Purple::Debug::info("PlugIn", "No conversation\n");
$conv = Purple::Conversation->new(1, $account, $sender);
}
$im = $conv->get_im_data();
$im->send( "One moment please..." );
my $query = "";
# eval {
# $query = capture("\"D:\\SourceCode\\PlugInNET\\bin\\Debug\\PlugInNET.exe\" \"" . $message . "\"");
# #$query = capture("\"D:\\SourceCode\\PlugInNET\\bin\\Debug\\PlugInNET.exe\"", "\"" . $message . "\"");
# #my $query = capture("D:\\SourceCode\\PlugInNET\\bin\\Debug\\PlugInNET.exe");
# #my $query = `\"D:\\SourceCode\\PlugInNET\\bin\\Debug\\PlugInNET.exe\" \"$message\"`;
# #my $query = `dir /b`;
# };
# if( $# )
# {
# Purple::Debug::info("PlugIn", "Error: " . $# . "\n");
# }
Purple::Debug::info("PlugIn", "Query: " . $query . "\n");
open ( my $fh, "-|", "D:\\SourceCode\\PlugInNET\\bin\\Debug\\PlugInNET.exe \"$message\"" ) or die "Cannot run free, $ERRNO";
while (<$fh>)
{
Purple::Debug::info("PlugIn", "Read: Line " . $_ . "\n");
$query = $query . $_ . "\n";
}
close $fh;
Purple::Debug::info("PlugIn", "Query: " . $query . "\n");
if( $query eq "" )
{
$im->send( "I'm sorry, my brain doesn't seem to be functioning at the moment" );
} else {
#msgs = split(/-----------\n/, $query);
foreach( #msgs )
{
Purple::Debug::info("PlugIn", "Result Msg: \"" . $_ . "\"\n");
$im->send( "<BODY>" . $_ . "</BODY>" );
}
}
}
}
The plan was to fix up the paths once I had it working properly
Please consider using file handles instead of backticks to capture stdout from another source. You'll be able collect errors.
#!/usr/bin/perl
use strict;
use warnings;
use English;
# No taint protection in this example
open ( my $fh, '-|', '/usr/bin/free' ) or die "Cannot run free, $ERRNO";
while (<$fh>)
{
print;
}
close $fh;

Searching for two matches in perl

I'm looking for a way to match two terms in a single string. For instance if I need to match both "foo" and "bar" in order for the string to match and be printed, and the string is "foo 121242Z AUTO 123456KT 8SM M10/M09 SLP02369", it would not match. But if the string was "foo 121242Z AUTO 123456KT 8SM bar M10/M09 SLP02369", it would match and then go on to be printed. Here's the code that I have currently but I am a bit stuck. Thanks!
use strict;
use warnings;
use File::Find;
use Cwd;
my #folder = ("/d2/aschwa/archive_project/METAR_data/");
open(OUT , '>', 'TEKGEZ_METARS.txt') or die "Could not open $!";
print OUT "Date (YYYYMMDD), Station, Day/Time, Obs Type, Wind/Gust (Kt), Vis (SM),
Sky, T/Td (C), Alt, Rmk\n";
print STDOUT "Finding METAR files\n";
my $criteria = sub {if(-e && /^/) {
open(my $file,$_) or die "Could not open $_ $!\n";
my $dir = getcwd;
my #dirs = split ('/', $dir);
while(<$file>) {
$_ =~ tr/\015//d;
print OUT $dirs[-1], ' ', $_ if /foo?.*bar/;
}
}
};
find($criteria, #folder);
close OUT;
print STDOUT "Done Finding Station METARS\n";
Why not just simple:
perl -ne'print if /foo.*bar/'
If you want process more files from some directory use find
find /d2/aschwa/archive_project/METAR_data/ -type f -exec perl -MFile::Spec -ne'BEGIN{$dir = (File::Spec->splitdir($ARGV[0]))[-2]} print $dir, ' ', $_ if /foo.*bar/' {} \; > TEKGEZ_METARS.txt
You can achieve it with positive look-ahead for both strings:
print OUT $dirs[-1], ' ', $_ if m/(?=.*foo)(?=.*bar)/;
#!/usr/bin/perl
use warnings;
use strict;
my $string1 = "foo 121242Z AUTO 123456KT 8SM M10/M09 SLP02369";
my $string2 = "foo 121242Z AUTO 123456KT 8SM bar M10/M09 SLP02369";
my #array = split(/\s+/, $string2);
my $count = 0;
foreach (#array){
$count++ if /foo/;
$count++ if /bar/;
}
print join(" ", #array), "\n" if $count == 2;
This will print for $string2, but not for $string1

Display And Pass Command Line Arguments in Perl

I have the following program "Extract.pl", which opens a file, finds the lines containing "warning....", "info...", "disabling..." then counts and prints the value and number of them. It is working ok.
What I want to do is to create command line arguments for each of the 3 matches - warning, disabling and infos and then run either of them from the command prompt.
Here is the code:
#!/usr/bin/perl
use strict;
use warnings;
my %warnings = ();
my %infos = ();
my %disablings = ();
open (my $file, '<', 'Warnings.txt') or die $!;
while (my $line = <$file>) {
if($line =~ /^warning ([a-zA-Z0-9]*):/i) {
++$warnings{$1};
}
if($line =~ /^disabling ([a-zA-Z0-9]*):/i) {
++$disablings{$1};
}
if($line =~ /^info ([a-zA-Z0-9]*):/i) {
++$infos{$1};
}
}
close $file;
foreach my $w (sort {$warnings{$a} <=> $warnings{$b}} keys %warnings) {
print $w . ": " . $warnings{$w} . "\n";
}
foreach my $d (sort {$disablings{$a} <=> $disablings{$b}} keys %disablings) {
print $d . ": " . $disablings{$d} . "\n";
}
foreach my $i (sort {$infos{$a} <=> $infos{$b}} keys %infos) {
print $i . ": " . $infos{$i} . "\n";
}
The builtin special array #ARGV holds all command line arguments to the script, excluding the script file itself (and the interpreter, if called as perl script.pl). In the case of a call like perl script.pl foo bar warnings, #ARGV would contain the values 'foo', 'bar', and 'warnings'. It's a normal array, so you could write something like (assuming the first argument is one of your options):
my ($warning, $info, $disabling);
if ($ARGV[0] =~ /warning/i) { $warning = 1 }
elsif ($ARGV[0] =~ /info/i) { $info = 1 }
elsif ($ARGV[0] =~ /disabling/i) { $disabling = 1 }
# [...] (opening the file, starting the main loop etc...)
if ( $warning and $line =~ /^warning ([a-zA-Z0-9]*)/i ) {
++$warnings{$1};
}
elsif ( $info and $line =~ /^info ([a-zA-Z0-9]*)/i ) {
++$infos{$1};
}
elsif ( $disabling and $line =~ /^disabling ([a-zA-Z0-9]*)/i ) {
++$disablings{$1};
}
I created flag variables for the three conditions before the main loop that goes through the file to avoid a regex compilation on every line of the file.
You could also use the Getopt::Long or Getopt::Std modules. These provide easy and flexible handling of the command line arguments.