Perl one liner in Bash script
tl;dr and Solution
This usage of if
with [ ]
will not give you the result you expect.
What you're looking for
...
if run_command; then
...
Longer explanation
Basics of if
if
is a shell feature- based on the condition, it executes the body contained in between
then
andfi
- the "condition" that
if
checks is a command - commands usually have a return/exit code. typically
0
for success1
(common) and everything else for some error- e.g.
127
for command not found
- e.g.
- when the return/exit code is 0, the body is executed
- otherwise it is skipped; or control is passed to
elif
orelse
- the syntax is
if <command>; then...
Where does that [ ]
come from?
test
is a command that can check file types and compare values- refer man test and
help test
(bash only)
- refer man test and
[ ... ]
is a synonym fortest
- NB the brackets should be surrounded by spaces on both sides
if [ -f "/path/to/$filename" ]; then
- exception: when terminated by new line or
;
space not required
- exception: when terminated by new line or
test
(or[ ]
) evaluates expressions and cannot execute other commands or functionsif [ expr ]; then
is alternate syntax forif test expr; then
PS: good practice to "quote" your "$variables" when used with test
or [ ]
PPS: [[ ... ]]
is a different thing altogether. not POSIX; available only in some shells. take a look at this thread on the UNIX Stack Exchange
Using a Perl one-liner in a shell script
There is at least a quoting issue. You make the single quotes a part of the saCMD="'s...'"
. They will not be removed by the shell but passed to perl, as you can see in the echo
output.
In addition,
#execute the command
`perl -pi -e $saCMD $name`
has likely useless backticks. Or do you also want to run a command that's being output by the perl script? To debug shell scripts, place set -x
at the beginning.
This works here:
#!/bin/bash
SERIAL=$(date +%Y%m%d%H%M)
for name in "$@"; do
saCMD="s/^(\W*)\d*.*;\W*serial/\${1}$SERIAL ; serial/"
perl -pi -e "$saCMD" "$name"
done
and turns your example data into
$TTL 300
domain.org. IN SOA ns1.domain.com. admin.domain.org. (
201508201330 ; serial, todays date+todays
7200 ; refresh, seconds
7200 ; retry, seconds
2419200 ; expire, seconds
3600 ) ; minimum, seconds
Perl script versus one-liner - differences in functionality with regex
A one-liner needs -n
(or -p
) to process input, so that files are opened, streams attached, and a loop set up. It still needs that even as the -0777
unsets the input record separator, so the file is read at once; see Why use the -p|-n in slurp mode in perl one liner?
That regex matches either a newline or any character other than a newline, and there is a modifier for that, /s
, with which .
matches newline as well. Then that need be inside curly braces, which you need to escape in newer Perls. The newline that follows doesn't need grouping.
So altogether you'd have
<bash command> | perl -0777 -ne'/(\{(.*)\}\n)/s and print "$1\n"'
Why does my Perl one-liner containing an escaped $ not work inside Bash backticks?
From the Bash manual:
Command Substitution
Command substitution allows the output of a command to replace the command name. There are two forms:
$(command)
or
`command`
...
When the old-style backquote form of substitution is used, backslash retains its literal meaning except when followed by
$
,`
, or\
... When using the$(command)
form, all characters between the parentheses make up the command; none are treated specially.
(emphasis added)
Bash is treating the \
in \$
as an escape sequence, so
ITEMVALUE=`perl -ne 'print /\$.*/g' file.txt`
actually runs
perl -ne 'print /$.*/g' file.txt
$.
is a Perl built-in variable representing the current line number, so the regex becomes /1*/
for the first line, /2*/
for the second line, and so on. You'll get output for any line containing its own line number.
You can see this using Perl's re
pragma:
$ ITEMVALUE=`perl -Mre=debug -ne 'print /\$.*/g' file.txt`
Compiling REx "1*"
...
Compiling REx "2*"
...
To fix, use the $(command)
form of command substitution, which is generally preferred anyway:
ITEMVALUE=$(perl -ne 'print /\$.*/g' file.txt)
How does this Perl one-liner actually work?
This is a variant on “Just another Perl hacker”, a Perl meme. As JAPHs go, this one is relatively tame.
The first thing you need to do is figure out how to parse the perl program. It lacks parentheses around function calls and uses the +
and quote-like operators in interesting ways. The original program is this:
print+pack+q,c*,,map$.+=$_,74,43,-2,1,-84, 65,13,1,5,-12,-3, 13,-82,44,21, 18,1,-70,56, 7,-77,72,-7,2, 8,-6,13,-70,-34
pack
is a function, whereas print
and map
are list operators. Either way, a function or non-nullary operator name immediately followed by a plus sign can't be using +
as a binary operator, so both +
signs at the beginning are unary operators. This oddity is described in the manual.
If we add parentheses, use the block syntax for map
, and add a bit of whitespace, we get:
print(+pack(+q,c*,,
map{$.+=$_} (74,43,-2,1,-84, 65,13,1,5,-12,-3, 13,-82,44,21,
18,1,-70,56, 7,-77,72,-7,2, 8,-6,13,-70,-34)))
The next tricky bit is that q
here is the q
quote-like operator. It's more commonly written with single quotes:
print(+pack(+'c*',
map{$.+=$_} (74,43,-2,1,-84, 65,13,1,5,-12,-3, 13,-82,44,21,
18,1,-70,56, 7,-77,72,-7,2, 8,-6,13,-70,-34)))
Remember that the unary plus is a no-op (apart from forcing a scalar context), so things should now be looking more familiar. This is a call to the pack
function, with a format of c*
, meaning “any number of characters, specified by their number in the current character set”. An alternate way to write this is
print(join("", map {chr($.+=$_)} (74, …, -34)))
The map
function applies the supplied block to the elements of the argument list in order. For each element, $_
is set to the element value, and the result of the map
call is the list of values returned by executing the block on the successive elements. A longer way to write this program would be
@list_accumulator = ();
for $n in (74, …, -34) {
$. += $n;
push @list_accumulator, chr($.)
}
print(join("", @list_accumulator))
The $.
variable contains a running total of the numbers. The numbers are chosen so that the running total is the ASCII codes of the characters the author wants to print: 74=J
, 74+43=117=u
, 74+43-2=115=s
, etc. They are negative or positive depending on whether each character is before or after the previous one in ASCII order.
For your next task, explain this JAPH (produced by EyesDrop).
''=~('(?{'.('-)@.)@_*([]@!@/)(@)@-@),@(@@+@)'
^'][)@]`}`]()`@.@]@%[`}%[@`@!#@%[').',"})')
Don't use any of this in production code.
Perl one liner inside a perl script not working
Yes, use current perl process instead of forking new one. This is equivalent of your
perl -pi -e 's/^(node.session.auth.authmethod\s*=\s*).*$/#\1hello/g' /root/text.conf
one-liner,
use strict;
use warnings;
local $^I = "";
local @ARGV = "/root/text.conf";
while (<>) {
s/^(node.session.auth.authmethod\s*=\s*).*$/#\1hello/g;
print;
}
Perl one-liner substitution without further expansion of a shell variable?
Instead of expanding a shell variable directly in the perl code you can pass it as an argument by setting the s
switch:
#!/usr/bin/env bash
perl -i -spe 's/name\@domain\.org/$replacement/' -- -replacement="$1" file1.txt file2.txt
In perl's s///
, without the use of e
or ee
modifiers, variables in the replacement part are treated as literals so you don't need to escape them.
How can I properly run Perl one liner command line scripts?
perl
is not a valid command inside a Perl script. If you had named that file as a .sh script, and used #!/bin/bash
on the shebang line, it would have worked, but it doesn't really make a lot of sense to write a bash file just to invoke Perl (why not invoke Perl directly?)
Since you mentioned you want to interact with the command line, I'll mention here that you can get at the command line options within Perl via the @ARGV
array. (See perldoc perlvar.)
Related Topics
Scale PDF to Add Border for Printing Full Size Pages
Pyinstaller on 32-Bit Linux - Importerror: The 'six' Package Is Required
How to Split Two Vertical Pane Inside a Horizontal Pane in Tmux Using Tmuxinator
/Usr/Local/Ssl/Lib/Libcrypto.A: Could Not Read Symbols: Bad Value
"Hello World" Function Without Using C Printf
Awk, Pipe and Tail -F Giving Unexpected Behavior
Cache Coloring on Slab Memory Management in Linux Kernel
Force Netcat to Send Messages Immediately (Without Buffering)
Conditional Awk Hashmap Match Lookup
Scp Command Between 2 Servers with 2 Different .Pem Keys
Exactly When Tasklet Runs After It Is Schedule by Isr
/Usr/Bin/Env Questions Regarding Shebang Line Pecularities
How to Sort Numbers in a Qtreewidget Column
Human Readable, Recursive, Sorted List of Largest Files
Bash Script to Create Symbolic Links to Shared Libraries