How to tell bash not to expand $_ variable? - perl

I want to use some perl line, like this:
perl -pe "$_=~s///e"
The problem is, bash keeps expanding the "$_" variable. I could put the perl expression into single quotes, but that would prevent me from adding some variables into a script.
Is there a way stop bash from expanding "$_" variable?

perl -pe '$_=~s///e'
or
perl -pe "\$_=~s///e"

First off: You know that you can use $ENV{myvariable} to access environment variables, right? And that you do not need to specify $_ when using m//, s/// and tr///?
Furthermore, if you want to pass variables to perl, there are other ways of doing that besides trying to interpolate shell variables into your perl code.
perl -we 'my ($var1, $var2, $var3) = #ARGV;' "$MYFOO" "$BAR" "$baz"
If your shell variables do not contain whitespace, you can dispense with the quoting.
Now, if you want to use the -p or -n switches, there are ways around that too.
perl -pwe 'BEGIN { my $var1 = shift; my $var2 = shift } #code goes here'
"$MYFOO" "$BAR" file1 file2
Using shift in a BEGIN statement will remove variables from #ARGV so that they are not used by the implicit while loop of the -p and -n switches.

Mix-and-match.
perl -pe '$_=~s///e; echo "'"$idontknowperl"'"'
As long as the quoted sections butt up against each other it will be considered a single argument.

Related

perl: print to console all the matched pattern

I have mulitple lines
QQQQl123
hsdhjhksd
QQQQl234
ajkdkjsdh
QQQQl564
i want to print all matching QQQQl[0-9]+
like
QQQQl123
QQQQl234
QQQQl564
how to do this using perl
I tried:
$ perl -0777pe '/QQQQl[0-9]+/' filename
it shows nothing
perl -we 'while(<>){ next unless $_=~/QQQQl[0-9]+/; print $_; }' < filename
perl -ne 'print if /QQQQl[0-9]+/' filename
Or, if, for some reason, you insist on using -0777, you could do
perl -0777nE 'say for /QQQQl[0-9]+/g' filename
(or print "$_\n" instead of say)
Your code doesn't work because /QQQQl[0-9]+/ returns true because $_ indeed contains that pattern, but you never asked Perl to do anything based on that return value.
-n is preferable to -p in that case, since you don't want to print every line but only some (-p automatically prints every line, and there is very little you can do about it).

Is it possible to pass command-line arguments to #ARGV when using the -n or -p options?

I think the title of my question basically covers it. Here's a contrived example which tries to filter for input lines that exactly equal a parameterized string, basically a Perlish fgrep -x:
perl -ne 'chomp; print if $_ eq $ARGV[0];' bb <<<$'aa\nbb\ncc';
## Can't open bb: No such file or directory.
The problem of course is that the -n option creates an implicit while (<>) { ... } loop around the code, and the diamond operator gobbles up all command-line arguments for file names. So, although technically the bb argument did get to #ARGV, the whole program fails because the argument was also picked up by the diamond operator. The end result is, it is impossible to pass command-line arguments to the Perl program when using -n.
I suppose what I really want is an option that would create an implicit while (<STDIN>) { ... } loop around the code, so command-line arguments wouldn't be taken for file names, but such a thing does not exist.
I can think of three possible workarounds:
1: BEGIN { ... } block to copy and clear #ARGV.
perl -ne 'BEGIN { our #x = shift(#ARGV); } chomp; print if $_ eq $x[0];' bb <<<$'aa\nbb\ncc';
## bb
2: Manually code the while-loop in the one-liner.
perl -e 'while (<STDIN>) { chomp; print if $_ eq $ARGV[0]; }' bb <<<$'aa\nbb\ncc';
## bb
3: Find another way to pass the arguments, such as environment variables.
PAT=bb perl -ne 'chomp; print if $_ eq $ENV{PAT};' <<<$'aa\nbb\ncc';
## bb
The BEGIN { ... } block solution is undesirable since it constitutes a bit of a jarring context switch in the one-liner, is somewhat verbose, and requires messing with the special variable #ARGV.
I consider the manual while-loop solution to be more of a non-solution, since it forsakes the -n option entirely, and the point is I want to be able to use the -n option with command-line arguments.
The same can be said for the environment variable solution; the point is I want to be able to use command-line arguments with the -n option.
Is there a better way?
You've basically identified them all. The only one you missed, that I know of at least, is the option of passing switch arguments (instead of positional arguments):
$ perl -sne'chomp; print if $_ eq $kwarg' -- -kwarg=bb <<<$'aa\nbb\ncc';
bb
You could also use one of the many getopt modules instead of -s. This is essentially doing the same thing as manipulating #ARGV in a BEGIN {} block before the main program loop, but doing it for you and making it a little cleaner for a one-liner.

Perl generate a file based on a template

