pl - Perl One-Liner Magic Wand


Some tasks are too menial for a dedicated script but still too cumbersome even with the many neat one-liner options of perl -E. This small script fills the gap: various one-letter commands & magic variables (with meaningful aliases too) and more nifty loop options take Perl programming to the command line. Fully imports List::Util. With no program on the command line, starts a pl Shell.

How to e(cho) values, including from @A(RGV), with single $q(uote) & double $Q(uote):

    pl 'echo "${quote}Perl$quote", "$Quote@ARGV$Quote"' one-liner
    pl 'e "${q}Perl$q", "$Q@A$Q"' one-liner

    >   'Perl' "one-liner"

Same for hard-to-print values:

    pl 'echo \"Perl", \@ARGV, undef' one-liner
    pl 'e \"Perl", \@A, undef' one-liner

    >   \'Perl' [
    >     'one-liner'
    >   ] undef

Loop over args, printing each with line ending. And same, SHOUTING:

    pl -opl '' Perl one-liner
    pl -opl '$_ = uc' Perl one-liner

    >   Perl
    >   one-liner
    >   PERL
    >   ONE-LINER

Print up to 3 matching lines, resetting count (and $.) for each file:

    pl -rP3 '/Perl.*one.*liner/' file*

Count hits in magic statistics hash %N(UMBER):

    pl -n '++$NUMBER{$1} while /(Perl|one|liner)/g' file*
    pl -n '++$N{$1} while /(Perl|one|liner)/g' file*

    >          2: one
    >          7: liner
    >          9: Perl

Though they're sometimes slightly, sometimes quite a bit more complicated, most Perl one-liners from the internet work, just by omitting -e or -E. There's only one main program in pl, but you can just as well concatenate the -es with ;. See minor differences for exceptions. Let's see many varied examples.


Don't believe everything you read on SourceForge^H^H^H the internet! -- Plato :-y

Pl follows Perl's philosophy for one-liners: the one variable solely used in one-liners, @F, is single-lettered. Because not everyone may like that, pl has it both ways. Everything is aliased both as a word and as a single letter, including Perl's own @F & *ARGV.

Perl one-liners, and hence pl, are by nature bilingual. You must run the command with its options & arguments, typically from Shell. By design, Perl quotes mimic Shell quotes, so here they collide. As Perl also uses Shell meta-characters like $, the best solution is to protect Perl-code from the Shell with single quotes. That means you can't use them inside. (An ugly way around that, is '\'', which ends a string, backslashes a quote and starts another.) For literal quotes use $q(uote). For quoting use double quotes or q{}.

Shell and Perl, unlike most other languages, don't make you stick your toe up your nose to get newlines into strings. Thus, you see long "one-liners" as legible many-liners. You get more features on the pl homepage, like in the veggie-burger menu, you can toggle many-line display. In normal text short and long name variants are initial-bold as X(YZ). All examples use the long names, if applicable. On the homepage those are in the darker blue upper half. They are repeated with the short variant. Many examples are followed by their output, indented with >.



Many of perl's options are also available in pl, sometimes enhanced with extra functionality. And the new options complement what perl offers, specifically oriented towards one-liners.


perl: Specify record separator with -n/-p (\0, if no argument).


Map program over already available @A(RGV) (from command line or previous -A) or undef. If you wrap program in {} uses grep instead of map. The result becomes the new @A(RGV). You can mix it with -B. The 1st two are equivalent, except that the 1st one isn't limited by Shell line length limitations. The third again greps by file size, reading only the Perl modules less than 1 kB:

    pl -nA '<*.pm>' '...'
    pl -n '...' *.pm
    pl -nA '<*.pm>' -A '{ (stat)[7] < 1000 }' '...'

perl: Autosplit mode with -n/-p (splits $_ into @F(IELD)).


Run program before reading a new file in -n/-P/-p.


Add program before main program in same scope. You can use it to initialise my variables. Whereas, if you define a my variable in the main program of a -n, -p, -P, -o, or -O loop, that's a new variable on each iteration. This doesn't do a BEGIN block unless you wrap program in {}. You may mix it with -A.


perl: Check syntax only (runs BEGIN and CHECK blocks).


perl: Enables the listed Unicode features.


Colorize (people with impairment may adapt their system, terminal, or browser) some of the output; when can be never, always, or auto (the default).


perl: Run program under debugger.


perl: Set debugging flags (argument is a bit mask or alphabets).


Run program after finishing reading a file in -n/-p.


