How can I get Perl to accept negative numbers as command-line arguments? - perl

Is there a way to get Perl to avoid treating negative values as command-line switches? Neither stringifying nor backslashing the argument seems to help under Linux:
$ perl -e 'print "#ARGV\n";' 4 5
4 5
$ perl -e 'print "#ARGV\n";' -4 5
Unrecognized switch: -4 (-h will show valid options).
$ perl -e 'print "#ARGV\n";' "-4" 5
Unrecognized switch: -4 (-h will show valid options).
$ perl -e 'print "#ARGV\n";' '-4' 5
Unrecognized switch: -4 (-h will show valid options).
$ perl -e 'print "#ARGV\n";' \-4 5
Unrecognized switch: -4 (-h will show valid options).

$ perl -E "say join ', ', #ARGV" -- -1 2 3
-1, 2, 3
The trick is using the double-hyphen (--) to end the option parsing. Double-hyphen is a GNU convention:
$ touch -a
usage: touch [-acfm] [-r file] [-t [[CC]YY]MMDDhhmm[.SS]] file ...
$ touch -- -a
$ ls
-a

Related

Perl capture between single quotes in a one-liner

I am trying to print out the text that is surrounded by single quotes.
/bin/bash -lc '/home/CASPER_REPORTS/scripts/CASPER_gen_report.sh CASPER_1'
/bin/bash -lc '/home/CASPER_REPORTS/scripts/CASPER_gen_report.sh CASPER_1A'
/bin/bash -lc '/home/CASPER_REPORTS/scripts/CASPER_gen_report.sh CASPER_2'
/bin/bash -lc '/home/CASPER_REPORTS/scripts/CASPER_gen_report.sh CASPER_3'
/bin/bash -lc '/home/CASPER_REPORTS/scripts/CASPER_gen_report.sh CASPER_3A'
The Boolean one I guess means that perl sees the string.
$ cat /tmp/casper_reports | perl -nle 'print /'.*'/'
1
1
1
1
1
However when I try and capture it with the parenthesis it throws an error
$ cat /tmp/boobomb | perl -nle 'print /'(.*)'/'
-bash: syntax error near unexpected token `('
In the Bash and Zsh shells, you can use $'' to allow escaped single quotes.
echo $'I wouldn\'t'
This also keeps $1 from being interpreted by bash and available to perl too.
perl -nle $'print $1 if /\'(.*)\'/' < /tmp/boobomb
also see
https://unix.stackexchange.com/questions/30903/how-to-escape-quotes-in-shell
Use hex for the single quote (27) via hexadecimal escape, so \x27
perl -wnE'say $1 if /\x27(.*)\x27/' input.txt
This assumes a single pair of single quotes, per shown sample data on which it was tested.

How to tell if my program is being piped to another (Perl)

"ls" behaves differently when its output is being piped:
> ls ???
bar foo
> ls ??? | cat
bar
foo
How does it know, and how would I do this in Perl?
In Perl, the -t file test operator indicates whether a filehandle
(including STDIN) is connected to a terminal.
There is also the -p test operator to indicate whether a filehandle
is attached to a pipe.
$ perl -e 'printf "term:%d, pipe:%d\n", -t STDIN, -p STDIN'
term:1, pipe:0
$ perl -e 'printf "term:%d, pipe:%d\n", -t STDIN, -p STDIN' < /tmp/foo
term:0, pipe:0
$ echo foo | perl -e 'printf "term:%d, pipe:%d\n", -t STDIN, -p STDIN'
term:0, pipe:1
File test operator documentation at perldoc -f -X.
use IO::Interactive qw(is_interactive);
is_interactive() or warn "Being piped\n";

Why the result of encode_base64 doesn't correspond to decode_base64?

$ perl -MMIME::Base64 -e 'print encode_base64("syn_ack#163.com");'
c3luX2Fjay5jb20=
$ perl -MMIME::Base64 -e 'print decode_base64("c3luX2Fjay5jb20=");'
syn_ack.com
The encode result cannot decode to original string, why?
You have to escape # as \#or use different quotes.
This is because double quotes are expanded, and #163 is treated as an array #163 (even if this name is not valid identifier).
This works as expected:
perl -MMIME::Base64 -e "print encode_base64('syn_ack#163.com');"
c3luX2Fja0AxNjMuY29t
perl -MMIME::Base64 -e 'print encode_base64("syn_ack\#163.com");'
c3luX2Fja0AxNjMuY29t
perl -MMIME::Base64 -e "print decode_base64('c3luX2Fja0AxNjMuY29t');"
syn_ack#163.com
Switch the quotes. Perl will interpolate variables when using double-quotes.
$ perl -MMIME::Base64 -e "print encode_base64('syn_ack#163.com');"
c3luX2Fja0AxNjMuY29t
$ perl -MMIME::Base64 -e "print decode_base64('c3luX2Fja0AxNjMuY29t');"
syn_ack#163.com
http://perlmeme.org/howtos/using_perl/interpolation.html
When you see unexpected results with Perl, make sure warnings are enabled.
$ perl -w -MMIME::Base64 -e 'print encode_base64("syn_ack#163.com");'
Possible unintended interpolation of #163 in string at -e line 1.
c3luX2Fjay5jb20=
No interpolation occurs inside single-quoted ('') strings, so you could run
perl -w -MMIME::Base64 -e 'print encode_base64('syn_ack#163.com');'
or leave the double-quotes ("") and escape the #
perl -w -MMIME::Base64 -e 'print encode_base64("syn_ack\#163.com");'
Either outputs
c3luX2Fja0AxNjMuY29t
Decoding gives
$ perl -w -MMIME::Base64 -e 'print decode_base64("c3luX2Fja0AxNjMuY29t");'
syn_ack#163.com

Why does Perl's bignum module give me a strange result to a power calculation?

Context: ActiveState Perl:
This is perl 5, version 12, subversion 4 (v5.12.4) built for MSWin32-x86-multi-thread
>perl -Mbignum=l -e "print 2 ** 32"
4294967296
>perl -Mbignum=l -e "print -2 ** 32"
-4294967296
Then I got to thinking, maybe I need to delimit the negative two.
>perl -Mbignum=l -e "print (-2) ** 32"
-2
Finally figured it out.
>perl -Mbignum=l -e "print ((-2) ** 32)"
4294967296
So how come all the parentheses?
This thread covers both of your questions (you have to go down a little to find the part corresponding to print (-2) ** 32).
Summarizing what is there:
For your first issue (perl -Mbignum=l -e "print -2 ** 32"): in Perl exponentiation has higher precedence than unary negation.
For the second issue (perl -Mbignum=l -e "print (-2) ** 32"): the key is the following warning in the documentation for print.
Also be careful not to follow the print keyword with a left parenthesis unless you want the corresponding right parenthesis to terminate the arguments to the print--interpose a + or put parentheses around all the arguments.
I don’t think this has to do with bignum.
$ perl -MO=Deparse -e "print 2 ** 32"
print 4294967296; # regular case
$ perl -MO=Deparse -e "print -2 ** 32"
print -4294967296; # ** has higher precedence than -
$ perl -MO=Deparse -e "print (-2) ** 32"
print(-2) ** 32; # parentheses parsed as function application
$ perl -MO=Deparse -e "print ((-2) ** 32)"
print 4294967296; # finally what you want
I guess the function application is what bit you (parsing print (-2) as the function print being called with -2 as an argument).
It's not a bignum related issue, if you try this:
perl -e "print (-2) + 32"
you get: -2
So the "problem" is with the arguments format of the print function
If you substitute your constants with variables, B::Deparse will show you how perl parses the code, so
$ perl -MO=Deparse,-p -e " print $fa ** $fb "
print(($fa ** $fb));
-e syntax OK
$ perl -MO=Deparse,-p -e " print -$fa ** $fb "
print((-($fa ** $fb)));
-e syntax OK
$ perl -MO=Deparse,-p -e " print (-$fa ) ** $fb "
(print((-$fa)) ** $fb);
-e syntax OK
$ perl -MO=Deparse,-p -e " print ( (-$fa ) ** $fb )"
print(((-$fa) ** $fb));
-e syntax OK

Insert a line at specific line number with sed or awk

I have a script file which I need to modify with another script to insert a text at the 8th line.
String to insert: Project_Name=sowstest, into a file called start.
I tried to use awk and sed, but my command is getting garbled.
sed -i '8i This is Line 8' FILE
inserts at line 8
This is Line 8
into file FILE
-i does the modification directly to file FILE, no output to stdout, as mentioned in the comments by glenn jackman.
An ed answer
ed file << END
8i
Project_Name=sowstest
.
w
q
END
. on its own line ends input mode; w writes; q quits. GNU ed has a wq command to save and quit, but old ed's don't.
Further reading: https://gnu.org/software/ed/manual/ed_manual.html
OS X / macOS / FreeBSD sed
The -i flag works differently on macOS sed than in GNU sed.
Here's the way to use it on macOS / OS X:
sed -i '' '8i\
8 This is Line 8' FILE
See man 1 sed for more info.
the awk answer
awk -v n=8 -v s="Project_Name=sowstest" 'NR == n {print s} {print}' file > file.new
POSIX sed (and for example OS X's sed, the sed below) require i to be followed by a backslash and a newline. Also at least OS X's sed does not include a newline after the inserted text:
$ seq 3|gsed '2i1.5'
1
1.5
2
3
$ seq 3|sed '2i1.5'
sed: 1: "2i1.5": command i expects \ followed by text
$ seq 3|sed $'2i\\\n1.5'
1
1.52
3
$ seq 3|sed $'2i\\\n1.5\n'
1
1.5
2
3
To replace a line, you can use the c (change) or s (substitute) commands with a numeric address:
$ seq 3|sed $'2c\\\n1.5\n'
1
1.5
3
$ seq 3|gsed '2c1.5'
1
1.5
3
$ seq 3|sed '2s/.*/1.5/'
1
1.5
3
Alternatives using awk:
$ seq 3|awk 'NR==2{print 1.5}1'
1
1.5
2
3
$ seq 3|awk '{print NR==2?1.5:$0}'
1
1.5
3
awk interprets backslashes in variables passed with -v but not in variables passed using ENVIRON:
$ seq 3|awk -v v='a\ba' '{print NR==2?v:$0}'
1
a
3
$ seq 3|v='a\ba' awk '{print NR==2?ENVIRON["v"]:$0}'
1
a\ba
3
Both ENVIRON and -v are defined by POSIX.
sed -e '8iProject_Name=sowstest' -i start using GNU sed
Sample run:
[root#node23 ~]# for ((i=1; i<=10; i++)); do echo "Line #$i"; done > a_file
[root#node23 ~]# cat a_file
Line #1
Line #2
Line #3
Line #4
Line #5
Line #6
Line #7
Line #8
Line #9
Line #10
[root#node23 ~]# sed -e '3ixxx inserted line xxx' -i a_file
[root#node23 ~]# cat -An a_file
1 Line #1$
2 Line #2$
3 xxx inserted line xxx$
4 Line #3$
5 Line #4$
6 Line #5$
7 Line #6$
8 Line #7$
9 Line #8$
10 Line #9$
11 Line #10$
[root#node23 ~]#
[root#node23 ~]# sed -e '5ixxx (inserted) "line" xxx' -i a_file
[root#node23 ~]# cat -n a_file
1 Line #1
2 Line #2
3 xxx inserted line xxx
4 Line #3
5 xxx (inserted) "line" xxx
6 Line #4
7 Line #5
8 Line #6
9 Line #7
10 Line #8
11 Line #9
12 Line #10
[root#node23 ~]#
Perl solutions:
quick and dirty:
perl -lpe 'print "Project_Name=sowstest" if $. == 8' file
-l strips newlines and adds them back in, eliminating the need for "\n"
-p loops over the input file, printing every line
-e executes the code in single quotes
$. is the line number
equivalent to #glenn's awk solution, using named arguments:
perl -slpe 'print $s if $. == $n' -- -n=8 -s="Project_Name=sowstest" file
-s enables a rudimentary argument parser
-- prevents -n and -s from being parsed by the standard perl argument parser
positional command arguments:
perl -lpe 'BEGIN{$n=shift; $s=shift}; print $s if $. == $n' 8 "Project_Name=sowstest" file
environment variables:
setenv n 8 ; setenv s "Project_Name=sowstest"
echo $n ; echo $s
perl -slpe 'print $ENV{s} if $. == $ENV{n}' file
ENV is the hash which contains all environment variables
Getopt to parse arguments into hash %o:
perl -MGetopt::Std -lpe 'BEGIN{getopt("ns",\%o)}; print $o{s} if $. == $o{n}' -- -n 8 -s "Project_Name=sowstest" file
Getopt::Long and longer option names
perl -MGetopt::Long -lpe 'BEGIN{GetOptions(\%o,"line=i","string=s")}; print $o{string} if $. == $o{line}' -- --line 8 --string "Project_Name=sowstest" file
Getopt is the recommended standard-library solution.
This may be overkill for one-line perl scripts, but it can be done
For those who are on SunOS which is non-GNU, the following code will help:
sed '1i\^J
line to add' test.dat > tmp.dat
^J is inserted with ^V+^J
Add the newline after '1i.
\ MUST be the last character of the line.
The second part of the command must be in a second line.
sed -i "" -e $'4 a\\\n''Project_Name=sowstest' start
This line works fine in macOS
macOS sed solutions
for example: inserts at line 1
ns
# recommended 👍
# This command only needs to write one line
$ sed -i '' '1s/^/The new First Line\n/' ./your-source-file-name
ni
# not recommended 👎
# This way the command needs to be written on multiple lines
$ sed -i '' '1i\
The new First Line\
' ./your-source-file-name
test demo
$ sed -i '' '1s/^/Perl 🐪 camel\n/' ./multi-line-text.txt
it is working fine in linux to add in 2 lines.
sed '2s/$/ myalias/' file
Thank you umläute
sed -i "" -e $'4 a\\\n''Project_Name=sowstest' filename
the following was usefull on macOS to be able to add a new line after the 4
In order to loop i created an array of folders, ti iterate on them in mac zsh
for foldercc in $foldernames;
sed -i "" -e $'4 a\\\n''Project_Name=sowstest' $foldercc/filenames;