How Does This Perl One Liner in The Bash Works

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

  1. if is a shell feature
  2. based on the condition, it executes the body contained in between then and fi
  3. the "condition" that if checks is a command
  4. commands usually have a return/exit code. typically
    • 0 for success
    • 1 (common) and everything else for some error
      • e.g. 127 for command not found
  5. when the return/exit code is 0, the body is executed
  6. otherwise it is skipped; or control is passed to elif or else
  7. the syntax is if <command>; then...

Where does that [ ] come from?

  1. test is a command that can check file types and compare values
    • refer man test and help test (bash only)
  2. [ ... ] is a synonym for test
    • 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
  3. test (or [ ]) evaluates expressions and cannot execute other commands or functions
  4. if [ expr ]; then is alternate syntax for if 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



Leave a reply



Submit