Add an END block after main-program in same scope. So, my-vars work as follows: the END block is a closure of the 1st $inner variable. Perl warns "Variable "$inner" will not stay shared":

    pl -OB 'my $outer' -E 'echo $inner, $outer' 'my $inner = $outer = $ARGV' a b c
    pl -OB 'my $outer' -E 'e $inner, $outer' 'my $inner = $outer = $A' a b c

    >   a c

perl: Don't do $sitelib/ at startup.


perl: Provide split() pattern for -a switch (//'s are optional).


perl: Specify @INC/#include directory (several -I's allowed).


perl: Edit <> files in place (makes backup if extension supplied).


As I said before, I never repeat myself. :-)

perl: Assume while (<>) { ... } loop around program. It's a little richer than that: if you use last, it closes the current file, leaving you to continue the loop on the next file.


Assume for(@ARGV) { ... } loop around main program, and $ARGIND (or $I) is the current position. In this case -p doesn't imply -n. If you give number, passes that many args at once as an array, referencing the original values. If there aren't enough on the last round, fills up @A(RGV) with undefs.

    pl -opl '' I II III IV
    pl -o3 'echo $ARGIND, @$_' i ii iii iv v vi vii viii ix
    pl -opl '' I II III IV
    pl -o3 'e $I, @$_' i ii iii iv v vi vii viii ix

    >   I
    >   II
    >   III
    >   IV
    >   0 i ii iii
    >   3 iv v vi
    >   6 vii viii ix

like -o but use @A(RGV) as loop variable.


Does pl -penis do pussy? It implements cat. :-*

perl+: On each loop print (also -o and -O, in which case you must fill $_) iteration. If you give number, prints at most number times.


Like -p but print only if main program evaluates to true, like grep.


Reset $. and -p/-P counter for each file.


perl: Enable tainting checks.


perl: Enable tainting warnings.


perl: Allow unsafe operations.


perl: Dump core after parsing program.


perl: Print version, patchlevel and license.


Rerun with given perl version, which is just a string appended to perl.


perl: Enable all warnings.


perl: Enable many useful warnings.


perl: Disable all warnings.


Various functions, always also with a one letter alias, perform little tasks that can be useful in one-liners.

benchmark { } [name[, arg...]] | b { } [name[, arg...]]

Benchmark slow code for 10 s, display name, looping over args.

Benchmark { } [name[, arg...]] | B { } [name[, arg...]]

Same but run code 100 times in benchmark, to reduce overhead.

Config [regexp...] | C [regexp...]

Import and return %Config, e.g., Config->{sitelib}, optionally only part matching regexps.

Date [arg...][, tz] | D [arg...][, tz]

Why is Halloween Christmas? Because Oct 31 = Dec 25. (^)