I am working on a use case that requires me to generate .hpp files based on a template. So something like
#ifdef changethis_hpp
#define changethis_hpp
#include<fixedheader1>
...
#include<fixedheaderN>
class changethis
{
....
};
needs to be generated based on the requirement of changethis string.
How can I achieve this in perl?
WHITSF
I wrote a fixed template.txt file and and then replaced the text with changethis string and then dumped it as a changethis.hpp.
But is there any other way I can achieve this in perl?
There's a Perl FAQ, How can I expand variables in text strings?. It starts like this:
If you can avoid it, don't, or if you can use a templating system,
such as Text::Template or Template Toolkit, do that instead.
You might even be able to get the job done with sprintf or printf:
my $string = sprintf 'Say hello to %s and %s', $foo, $bar;
However, for the one-off simple case where I don't want to pull out a
full templating system, I'll use a string that has two Perl scalar
variables in it. In this example, I want to expand $foo and $bar to
their variable's values:
my $foo = 'Fred';
my $bar = 'Barney';
$string = 'Say hello to $foo and $bar';
One way I can do this involves the substitution operator and a double /e
flag. The first /e evaluates $1 on the replacement side and turns it
into $foo. The second /e starts with $foo and replaces it with its
value. $foo, then, turns into 'Fred', and that's finally what's left in
the string:
$string =~ s/(\$\w+)/$1/eeg; # 'Say hello to Fred and Barney'
The /e will also silently ignore violations of strict, replacing
undefined variable names with the empty string. Since I'm using the /e
flag (twice even!), I have all of the same security problems I have with
eval in its string form. If there's something odd in $foo , perhaps
something like #{[ system "rm -rf /" ]}, then I could get myself in
trouble.
I'd highly recommend you ignore most of this advice and go directly to a templating system (as recommended in the first line).
I use Text::Template for such tasks.

Simple search and replace without regex

