I am attempting to parse an output file for some data, and am having problems returning the value to my Windows shell.
What I am trying to do is simply return a value using a simple regular expression, and store that into my shell variable.
I currently have something like this:
%VAL% = %PERL% -e '$tmp="Value: 1000"; if ($tmp =~ /Value:\s(\d+)/) { print $1; }'
where %VAL% is where I'd like to return what was found in $1, and %PERL% points to a local copy of perl.exe.
Can somebody please point out the proper way of doing this?
EDIT: It's a bit uglier than Unix:
for /f "tokens=*" %i in ('perl -e "$tmp=\"Value: 1000\"; if ($tmp =~ /Value:\s(\d+)/) { print $1; }"') do set VAL = %i
Or use set /p with an intermediary file:
%PERL% -e '$tmp="Value: 1000"; if ($tmp =~ /Value:\s(\d+)/) { print $1; }' > file.txt
set /p VAL = < file.txt
del file.txt
From Silly Batch File Tricks
Why don't you create the variable inside the script? see env_var on win32 perl wiki.
Related
I have a file which contains each users userid and password. I need to fetch userid and password from that file by passing userid as an search element using awk command.
user101,smith,smith#123
user102,jones,passj#007
user103,albert,albpass#01
I am using a awk command inside my perl script like this:
...
...
my $userid = ARGV[0];
my $user_report_file = "report_file.txt";
my $data = `awk -F, '$1 ~ /$userid/ {print $2, $3}' $user_report_file`;
my ($user,$pw) = split(" ",$data);
...
...
Here I am getting the error:
awk: ~ /user101/ {print , }
awk: ^ syntax error
But if I run same command in terminal window its able to give result like below:
$] awk -F, '$1 ~ /user101/ {print $2, $3}' report_file.txt
smith smith#123
What could be the issue here?
The backticks are a double-quoted context, so you need to escape any literal $ that you want awk to interpret.
my $data = `awk -F, '\$1 ~ /$userid/ {print \$2, \$3}' $user_report_file`;
If you don't do that, you're interpolating the capture variables from the last successful Perl match.
When I have these sorts of problems, I try the command as a string first to see if it is what I expect:
my $data = "awk -F, '\$1 ~ /$userid/ {print \$2, \$3}' $user_report_file";
say $data;
Here's the Perl equivalent of that command:
$ perl -aF, -e '$F[0]=~/101/ && print "#F[1,2]"' report_file
But, this is something you probably want to do in Perl instead of creating another process:
Interpolating data into external commands can go wrong, such as a filename that is foo.txt; rm -rf /.
The awk you run is the first one in the path, so someone can make that a completely different program (so use the full path, like /usr/bin/awk).
Taint checking can tell you when you are passing unsanitized data to the shell.
Inside a program you don't get all the shortcuts, but if this is the part of your program that is slow, you probably want to rethink how you are accessing this data because scanning the entire file with any tool isn't going to be that fast:
open my $fh, '<', $user_report_file or die;
while( <$fh> ) {
chomp;
my #F = split /,/;
next unless $F[0] =~ /\Q$userid/;
print "#F[1,2]";
last; # if you only want the first one
}
If I run a script with perl -Fsomething, is that something value saved anywhere in the Perl environment where the script can find it? I'd like to write a script that by default reuses the input delimiter (if it's a string and not a regular expression) as the output delimiter.
Looking at the source, I don't think the delimiter is saved anywhere. When you run
perl -F, -an
the lexer actually generates the code
LINE: while (<>) {our #F=split(q\0,\0);
and parses it. At this point, any information about the delimiter is lost.
Your best option is to split by hand:
perl -ne'BEGIN { $F="," } #F=split(/$F/); print join($F, #F)' foo.csv
or to pass the delimiter as an argument to your script:
F=,; perl -F$F -sane'print join($F, #F)' -- -F=$F foo.csv
or to pass the delimiter as an environment variable:
export F=,; perl -F$F -ane'print join($ENV{F}, #F)' foo.csv
As #ThisSuitIsBlackNot says it looks like the delimiter is not saved anywhere.
This is how the perl.c stores the -F parameter
case 'F':
PL_minus_a = TRUE;
PL_minus_F = TRUE;
PL_minus_n = TRUE;
PL_splitstr = ++s;
while (*s && !isSPACE(*s)) ++s;
PL_splitstr = savepvn(PL_splitstr, s - PL_splitstr);
return s;
And then the lexer generates the code
LINE: while (<>) {our #F=split(q\0,\0);
However this is of course compiled, and if you run it with B::Deparse you can see what is stored.
$ perl -MO=Deparse -F/e/ -e ''
LINE: while (defined($_ = <ARGV>)) {
our(#F) = split(/e/, $_, 0);
}
-e syntax OK
Being perl there is always a way, however ugly. (And this is some of the ugliest code I have written in a while):
use B::Deparse;
use Capture::Tiny qw/capture_stdout/;
BEGIN {
my $f_var;
}
unless ($f_var) {
$stdout = capture_stdout {
my $sub = B::Deparse::compile();
&{$sub}; # Have to capture stdout, since I won't bother to setup compile to return the text, instead of printing
};
my (undef, $split_line, undef) = split(/\n/, $stdout, 3);
($f_var) = $split_line =~ /our\(\#F\) = split\((.*)\, \$\_\, 0\);/;
print $f_var,"\n";
}
Output:
$ perl -Fe/\\\(\\[\\\<\\{\"e testy.pl
m#e/\(\[\<\{"e#
You could possible traverse the bytecode instead, since the start probably will be identical every time until you reach the pattern.
My .vimrc is configured to automatically compile my Perl script upon save.
However, it is hard-coded to perl 5.14.
Some of the scripts I maintain are 5.8.8, and others are 5.16
I would like vim to compile my code based on the version in the hashbang #! line
Is this possible?
Here is my current .vimrc:
" check perl code with :make
" by default it will call "perl -c" to verify the code and, if there are any errors, the cursor will be positioned in the offending line.
" errorformat uses scanf to extract info from errors/warnings generated by make.
" %f = filename %l = line number %m = error message
" autowrite tells vim to save the file whenever the buffer is changed (for example during a save)
" BufWritePost executes the command after writing the buffer to file
" [F4] for quick :make
autocmd FileType perl set makeprg=/home/utils/perl-5.14/5.14.1-nothreads-64/bin/perl\ -c\ %\ $*
autocmd FileType perl set errorformat=%f:%l:%m
autocmd FileType perl set autowrite
autocmd BufWritePost *.pl,*.pm,*.t :make
You have to dynamically change the Perl path in 'makeprg' based on the shebang line. For example:
:let perlExecutable = matchstr(getline(1), '^#!\zs\S\+')
:let perlExecutable = (empty(perlExecutable) ? '/home/utils/perl-5.14/5.14.1-nothreads-64/bin/perl' : perlExecutable) " Default in case of no match
:let &makeprg = perlExecutable . ' -c % $*'
You can invoke that (maybe encapsulated in a function) on the FileType event, but then it won't properly detect new files that don't have the shebang yet. Or prepend the call to your autocmd BufWritePost *.pl,*.pm,*.t :make; that will re-detect after each save.
PS: Instead of all those :autocmd FileType in your ~/.vimrc, I would rather place those in ~/.vim/after/ftplugin/perl.vim (if you have :filetype plugin on). Also, you should use :setlocal instead of (global) :set.
Use plenv to change perl versions for your projects: plenv local 5.8.8
https://github.com/tokuhirom/plenv
You don't have to specify the path for shebang and makeprg.
I found another way to solve this problem.
Instead of setting makeprg to the perl executable, I tell vim to execute another script.
That script determines the appropriate version of perl, then runs that version with -c
This solution could be extended for other scripting languages
In the .vimrc, change:
autocmd FileType perl set makeprg=/home/utils/perl-5.14/5.14.1-nothreads-64/bin/perl\ -c\ %\ $*
to:
autocmd FileType perl set makeprg=/home/username/check_script_syntax.pl\ %\ $*
#!/home/utils/perl-5.14/5.14.1-nothreads-64/bin/perl
use 5.014; # enables 'say' and 'strict'
use warnings FATAL => 'all';
use IO::All;
my $script = shift;
my $executable;
for my $line ( io($script)->slurp ) {
##!/home/utils/perl-5.14/5.14.1-nothreads-64/bin/perl
##!/home/utils/perl-5.8.8/bin/perl
if ( $line =~ /^#!(\/\S+)/ ) {
$executable = $1;
} elsif ( $script =~ /\.(pm)$/ ) {
if ( $script =~ /(releasePatterns.pm|p4Add.pm|dftform.pm)$/ ) {
$executable = '/home/utils/perl-5.8.8/bin/perl';
} else {
$executable = '/home/utils/perl-5.14/5.14.1-nothreads-64/bin/perl';
}
} else {
die "ERROR: Could not find #! line in your script $script";
}
last;
}
if ( $script =~ /\.(pl|pm|t)$/ ) {
$executable .= " -c";
} else {
die "ERROR: Did not understand how to compile your script $script";
}
my $cmd = "$executable $script";
say "Running $cmd";
say `$cmd`;
I have a huge file which consists of similar lines below , with different clocks:
cmd -quiet [get_ports p1] ref_clocks "cudtclk_sp cudtclk"
cmd -quiet [get_ports p2] clock "cu2xdtclk_sp cu2xdtclk"
And I need to replace cudtclk with some other name like cdtclk whenever I have ref_clocks in my file, globally.
I have written following code but it doesn't seem to be working.
#!/usr/bin/perl
use strict;
use warnings;
sub clock_change
{ # Get the subroutine's argument.
my $arg = shift;
# Hash of stuff we want to replace.
my %replace = (
"cudtclk" => "cdtclk",
);
# See if there's a replacement for the given text.
my $text = $replace{$arg};
if(defined($text)) {
return $text;
}
return $arg;
}
open PAR, "<file name>";
while(<PAR>) {
$_ =~ s/\S+\s\S+\s\S+\s\S+\sref_clocks\s+(\S+\s+\S+)/clock_change($1)/eig;
print $_; ##print it to some file later.
}
"And I need to replace cudtclk with some other name like cdtclk"
perl -pe 's/\bcudtclk\b/cdtclk/' thefile > newfile
"whenever I have ref_clocks"
perl -pe 's/\bcudtclk\b/cdtclk/ if /\bref_clocks\b/' thefile > newfile
Alternatively:
# saves original file as file.bak
perl -i.bak -pe 's/\bcudtclk\b/cdtclk/ if /\bref_clocks\b/' file
Tighten to suit your data, as necessary.
Although the substitution seems like unnecessarily complex, you can fix it with something similar to:
$_ =~ s/(ref_clocks\s+")([^_]+)_sp(\s+)\2/
$1.clock_change($2)."_sp$3".clock_change($2)/eig;
I have a directory full of files containing records like:
FAKE ORGANIZATION
799 S FAKE AVE
Northern Blempglorff, RI 99xxx
01/26/2011
These items are being held for you at the location shown below each one.
IF YOU ASKED THAT MATERIAL BE MAILED TO YOU, PLEASE DISREGARD THIS NOTICE.
The Waltons. The complete DAXXXX12118198
Pickup at:CHUPACABRA LOCATION 02/02/2011
GRIMLY, WILFORD
29 FAKE LANE
S. BLEMPGLORFF RI 99XXX
I need to remove all entries with the expression Pickup at:CHUPACABRA LOCATION.
The "record separator" issue:
I can't touch the input file's formatting -- it must be retained as is. Each record
is separated by roughly 40+ new lines.
Here's some awk ( this works ):
BEGIN {
RS="\n\n\n\n\n\n\n\n\n+"
FS="\n"
}
!/CHUPACABRA/{print $0}
My stab with perl:
perl -a -F\n -ne '$/ = "\n\n\n\n\n\n\n\n\n+";$\ = "\n";chomp;$regex="CHUPACABRA";print $_ if $_ !~ m/$regex/i;' data/lib51.000
Nothing is returned. I'm not sure how to specify 'field separator' in perl except at the commandline. Tried the a2p utility -- no dice. For the curious, here's what it produces:
eval '$'.$1.'$2;' while $ARGV[0] =~ /^([A-Za-z
# process any FOO=bar switches
#$FS = ' '; # set field separator
$, = ' '; # set output field separator
$\ = "\n"; # set output record separator
$/ = "\n\n\n\n\n\n\n\n\n+";
$FS = "\n";
while (<>) {
chomp; # strip record separator
if (!/CHUPACABRA/) {
print $_;
}
}
This has to run under someone's Windows box otherwise I'd stick with awk.
Thanks!
Bubnoff
EDIT ( SOLVED ) **
Thanks mob!
Here's a ( working ) perl script version ( adjusted a2p output ):
eval '$'.$1.'$2;' while $ARGV[0] =~ /^([A-Za-z
# process any FOO=bar switches
#$FS = ' '; # set field separator
$, = ' '; # set output field separator
$\ = "\n"; # set output record separator
$/ = "\n"x10;
$FS = "\n";
while (<>) {
chomp; # strip record separator
if (!/CHUPACABRA/) {
print $_;
}
}
Feel free to post improvements or CPAN goodies that make this more idiomatic and/or perl-ish. Thanks!
In Perl, the record separator is a literal string, not a regular expression. As the perlvar doc famously says:
Remember: the value of $/ is a string, not a regex. awk has to be better for something. :-)
Still, it looks like you can get away with $/="\n" x 10 or something like that:
perl -a -F\n -ne '$/="\n"x10;$\="\n";chomp;$regex="CHUPACABRA";
print if /\S/ && !m/$regex/i;' data/lib51.000
Note the extra /\S/ &&, which will skip empty paragraphs from input that has more than 20 consecutive newlines.
Also, have you considered just installing Cygwin and having awk available on your Windows machine?
There is no need for (much)conversion if you can download gawk for windows
Did you know that Perl comes with a program called a2p that does exactly what you described you want to do in your title?
And, if you have Perl on your machine, the documentation for this program is already there:
C> perldoc a2p
My own suggestion is to get the Llama book and learn Perl anyway. Despite what the Python people say, Perl is a great and flexible language. If you know shell, awk and grep, you'll understand many of the Perl constructs without any problems.