Date (from arg [s, us], s{.us}, offset [+-]s{.us}, tz ([+-]0-14{:mm|.ff}). You should pass microseconds as strings because floats have implementation-dependent rounding issues. You must pass positive offsets as strings because otherwise it loses the +. Returns the date, if called in some context, else echoes it.

    pl 'Date;
        $_ = Date -86400, "+3600";
        echo $_, " -- ", Date "+8:45"'
    pl 'D;
        $_ = D -86400, "+3600";
        e $_, " -- ", D "+8:45"'

    >   Thu Jan 12 18:46:25.189356 2023
    >   Wed Jan 11 19:46:25.189450 2023  --  Fri Jan 13 02:31:25.189485 +08:45 2023
echo [arg...] | e [arg...]

Echo prettified args or $_ with spaces and newline. Prettified means, undef becomes that string, italic if --color is active. Anything that can be stringified, is. Any other reference goes through Data::Dumper, which pl loads only if needed.

If it's called in scalar context (e.g., $x = echo ...) instead return the same as it would echo, in one string (inspired by Shell $(...)). If it's called in list context (e.g., @l = echo ...) return each arg prettified individually, with a newline on the last one.

Echo [arg...] | E [arg...]

Same but no newline.

form format, [arg...] | f format, [arg...]

Form(at) and echo prettified args or $_ with newline. If it's called in scalar or list context (e.g., $x = form ...) instead return the same as it would echo, in one string. Parameter index can be "%1:" instead of "%1\$".

Form format, [arg...] | F format, [arg...]

Same but no newline.

Isodate [arg...][, tz] | I [arg...][, tz]

Same as D(ate) but uses ISO format.

    pl 'Isodate;
        $_ = Isodate 7 * -86400;
        echo $_, " -- ", Isodate "+8.75"'
    pl 'I;
        $_ = I 7 * -86400;
        e $_, " -- ", I "+8.75"'

    >   2023-01-12T18:46:25.240822
    >   2023-01-05T18:46:25.240907  --  2023-01-13T02:31:25.240935 +08:45
keydiff [key[, value]] | k [key[, value]]

Store value or chomped $_ in $KEYDIFF{key or $1}[$ARGIND]. At the END for each key (which pl sorts numerically if possible) pl diffs all values.

Keydiff [number[, value]] | K [number[, value]]

Same but key is $FIELD[number] or $F[0].

Number [n[, hash_or_array]] | N [n[, hash_or_array]]

Trim hash_or_array (default %N(UMBER)) values less than n (default 2), or, if negative, more than -n e.g., -EN or -E 'Number -5, @RESULT'. This happens recursively at any depth in nested hashes or arrays.

The first argument can also be a function, where deletion happens for every element where it returns a falsy value. It gets called for each scalar element with 3 arguments: 0 - the current (nested) hash or array, 1 - its key or index, 2 - its value.

    pl '%RESULT = (neg => [-9..-1], pos => [1..9]); Number sub { ! ($_[2] % 3) }, %RESULT'
    pl '%R = (neg => [-9..-1], pos => [1..9]); N sub { ! ($_[2] % 3) }, %R'

    >   neg:  [
    >     -9,
    >     -6,
    >     -3
    >   ]
    >   pos:  [
    >     3,
    >     6,
    >     9
    >   ]
piped { } cmd[, arg...] | p { } cmd[, arg...]

Open pipe from cmd and loop over it.

template [tmpl[, hash|key => value...]] | t [tmpl[, hash|key => value...]]

Replace values from hash in template (defaults to $_), which may also be a filehandle of ref to a filename. Hash (defaults to %T(EMPLATE)) may be given as a reference or key-value pairs. The template may use one of three markup styles: [% x %], {{ x }}, or <?pl x ?>. These are totally equivalent. The 1st one found in the template will be used. If you put a ~ just inside a delimiter (e.g., <?pl~ x ~?>), it will gobble horizontal whitespace on that side, behind also one newline.

Within the markup x may be any valid syntax for a Perl hash key. The 3 characters |, :, or ! mark the end of the key name and introduce 3 kinds of filter. The 1st two, can be ?| or ?:, meaning this item applies only if the key exists. Otherwise the key is optional. If you give a key, its value is locally assigned to $_. If undef it defaults to ''.

You write the filter in Perl. You can abort the filter with last. If the filter starts with | then you pipe its scalar value into the template. If there is no filter after |, you recursively treat the value as a template. If the filter starts with : then you insert the value of $_. An easy way to only do that is [%:%]. The whole template gets compiled to an anonymous sub, the 1st time. As this happens inside the function, filters have no access to your surrounding my variables.

You insert the Perl code following ! into the code the template compiles to verbatim. This is useful for flow control. E.g., if $TEMPLATE{x} (or $T{x}) is an array, you can loop over its values as @$_. The loop logic is entirely in Perl, which again localizes $_. If do-nothing [%!%] starts the document, it "declares" its markup syntax. And with ~ it suppresses surrounding space including one following newline. (See PLDUMP)

    pl 'template q(A: {{ a }} B: {{ | "no" }}{{ b ?| "yes" }} C: {{ lc "C" }} C+1: {{ c|$_ + 1}}), qw(a 1 c 3)'
    pl 'template q([%x!for( @$_ ) { %] X: [% : %] [%~ ! } %][% y %]), { x => [1..5] }'
    pl 'template q(<ul><?pl l!for( @$_ ) { ?> <li><?pl:?></li><?pl ! } ?> </ul>), { l => [1..3] }'
    pl 't q(A: {{ a }} B: {{ | "no" }}{{ b ?| "yes" }} C: {{ lc "C" }} C+1: {{ c|$_ + 1}}), qw(a 1 c 3)'
    pl 't q([%x!for( @$_ ) { %] X: [% : %] [%~ ! } %][% y %]), { x => [1..5] }'
    pl 't q(<ul><?pl l!for( @$_ ) { ?> <li><?pl:?></li><?pl ! } ?> </ul>), { l => [1..3] }'

    >   A: 1 B: no C: 3 C+1: 4
    >    X: 1 X: 2 X: 3 X: 4 X: 5
    >    1 2 3 
Template [tmpl[, hash|key => value...]] | T [tmpl[, hash|key => value...]]

Same but no newlines, also not on nested templates.

    pl '$_ = q( [ <?pl~inner|~?> ]);
        $TEMPLATE{inner} = q([ {{ who | $_ || "Jack" }} in the box ]);
        $TEMPLATE{who} = "Sue";
    pl '$_ = q( [ <?pl~inner|~?> ]);
        $T{inner} = q([ {{ who | $_ || "Jack" }} in the box ]);
        $T{who} = "Sue";

    >    [[ Jack in the box ]] [[ Sue in the box ]
    >   ]


Various variables, always also with a one letter alias, often perform magic tasks at the END.

*ARGV | *A

perl: ARGV, $ARGV & @ARGV are all aliased to A, $A & @A.


Index of ARG which -o, -n, or -p loop is currently processing.


perl: This is an alias to loop autosplit variable @F.

$quote | $q

Predefined to a single quote ' without any magic. Perl's q() makes it easy to integrate functional quotes under all circumstances. This does the same for literal quotes.

$Quote | $Q

Predefined to a double quote " without any magic. Perl's qq() makes it easy to integrate functional quotes under all circumstances. This does the same for literal quotes.


At END, sort by keys, print keydiff of $ARGIND array elements. Filled by k(eydiff).


At END, sort numerically by values.


At END, echo $RESULT if defined, then @RESULT one per line if not empty, then %RESULT sorted by keys.


Since pl -MO=Deparse won't show your parts of the program, it can be quite baffling when things go wrong. If you export this with value 1 before starting pl, you see how your parts get embedded in various bits of generated stuff. If you install perltidy, pl will use it. Some options get handled by perl, so they won't show up here:

    PLDUMP=1 \
        pl 'say "Hello Perld!"'

    >   use feature ':' . substr $^V, 1;
    >   sub pl::prog {
    >       $pl::last = 1;
    >     LINE: {
    >   #line 1 "main program"
    >           say "Hello Perld!";
    >       } continue {
    >           $pl::last = 0;
    >       }
    >       if ( $pl::last || eof ) {
    >           ++$ARGIND;
    >           if ($pl::last) { my $d = $.; close ARGV; $. = $d }
    >           exit if $pl::last == 2;
    >       }
    >   }

If you export this with value 2, it will instead show what a template would compile to:

    PLDUMP=2 \
        pl 'template q([%x!for( @$_ ) {%] X: [% : %] [%~!}%]), x => [1..5]'
    PLDUMP=2 \
        pl 't q([%x!for( @$_ ) {%] X: [% : %] [%~!}%]), x => [1..5]'

    >   my @_template = [
    >     '',
    >     ' X: ',
    >     ''
    >   ] ;
    >   #line 1 "template"
    >   sub { my $_template = $_template[0]
    >   ; for((local $_ = $TEMPLATE{x} // '')?():(), @$_ ) {
    >   ;$_template .= $_template[1] . (do {{ 
    >   ; $_ }} // '');$_template .= $_template[2]
    >   ; }
    >   ;
    >   ; $_template }


Even if it's rare nowadays, you can still find Perl 5.16 out in the wild (e.g., in RHEL 7). Pl accommodates it gracefully, falling back to what works. It has shims for any, all, none, notall, product & sum0. (Some Unices maintain even older Perl versions, e.g., AIX or Solaris: you can go back till Perl 5.10 with pl 0.63.2.)

Minor Differences with perl -E

Known minor differences are:

  • by design in an -n loop last is per file instead of behaving like exit

  • don't goto LINE, but next LINE is fine

  • using pop, etc. to implicitly modify @A(RGV) works in -B begin code but not in your main program (which gets compiled to a function)

  • shenanigans with unbalanced braces won't work

Windows Notes

Work Is Never Done On Windows Systems ;-)

Do yourself a favour and get a real Shell, e.g., from Cygwin, git, MinGW, MSYS, or WSL! If you can't avoid or cmd.exe, you will have to first convert all inner quotes to qq or \". Then convert the outer single quotes to double quotes:

    pl "echo qq{${quote}Perl$quote}, \"$Quote@ARGV$Quote\"" one-liner
    pl "e qq{${q}Perl$q}, \"$Q@A$Q\"" one-liner

    >   Perl one-liner

PowerShell is weirder. (Did I mention you'd be better off with a real Shell?) You must use outer single quotes, but you still need to protect double quotes:

    pl 'echo qq{${quote}Perl$quote}, \"$Quote@ARGV$Quote\"' one-liner
    pl 'e qq{${q}Perl$q}, \"$Q@A$Q\"' one-liner

    >   Perl one-liner

While the old Windows 10 terminal understands ANSI escape sequences, it makes it horribly hard to activate them. Therefore, they're off by default, requiring --color to override that choice.

Pl is on SourceForge and also available on meta::cpan.