I've got a file with various wildcards in it that I want to be able to substitute from a (Bash) shell script. I've got the following which works great until one of the variables contains characters that are special to regexes:
VERSION="1.0"
perl -i -pe "s/VERSION/${VERSION}/g" txtfile.txt # No problems here
APP_NAME="../../path/to/myapp"
perl -i -pe "s/APP_NAME/${APP_NAME}/g" txtfile.txt # Error!
So instead I want something that just performs a literal text replacement rather than a regex. Are there any simple one-line invocations with Perl or another tool that will do this?
The 'proper' way to do this is to escape the contents of the shell variables so that they aren't seen as special regex characters. You can do this in Perl with \Q, as in
s/APP_NAME/\Q${APP_NAME}/g
but when called from a shell script the backslash must be doubled to avoid it being lost, like so
perl -i -pe "s/APP_NAME/\\Q${APP_NAME}/g" txtfile.txt
But I suggest that it would be far easier to write the entire script in Perl
Use the following:
perl -i -pe "s|APP_NAME|\\Q${APP_NAME}|g" txtfile.txt
Since a vertical bar is not a legal character as part of a path, you are good to go.
I don't particularly like this answer because there should be a better way to do a literal replace in Perl. \Q is cryptic. Using quotemeta adds extra lines of code.
But... You can use substr to replace a portion of a string.
#!/usr/bin/perl
my $name = "Jess.*";
my $sentence = "Hi, my name is Jess.*, dude.\n";
my $new_name = "Prince//";
my $name_idx = index $sentence, $name;
if ($name_idx >= 0) {
substr($sentence, $name_idx, length($name), $new_name);
}
print $sentence;
Output:
Hi, my name is Prince//, dude.
You don't have to use a regular expression for this (using substr(), index(), and length()):
perl -pe '
foreach $var ("VERSION", "APP_NAME") {
while (($i = index($_, $var)) != -1) {
substr($_, $i, length($var)) = $ENV{$var};
}
}
'
Make sure you export your variables.
You can use a regex but escape any special characters.
Something like this may work.
APP_NAME="../../path/to/myapp"
APP_NAME=`echo "$APP_NAME" | sed -e '{s:/:\/:}'`
perl -i -pe "s/APP_NAME/${APP_NAME}/g" txtfile.txt
Use:
perl -i -pe "\$r = qq/\Q${APP_NAME}\E/; s/APP_NAME/\$r/go"
Rationale: Escape sequences
I managed to get a working solution, partly based on bits and pieces from other peoples' answers:
app_name='../../path/to/myapp'
perl -pe "\$r = q/${app_name//\//\\/}/; s/APP_NAME/\$r/g" <<<'APP_NAME'
This creates a Perl variable, $r, from the result of the shell parameter expansion:
${app_name//\//\\/}
${ # Open parameter expansion
app_name # Variable name
// # Start global substitution
\/ # Match / (backslash-escaped to avoid being interpreted as delimiter)
/ # Delimiter
\\/ # Replace with \/ (literal backslash needs to be escaped)
} # Close parameter expansion
All that work is needed to prevent forward slashes inside the variable from being treated as Perl syntax, which would otherwise close the q// quotes around the string.
In the replacement part, use the variable $r (the $ is escaped, to prevent it from being treated as a shell variable within double quotes).
Testing it out:
$ app_name='../../path/to/myapp'
$ perl -pe "\$r = q/${app_name//\//\\/}/; s/APP_NAME/\$r/g" <<<'APP_NAME'
../../path/to/myapp

How do I best pass arguments to a Perl one-liner?

I have a file, someFile, like this:
$cat someFile
hdisk1 active
hdisk2 active
I use this shell script to check:
$cat a.sh
#!/usr/bin/ksh
for d in 1 2
do
grep -q "hdisk$d" someFile && echo "$d : ok"
done
I am trying to convert it to Perl:
$cat b.sh
#!/usr/bin/ksh
export d
for d in 1 2
do
cat someFile | perl -lane 'BEGIN{$d=$ENV{'d'};} print "$d: OK" if /hdisk$d\s+/'
done
I export the variable d in the shell script and get the value using %ENV in Perl. Is there a better way of passing this value to the Perl one-liner?
You can enable rudimentary command line argument with the "s" switch. A variable gets defined for each argument starting with a dash. The -- tells where your command line arguments start.
for d in 1 2 ; do
cat someFile | perl -slane ' print "$someParameter: OK" if /hdisk$someParameter\s+/' -- -someParameter=$d;
done
See: perlrun
Sometimes breaking the Perl enclosure is a good trick for these one-liners:
for d in 1 2 ; do cat kk2 | perl -lne ' print "'"${d}"': OK" if /hdisk'"${d}"'\s+/';done
Pass it on the command line, and it will be available in #ARGV:
for d in 1 2
do
perl -lne 'BEGIN {$d=shift} print "$d: OK" if /hdisk$d\s+/' $d someFile
done
Note that the shift operator in this context removes the first element of #ARGV, which is $d in this case.
Combining some of the earlier suggestions and adding my own sugar to it, I'd do it this way:
perl -se '/hdisk([$d])/ && print "$1: ok\n" for <>' -- -d='[value]' [file]
[value] can be a number (i.e. 2), a range (i.e. 2-4), a list of different numbers (i.e. 2|3|4) (or almost anything else, that's a valid pattern) or even a bash variable containing one of those, example:
d='2-3'
perl -se '/hdisk([$d])/ && print "$1: ok\n" for <>' -- -d=$d someFile
and [file] is your filename (that is, someFile).
If you are having trouble writing a one-liner, maybe it is a bit hard for one line (just my opinion). I would agree with #FM's suggestion and do the whole thing in Perl. Read the whole file in and then test it:
use strict;
local $/ = '' ; # Read in the whole file
my $file = <> ;
for my $d ( 1 .. 2 )
{
print "$d: OK\n" if $file =~ /hdisk$d\s+/
}
You could do it looping, but that would be longer. Of course it somewhat depends on the size of the file.
Note that all the Perl examples so far will print a message for each match - can you be sure there are no duplicates?
My solution is a little different. I came to your question with a Google search the title of your question, but I'm trying to execute something different. Here it is in case it helps someone:
FYI, I was using tcsh on Solaris.
I had the following one-liner:
perl -e 'use POSIX qw(strftime); print strftime("%Y-%m-%d", localtime(time()-3600*24*2));'
which outputs the value:
2013-05-06
I was trying to place this into a shell script so I could create a file with a date in the filename, of X numbers of days in the past. I tried:
set dateVariable=`perl -e 'use POSIX qw(strftime); print strftime("%Y-%m-%d", localtime(time()-3600*24*$numberOfDaysPrior));'`
But this didn't work due to variable substitution. I had to mess around with the quoting, to get it to interpret it properly. I tried enclosing the whole lot in double quotes, but this made the Perl command not syntactically correct, as it messed with the double quotes around date format. I finished up with:
set dateVariable=`perl -e "use POSIX qw(strftime); print strftime('%Y-%m-%d', localtime(time()-3600*24*$numberOfDaysPrior));"`
Which worked great for me, without having to resort to any fancy variable exporting.
I realise this doesn't exactly answer your specific question, but it answered the title and might help someone else!
That looks good, but I'd use:
for d in $(seq 1 2); do perl -nle 'print "hdisk$ENV{d} OK" if $_ =~ /hdisk$ENV{d}/' someFile; done
It's already written on the top in one long paragraph but I am also writing for lazy developers who don't read those lines.
Double quotes and single quote has big different meaning for the bash.
So please take care
Doesn't WORK perl '$VAR' $FILEPATH
WORKS perl "$VAR" $FILEPATH