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
values, including from @A
, with single $q
& double $Q
:
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
:
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 -e
s with ;
. See minor differences for exceptions. Let's see many varied examples.
Don't believe everything you read on SourceForge the internet! –
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, shown here slightly lighter as shell '
. 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
. 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. In the ☰ veggie-burger menu, you can toggle many-line display. In normal text short and long name variants are initial-bold as X
. All examples use the long names, if applicable. Those are in the darker blue upper half. In the lighter lower half, you get the short variant, again selectable in the menu. If there is a ▶ play button, hover (or tap) the example to fold out a terminal with the output. The blue sidelines show the structure and act as breadcrumbs: You can hover, click or bookmark them.
DOCUMENTATION
Options
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.
-0[octal]
-Aprog
Map program over already available @A
(from command line or previous -A) or undef. If you wrap program in {}
uses grep
instead of map
. The result becomes the new @A
. 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 } ' '... '
-a
-bprog
-Bprog
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.
-c
-C[number/list]
--color[=when]
-d[:debugger]
-D[number/list]
-eprog
-Eprog
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
-f
-F/pattern/
-Idirectory
-i[extension]
As I said before, I never repeat myself. 😂
-n
-o[number]
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
with undef
s.
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
-O[number]
Does pl -penis
do pussy? cat
.
-p[number]
-P[number]
-r
-T
-t
-U
-u
-v
-Vversion
-W
-w
-X
Functions
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 { } [name[, arg…]]
B { } [name[, arg…]]
Config [regexp…]
C [regexp…]
Why is Halloween Christmas? Because Oct 31 = Dec 25. 🎅
Date [arg…][, tz]
D [arg…][, tz]
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" '
Mon Aug 14 23:34:20.425194 2023 Mon Aug 14 00:34:20.425292 2023 -- Tue Aug 15 06:19:20.425333 +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…]
form format, [arg…]
f format, [arg…]
Form format, [arg…]
F format, [arg…]
Isodate [arg…][, tz]
I [arg…][, tz]
keydiff [key[, value]]
k [key[, value]]
Keydiff [number[, value]]
K [number[, value]]
Number [n[, hash_or_array]]
N [n[, hash_or_array]]
Trim hash_or_array (default %N
) 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…]
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
) 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 <ul> <li>1</li> <li>2</li> <li>3</li> </ul>
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;
$TEMPLATE{who} = "Sue";
template '
pl '$_ = q( [ <?pl~inner| ~?> ]);
$T{inner} = q([ {{ who | $_ || "Jack" }} in the box ]);
T;
$T{who} = "Sue";
t '
[[ Jack in the box ]] [[ Sue in the box ] ]
Variables
Various variables, always also with a one letter alias, often perform magic tasks at the END
.
*ARGV
*A
$ARGIND
$I
@FIELD
@F
$quote
$q
$Quote
$Q
%KEYDIFF
%K
%NUMBER
%N
*RESULT
*R
$ENV{PLDUMP}
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 }
To steal ideas from one person is plagiarism. To steal from many is research. 😉
EXAMPLESHere's a wide variety of examples, many solving real-life problems. Often you can copy & paste them as-is. Or you need to make minor changes, e.g., to adapt them to your search expression. Many of these examples started out quite small, illustrating the power of pl. But to be generally useful, they're extended to cope with border cases. See canned commands for how you can make your favourite ones easier to use.
Only some of these are original. Many are adaptations from the various Perl one-liner web pages (Tom
Dealing with Files
People say the back of my head looks really nice – but I don't see it. 😂
Heads …If you want just n, e.g., 10, lines from the head of each file, use the optional number argument to -p, along with -r to reset the count. The program can be empty but must be present if you provide additional args:
pl -rp10 ' ' file*
If you want the head up to a regexp, use the flip-flop operator, starting with line number 1. Use the print-if-true -P loop option, again with -r to reset the count:
pl -rP '1../last/ ' file*
You can combine the two if you want at most n lines, e.g., 10:
pl -rP10 '1../last/ ' file*
What has a head, a tail, but no legs?
If you want a bigger number of last lines, you need to stuff them in a list; not worth it. But, if you want just 1 last line from each file, the end-of-file -e code (no need to quote, as it has no special characters) can E
it for you, capitalized to not add another newline (yes, Perl is case sensitive):
pl -e Echo ' ' file*
pl -e E ' ' file*
If you want the tail from a line-number (e.g., 99) or a regexp, use the flip-flop operator, starting with your regexp and going till each end-of-file:
pl -P '99..eof ' file*
pl -P '/first/..eof ' file*
You can even get head and tail (which in programming logic translates to print if in 1st or
2nd range) if last line of head comes before 1st line of tail (or actually any number of such disjoint ranges):
pl -rP '1../last/ or /first/..eof ' file*
Remove Trailing Whitespace in Each File
This print-loops (-p) over each file, replacing it (-i) with the modified output. Line ends get stripped on reading and added on printing (-l) because they're also whitespace (\s
). At each end of line, substitute one or more spaces of any kind (incl. DOS newlines) with nothing:
pl -pli 's/\s+$// ' file*
Tabify/Untabify Each File
This print-loops (-p) over each file, replacing it (-i) with the modified output. At beginning of line and after each tab, this converts 8 spaces or less than 8 followed by a tab to a tab:
pl -pi '1 while s/(?:^|\t)\K(?: {1,7}\t| {8})/\t/ ' file*
To go the other way, subtract the tab-preceding length modulo 8, to get the number of spaces to replace with:
pl -pi '1 while s/^([^\t\n]*)\K\t/" " x (8 - length($1) % 8)/e ' file*
Fans of half-width tabs make that:
pl -pi '1 while s/(?:^|\t)\K(?: {1,3}\t| {4})/\t/ ' file*
pl -pi '1 while s/^([^\t\n]*)\K\t/" " x (4 - length($1) % 4)/e ' file*
Poets create worlds through a minimal of words. – Kim
Remove Empty Lines
Move a Line Further Down in Each File
Assume we have lines matching "from" followed by lines matching "to". The former shall move after the latter. This loops over each file, replacing it with the modified output. The flip-flop operator becomes true when matching the 1st regexp. Capture something in there to easily recognize it's the first, keep the line in a variable and empty $_
. When $1
is again true, it must be the last matching line. Append the keep variable to it.
pl -pi 'if( /(f)rom/.../(t)o/ ) {
if( $1 eq "f" ) { $k = $_; $_ = "" } elsif( $1 ) { $_ .= $k }
} ' file*
Rename a File Depending on Contents
This reads each file in an -n loop. When it finds the package
declaration, which gives the logical name of this file, it replaces double colons with slashes. It renames the file to the result. The last
statement then makes this the last line read of the current file, continuing with the next file:
pl -n 'if( s/^\s*package\s+([^\s;]+).*/$1/s ) {
s!::!/!g;
rename $ARGV, "$_.pm" or warn "$ARGV -> $_.pm: $!\n";
last;
} ' *.pm
pl -n 'if( s/^\s*package\s+([^\s;]+).*/$1/s ) {
s!::!/!g;
rename $A, "$_.pm" or warn "$A -> $_.pm: $!\n";
last;
} ' *.pm
This assumes all files are at the root of the destination directories. If not, you must add the common part of the target directories before $_
.
On Windows this won't quite work because that locks the file while reading. There you must add close ARGV;
(or close A;
) before the rename
.
For Java it's a bit more complicated because the full name is split into a package
followed by a class
or similar statement. Join them when we find the latter:
pl -n 'if( /^\s*package\s+([^\s;]+)/ ) {
$d = $1 =~ tr+.+/+r;
} elsif( /^\s*(?:(?:public|private|protected|abstract|sealed|final)\s+)*(?:class|interface|enum|record)\s+([^\s;]+)/ ) {
rename $ARGV, "$d/$1.java" or warn "$ARGV -> $d/$1.java: $!\n";
last;
} ' *.java
pl -n 'if( /^\s*package\s+([^\s;]+)/ ) {
$d = $1 =~ tr+.+/+r;
} elsif( /^\s*(?:(?:public|private|protected|abstract|sealed|final)\s+)*(?:class|interface|enum|record)\s+([^\s;]+)/ ) {
rename $A, "$d/$1.java" or warn "$A -> $d/$1.java: $!\n";
last;
} ' *.java
Delete Matching Files, Except Last One
If you have many files, which sort chronologically by name, and you want to keep only the last one, it can be quite painful to formulate Shell patterns. Check on each iteration of the -o loop, whether the index $ARGIND
(or $I
) is less than the last, before unlinking (deleting). If you want to test it first, replace unlink
with e
:
pl -o 'unlink if $ARGIND < $#ARGV ' file*
pl -o 'unlink if $I < $#A ' file*
If your resulting list is too long for the Shell, let Perl do it. Beware that the Shell has a clever ordering of files, while Perl does it purely lexically! The -A code assigns the result to @A
, as though it came from the command line. This list is then popped (shortened) in -B begin code, instead of checking each time. Since the programs don't contain special characters, you don't even need to quote them:
pl -oA '<file*> ' -B pop unlink
You can exclude files by any other criterion as well:
pl -oA 'grep !/keep-me/, <file*> ' unlink
42% of statistics are made up! 😂
File StatisticsCount Files per Suffix
Find and pl both use the -0 option to allow funny filenames, including newlines. Sum up encountered suffixes in sort-numerically-at-end hash %N
:
find -type f -print0 |
pl -0ln 'm@[^/.](\.[^/.]*)?$@;
++$NUMBER{$1 // "none"} '
find -type f -print0 |
pl -0ln 'm@[^/.](\.[^/.]*)?$@;
++$N{$1 // "none"} '
4: .3 4: .SHs 4: .act … 20: .tar 24: .com 24: .e2x 28: .aux 28: .utf 32: .enc 32: .typemap 36: .SH 40: .cpp 52: .bat 64: .plx 72: .inc 80: .xr 88: .json 108: .tml 136: .xml 224: .yml 332: .xs 376: .sh 412: .ucm 444: .PL 512: .c 640: .h 696: .txt 950: .pod 1392: .pl 2988: none 3264: .pm 10846: .t
There are three types of people: those who can count and those who can't. 🙃
Count Files per Directory per SuffixMatch to first or last /
and from last dot following something, i.e. not just a dot-file. Store sub-hashes in sort-by-key-and-stringify-at-end hash %R
. Count in a nested hash of directory & suffix:
find -type f -print0 |
pl -0ln 'm@^(?:\./)?(.+?)/.*?[^/.](\.[^/.]*)?$@;
++$RESULT{$1}{$2 // "none"} '
find -type f -print0 |
pl -0ln 'm@^(?:\./)?(.+?)/.*?[^/.](\.[^/.]*)?$@;
++$R{$1}{$2 // "none"} '
perl-5.30.0: { '.1' => 3, '.3' => 1, '.PL' => 111, … '.yml' => 56, none => 747 } perl-5.30.1: { … '.c' => 128, … '.h' => 160, … '.pm' => 816, '.pmc' => 2, … } perl-5.30.2: { … '.pl' => 348, … '.t' => 2712, … } perl-5.30.3: { '.1' => 3, '.3' => 1, '.PL' => 111, '.SH' => 9, …' '.perl' => 1, '.perldb' => 2, '.ph' => 1, '.pht' => 1, '.pkg' => 1, '.pl' => 348, '.yml' => 56, none => 747 }
find -type f -print0 |
pl -0ln 'm@^(?:\./)?(.+)/.*?[^/.](\.[^/.]*)?$@;
++$RESULT{$1}{$2 // "none"} '
find -type f -print0 |
pl -0ln 'm@^(?:\./)?(.+)/.*?[^/.](\.[^/.]*)?$@;
++$R{$1}{$2 // "none"} '
perl-5.30.3: { '.SH' => 8, '.act' => 1, … '.yml' => 3, none => 15 } perl-5.30.3/Cross: { '.new' => 1, '.patch' => 2, '.sh-arm-linux' => 1, '.sh-arm-linux-n770' => 1, none => 9 } … perl-5.30.3/lib: { '.pl' => 5, '.pm' => 36, '.pod' => 2, '.t' => 41 } perl-5.30.3/lib/B: { '.pm' => 2, '.t' => 3 } perl-5.30.3/lib/Class: { '.pm' => 1, '.t' => 1 } perl-5.30.3/lib/Config: { '.pm' => 1, '.t' => 1 } … perl-5.30.3/t: { '.pl' => 4, '.supp' => 1, none => 3 } perl-5.30.3/t/base: { '.t' => 9 } perl-5.30.3/t/benchmark: { '.t' => 1 } …
This is the same pivoted, grouping by suffix and counting per directory:
find -type f -print0 |
pl -0ln 'm@^(?:\./)?(.+)/.*?[^/.](\.[^/.]*)?$@;
++$RESULT{$2 // "none"}{$1} '
find -type f -print0 |
pl -0ln 'm@^(?:\./)?(.+)/.*?[^/.](\.[^/.]*)?$@;
++$R{$2 // "none"}{$1} '
… .pl: { 'perl-5.30.3' => 8, … 'perl-5.30.3/dist/Attribute-Handlers/demo' => 11, 'perl-5.30.3/dist/Devel-PPPort/devel' => 3, 'perl-5.30.3/dist/Devel-PPPort/parts' => 2, 'perl-5.30.3/dist/Devel-PPPort/t' => 1, 'perl-5.30.3/dist/IO/hints' => 1, 'perl-5.30.3/dist/Storable/hints' => 4, … } … .pm: { 'perl-5.30.3' => 1, 'perl-5.30.3/Porting' => 2, … 'perl-5.30.3/dist/Attribute-Handlers/lib/Attribute' => 1, 'perl-5.30.3/dist/Carp/lib' => 1, 'perl-5.30.3/dist/Carp/lib/Carp' => 1, 'perl-5.30.3/dist/Data-Dumper' => 1, 'perl-5.30.3/dist/Data-Dumper/t/lib' => 1, … } … .pod: { 'perl-5.30.3/Porting' => 8, 'perl-5.30.3/cpan/CPAN-Meta/lib/CPAN/Meta/History' => 5, 'perl-5.30.3/cpan/CPAN/lib/CPAN/API' => 1, … 'perl-5.30.3/dist/ExtUtils-ParseXS/lib' => 3, 'perl-5.30.3/dist/ExtUtils-ParseXS/lib/ExtUtils' => 1, 'perl-5.30.3/dist/Locale-Maketext/lib/Locale' => 1, 'perl-5.30.3/dist/Locale-Maketext/lib/Locale/Maketext' => 2, … } …
This is similar but stores in sort-by-number-at-end %N
. Therefore, it sorts by frequency, only secondarily by directory & suffix (pl sorts stably):
find -type f -print0 |
pl -0ln 'm@^(?:\./)?(.+)/.*?[^/.](\.[^/.]*)?$@;
++$NUMBER{"$1 " . ($2 // "none")} '
find -type f -print0 |
pl -0ln 'm@^(?:\./)?(.+)/.*?[^/.](\.[^/.]*)?$@;
++$N{"$1 " . ($2 // "none")} '
1: perl-5.30.3 .act 1: perl-5.30.3 .aix 1: perl-5.30.3 .amiga 1: perl-5.30.3 .android 1: perl-5.30.3 .bs2000 … 2: perl-5.30.3/Porting .c 2: perl-5.30.3/Porting .pm … 138: perl-5.30.3/cpan/Unicode-Collate/t .t 149: perl-5.30.3/pod .pod 206: perl-5.30.3/t/op .t
The function N
can trim %N
, to those entries at least the argument (default 2):
find -type f -print0 |
pl -0lnE Number 'm@^(?:\./)?(.+)/.*?[^/.](\.[^/.]*)?$@;
++$NUMBER{"$1 " . ($2 // "none")} '
find -type f -print0 |
pl -0lnE N 'm@^(?:\./)?(.+)/.*?[^/.](\.[^/.]*)?$@;
++$N{"$1 " . ($2 // "none")} '
find -type f -print0 |
pl -0lnE 'Number 80 ' 'm@^(?:\./)?(.+)/.*?[^/.](\.[^/.]*)?$@;
++$NUMBER{"$1 " . ($2 // "none")} '
find -type f -print0 |
pl -0lnE 'N 80 ' 'm@^(?:\./)?(.+)/.*?[^/.](\.[^/.]*)?$@;
++$N{"$1 " . ($2 // "none")} '
82: perl-5.30.3/cpan/Math-BigInt/t .t 82: perl-5.30.3/hints .sh 84: perl-5.30.3/cpan/IO-Compress/t .t 87: perl-5.30.3/cpan/Unicode-Collate/Collate/Locale .pl 103: perl-5.30.3/cpan/Encode/ucm .ucm 117: perl-5.30.3/ext/XS-APItest/t .t 137: perl-5.30.3/dist/Devel-PPPort/parts/base none 137: perl-5.30.3/dist/Devel-PPPort/parts/todo none 138: perl-5.30.3/cpan/Unicode-Collate/t .t 149: perl-5.30.3/pod .pod 206: perl-5.30.3/t/op .t
Sum up File-sizes per Suffix
This illustrates a simpler approach: rather than the complicated regexps above, let Perl split each filename for us. Find separates output with a dot and -F splits on that. The \\
is to escape one backslash from the Shell. No matter how many dots the filename contains, 1st element is the size and last is the suffix. Sum it in %N
, which gets sorted numerically at the end:
find -type f -printf "%s.%f\0 " |
pl -0lF\\. '$NUMBER{@FIELD > 2 ? ".$FIELD[-1]" : "none"} += $FIELD[0] '
find -type f -printf "%s.%f\0 " |
pl -0lF\\. '$N{@FIELD > 2 ? ".$FIELD[-1]" : "none"} += $FIELD[0] '
0: .configure 16: .perldb 85: .xsh 90: .inf 118: .pmc 138: .plugin … 7167163: .c 7638677: .pod 7794749: .h 9742749: .ucm 11124074: .t 11617824: .pm 12259742: .txt
I feel more like I do now than I did a while ago. 🙃
Count Files per DateIncredibly, find has no ready-made ISO date, so specify the 3 parts. If you don't want days, just leave out -%Td
. Sum up encountered dates in sort-value-numerically-at-end hash %N
:
find -type f -printf "%TY-%Tm-%Td\n " |
pl -ln '++$NUMBER{$_} '
find -type f -printf "%TY-%Tm-%Td\n " |
pl -ln '++$N{$_} '
1: 2018-07-19 1: 2019-04-10 … 34: 2020-02-11 93: 2020-02-29 2816: 2018-06-27 3307: 2019-05-11 6024: 2019-10-21 12159: 2019-10-24
Learn sign language! It's very handy. 😂
Count Files per Date with RollupRollup means, additionally to the previous case, sum up dates with the same prefix. The trick here is to count both for the actual year, month, and day, as well as replacing once only the day, once also the month with "__", and once also the year with "____". This sorts after numbers and gives a sum for all with the same leading numbers. Use the sort-by-key-and-stringify-at-end hash %R
:
find -type f -printf "%TY-%Tm-%Td\n " |
pl -ln 'do { ++$RESULT{$_} }
while s/[0-9]+(?=[-_]*$)/"_" x length $&/e '
find -type f -printf "%TY-%Tm-%Td\n " |
pl -ln 'do { ++$R{$_} }
while s/[0-9]+(?=[-_]*$)/"_" x length $&/e '
2018-06-27: 2816 2018-06-__: 2816 2018-07-19: 1 2018-07-__: 1 2018-__-__: 2817 2019-04-10: 1 2019-04-__: 1 … 2019-11-10: 11 2019-11-25: 6 2019-11-__: 17 2019-12-05: 4 2019-12-__: 4 2019-__-__: 21581 … 2020-05-14: 33 2020-05-15: 1 2020-05-17: 5 2020-05-29: 4 2020-05-__: 43 2020-__-__: 206 ____-__-__: 24604
Always remember you're unique, just like everyone else. 😂
Diff Several Inputs by a Unique KeyThe function k
stores the 2nd arg or chomped $_
in %K
keyed by 1st arg or $1
and the arg counter $ARGIND
(or $I
). Its sibling K
does the same using 1st arg or 0 as an index into @F
for the 1st part of the key. At the end these show only the rows differing between files. If you write to a terminal or specify --color the difference gets color-highlighted in per-character detail with Algorithm::Diff
, or in just one red blob without. Here are examples for how to alias these as canned commands.
Diff Several CSV, TSV, or passwd Files by 1st Field
This assumes commaless key fields and no newline in any field. Else you need a CSV-parser package. -F implies -a, which implies -n (even using older than Perl 5.20, which introduced this idea). -F, splits each line on commas, and K
by default takes the 1st field as your unique key:
pl -F, Keydiff *.csv
pl -F, K *.csv
11,H,Hydrogen,1: H & alkali metal,1.008 n/a1,H,Hydrogen,1:alkali metal,1 44, Be ,B ery ll ium,2:a lkaline e arth metal ,9 .01 24, Pl ,P erlium,2:p lb asis ,5 .3 2.0 n/a 88,O,Oxygen,16:O & chalcogen,168,O,Oxygen,16:O & chalcogen,168,O,Oxygen,16:O 41and chalcogen,1641,Nb, Ni obium,5:no name,92.906 n/a41,Nb, C olum bium,5:no name,93 4242,Ve,Veritasium,6:an element of truth,i n/a n/a 7474,W, Tungsten ,6:transition metal,183.84 74,W, n/a 80Wolfram ,6:transition metal,183.880,Hg, Me rcury ,12:no name,200.592 80,Hg, Quicksilve r,12:no name,200.6 80,Hg, Hyd rargyrum ,12:no name,201 110 n/a110,Ds,Darmstadtium,10:transition metal, [ 281] 110,Ds,Darmstadtium,10:transition metal,281
This is similar but removes the key from the stored value, so it doesn't get repeated for each file. Note how k
by default uses $1
as a key for $_
. Additionally, in a -B begin program, show the filenames one per line:
pl -nB 'echo for @ARGV ' 'keydiff if s/(.+?),// ' *.csv
pl -nB 'e for @A ' 'k if s/(.+?),// ' *.csv
atom-weight-1.csv atom-weight-2.csv atom-weight-3.csv 1H,Hydrogen,1: H & alkali metal,1.008 n/aH,Hydrogen,1:alkali metal,1 4Be , Ber yl lium,2: al kalinee a rth metal, 9. 012 Pl n/a 8, Perlium,2: pl ba sis, 5. 32 .0O,Oxygen,16:O & chalcogen,16O,Oxygen,16:O & chalcogen,16O,Oxygen,16:O 41and chalcogen,16Nb, Ni obium,5:no name,92.906 n/aNb, C olum bium,5:no name,93 42Ve,Veritasium,6:an element of truth,i n/a n/a 74W, Tungsten ,6:transition metal,183.84 W, n/a 80Wolfram ,6:transition metal,183.8Hg, Me rcury ,12:no name,200.592 Hg, Quicksilve r,12:no name,200.6 Hg, Hyd rargyrum ,12:no name,201 110 n/aDs,Darmstadtium,10:transition metal, [ 281] Ds,Darmstadtium,10:transition metal,281
A variant of CSV is TSV, with tab as separator. Tab is \t
, which you must escape from the Shell as \\t
, either with or without repeated keys:
pl -F\\t Keydiff *.tsv
pl -F\\t K *.tsv
11 H Hydrogen 1: H & alkali metal 1.008 n/a1 H Hydrogen 1:alkali metal 1 44 Be B ery ll ium 2:a lkaline e arth metal 9 .01 24 Pl P erlium 2:p lb asis 5 .3 2.0 n/a 88 O Oxygen 16:O & chalcogen 168 O Oxygen 16:O & chalcogen 168 O Oxygen 16:O 41and chalcogen 1641 Nb Ni obium 5:no name 92.906 n/a41 Nb C olum bium 5:no name 93 4242 Ve Veritasium 6:an element of truth i n/a n/a 7474 W Tungsten 6:transition metal 183.84 74 W n/a 80Wolfram 6:transition metal 183.880 Hg Me rcury 12:no name 200.592 80 Hg Quicksilve r 12:no name 200.6 80 Hg Hyd rargyrum 12:no name 201 110 n/a110 Ds Darmstadtium 10:transition metal [ 281] 110 Ds Darmstadtium 10:transition metal 281
pl -n 'keydiff if s/(.+?)\t// ' *.tsv
pl -n 'k if s/(.+?)\t// ' *.tsv
1H Hydrogen 1: H & alkali metal 1.008 n/aH Hydrogen 1:alkali metal 1 4Be B er yl lium 2: al kalinee a rth metal9 . 012 Pl n/a 8P erlium 2: pl ba sis5 . 32 .0O Oxygen 16:O & chalcogen 16O Oxygen 16:O & chalcogen 16O Oxygen 16:O 41and chalcogen 16Nb Ni obium 5:no name 92.906 n/aNb C olum bium 5:no name 93 42Ve Veritasium 6:an element of truth i n/a n/a 74W Tungsten 6:transition metal 183.84 W n/a 80Wolfram 6:transition metal 183.8Hg Me rcury 12:no name 200.592 Hg Quicksilve r 12:no name 200.6 Hg Hyd rargyrum 12:no name 201 110 n/aDs Darmstadtium 10:transition metal [ 281] Ds Darmstadtium 10:transition metal 281
The same, with a colon as separator, if you want to compare passwd files from several hosts. Here we additionally need to ignore commented out lines:
pl -F: 'Keydiff unless /^#/ ' /etc/passwd passwd*
pl -n 'keydiff if s/^([^#].*?):// ' /etc/passwd passwd*
pl -F: 'K unless /^#/ ' /etc/passwd passwd*
pl -n 'k if s/^([^#].*?):// ' /etc/passwd passwd*
Growing old you forget to zip up your fly. Later you forget to unzip your fly. 🤓
Diff Several zip Archives by Member NameThis uses the same mechanism as the CSV example. Additionally, through the p
block, it reads the output of unzip -vql
for each archive. That has an almost fixed format, except with extreme member sizes:
pl -oB 'echo for @ARGV ' 'piped {
keydiff if s@.{29,}% .{16} [\da-f]{8}\K (.+)@@;
} "unzip", "-vqq", $_ ' *.zip
pl -oB 'e for @A ' 'p {
k if s@.{29,}% .{16} [\da-f]{8}\K (.+)@@;
} "unzip", "-vqq", $_ ' *.zip
perl-5.30.0.zip perl-5.30.1.zip perl-5.30.2.zip perl-5.30.3.zip AUTHORS48 831 Defl:N 22282 54% 2019 -05 -11 11 :50 cc2a1286 48 864 Defl:N 22297 54% 2019 -1 0-24 23 :27 b793b cc5 48 927 Defl:N 22338 54% 2020 -02 -29 12 :55 8 ce cd35e 48 927 Defl:N 22338 54% 2020 -02 -11 14 :31 8 ce cd35e Artistic6321 Defl:N 2400 62% 2019-0 5 -11 11 :50 fa53ec296321 Defl:N 2400 62% 2019- 1 0-24 22 :17 fa53ec296321 Defl:N 2400 62% 2019- 1 0-24 22 :17 fa53ec296321 Defl:N 2400 62% 2019- Changes1 0-21 13 :20 fa53ec2931 68 Defl:N 1273 60% 2018 -06 -27 13 :17 66a9a f3e 31 11 Defl:N 1246 60% 2019 -1 0-27 10 :52 f826c 349 31 11 Defl:N 1246 60% 2019 -1 0-27 10 :52 f826c 349 31 11 Defl:N 1246 60% 2019 -1 0-28 09 :05 f826c 349 Configure587 6 87 Defl:N 1488 90 75% 2019 -05 -11 11:50 144c0f25 587 6 87 Defl:N 1488 90 75% 2019 -1 0-24 23:27 144c0f25 5878 25 Defl:N 148954 75% 2020 -02 -29 12:55 6761d877 5878 25 Defl:N 148954 75% 2020 -02 -27 18:40 6761d877 Copying12632 Defl:N 4924 61% 201 8 -06 -27 13:17 7117fcb912632 Defl:N 4924 61% 201 9 -1 0-24 22: 17 7117fcb912632 Defl:N 4924 61% 201 9 -1 0-24 22: 17 7117fcb912632 Defl:N 4924 61% 201 EXTERN.h9 -1 0-21 13:20 7117fcb91652 Defl:N 575 65% 2019-0 5 -11 11 :50 725e28ae1652 Defl:N 575 65% 2019- 1 0-24 23 :27 725e28ae1652 Defl:N 575 65% 2019- 1 0-24 23 :27 725e28ae1652 Defl:N 575 65% 2019- INSTALL1 0-21 13 :20 725e28ae10 8059 Defl:N 3735 1 65% 2019 -05 -11 11 :50 45af554 510 8085 Defl:N 3737 1 65% 2019 -1 0-24 23 :27 e 5f2f22b 10 7649 Defl:N 372 11 65% 2020 -02 -29 12 :55 9db83c1e 10 7649 Defl:N 372 11 65% 2020 -05 -14 13 :3 516726160 INTERN.h1309 Defl:N 505 61% 2019-0 5 -11 11 :50 2bf79a271309 Defl:N 505 61% 2019- 1 0-24 22 :17 2bf79a271309 Defl:N 505 61% 2019- 1 0-24 22 :17 2bf79a271309 Defl:N 505 61% 2019- MANIFEST1 0-21 13 :20 2bf79a27331 194 Defl:N 64654 81% 2019 -05 -12 01 :02 7c361194 331 248 Defl:N 64661 81% 2019 -1 0-24 23 :27 9e4f98b4 331 368 Defl:N 64708 81% 2020 -03 -14 13 :13 c858ba6a 331 422 Defl:N 64714 81% 2020 -05 -14 11 :15 28a30dfa …
Java .jar, .ear & .war files (which are aliases for .zip), after a clean build have many class files with the identical CRC but a different date. This excludes the date. Here are examples for how to combine these variants as Shell functions:
pl -o 'piped {
keydiff $2 if s@.{16} ([\da-f]{8}) (.+)@$1@;
} "unzip", "-vqq", $_ ' *.zip
pl -o 'p {
k $2 if s@.{16} ([\da-f]{8}) (.+)@$1@;
} "unzip", "-vqq", $_ ' *.zip
AUTHORS48 831 Defl:N 22282 54% cc2a1286 48 864 Defl:N 22297 54%b793b cc5 48 927 Defl:N 22338 54%8 ce cd35e 48 927 Defl:N 22338 54%8 ce cd35e Changes31 68 Defl:N 1273 60%66a9a f3e 31 11 Defl:N 1246 60% f826c 349 31 11 Defl:N 1246 60% f826c 349 31 11 Defl:N 1246 60% f826c 349 Configure587 6 87 Defl:N 1488 90 75% 144c0f25 587 6 87 Defl:N 1488 90 75% 144c0f25 5878 25 Defl:N 148954 75%676 1d877 5878 25 Defl:N 148954 75%676 1d877 INSTALL10 8059 Defl:N 3735 1 65%45af5545 10 8085 Defl:N 3737 1 65%e5f2f22b 10 7649 Defl:N 372 11 65%9db83c1e 10 7649 Defl:N 372 11 65%16726160 …
Browsers have a bug of not checking for updated CSS & JavaScript. A common workaround is to add a hex number to those file names. In that case use only the meaningful part of the filename as a key:
pl -o 'piped {
keydiff $2
if s@.{16} ([\da-f]{8}) (.+?)(?:\.([0-9a-f]{20})(\..[a-z]+))?$@if( $3 ) {
$n = "$2.\$x$4"; "$1 \$x=$3"
} else {
$n = $2; $1
}@e
} "unzip", "-vqq", $_ ' *.jar
pl -o 'p {
k $2
if s@.{16} ([\da-f]{8}) (.+?)(?:\.([0-9a-f]{20})(\..[a-z]+))?$@if( $3 ) {
$n = "$2.\$x$4"; "$1 \$x=$3"
} else {
$n = $2; $1
}@e
} "unzip", "-vqq", $_ ' *.jar
Actually I'm very different. But I rarely find time for it. –
This is like the zip example. Alas, tar gives no checksums, so this is less reliable. Exclude directories, by taking only lines not starting with a d
. Each time pl sees a wider owner/group or file size, columns shift right. Reformat the columns, to not show this as a difference:
pl -oB 'echo for @ARGV ' 'piped {
keydiff $4
if s!^[^d]\S+ \K(.+?) +(\d+) (.{16}) (.+)!Form "%-20s %10d %s", $1, $2, $3!e;
} "tar", "-tvf", $_ ' *.tar *.tgz *.txz
pl -oB 'e for @A ' 'p {
k $4
if s!^[^d]\S+ \K(.+?) +(\d+) (.{16}) (.+)!F "%-20s %10d %s", $1, $2, $3!e;
} "tar", "-tvf", $_ ' *.tar *.tgz *.txz
perl-5.30.0.txz perl-5.30.1.txz perl-5.30.2.txz perl-5.30.3.txz … cpan/Compress-Raw-Bzip2/bzip2-src/decompress.c-r--r--r-- pfeiffer/pfeiffer 2 094 8 2018 -06 -27 13:17 -r--r--r-- pfeiffer/pfeiffer 2 094 8 2019 -1 0-24 22: 17 -r--r--r-- pfeiffer/pfeiffer 2 12 87 2020 -02 -29 12:55 -r--r--r-- pfeiffer/pfeiffer 2 cpan/Compress-Raw-Bzip2/bzip2-src/huffman.c12 87 2020 -02 -1 218:4 1-r--r--r-- pfeiffer/pfeiffer 69 91 2018 -06 -27 13:17 -r--r--r-- pfeiffer/pfeiffer 69 91 2019 -1 0-24 22: 17 -r--r--r-- pfeiffer/pfeiffer 69 86 2020 -02 -29 12:55 -r--r--r-- pfeiffer/pfeiffer 69 cpan/Compress-Raw-Bzip2/bzip2-src/randtable.c86 2020 -02 -1 218:4 1-r--r--r-- pfeiffer/pfeiffer 386 6 2018 -06 -27 13:17 -r--r--r-- pfeiffer/pfeiffer 386 6 2019 -1 0-24 22: 17 -r--r--r-- pfeiffer/pfeiffer 386 1 2020 -02 -29 12:55 -r--r--r-- pfeiffer/pfeiffer 386 cpan/Compress-Raw-Bzip2/fallback/constants.h1 2020 -02 -1 218:4 1-r--r--r-- pfeiffer/pfeiffer 7238 201 8 -06 -27 13:17 -r--r--r-- pfeiffer/pfeiffer 7238 201 9 -1 0-24 22: 17 -r--r--r-- pfeiffer/pfeiffer 7238 201 9 -1 0-24 22: 17 -r--r--r-- pfeiffer/pfeiffer 7238 201 9 -1 0-21 13:20 …
Same without the date:
pl -o 'piped {
keydiff $3
if s!^[^d]\S+ \K(.+?) +(\d+) .{16} (.+)!Form "%-20s %10d", $1, $2!e;
} "tar", "-tvf", $_ ' *.tar *.tgz *.txz
pl -o 'p {
k $3
if s!^[^d]\S+ \K(.+?) +(\d+) .{16} (.+)!F "%-20s %10d", $1, $2!e;
} "tar", "-tvf", $_ ' *.tar *.tgz *.txz
… cpan/Compress-Raw-Bzip2/bzip2-src/decompress.c-r--r--r-- pfeiffer/pfeiffer 2 094 8-r--r--r-- pfeiffer/pfeiffer 2 094 8-r--r--r-- pfeiffer/pfeiffer 2 12 87 -r--r--r-- pfeiffer/pfeiffer 2 12 87 cpan/Compress-Raw-Bzip2/bzip2-src/huffman.c-r--r--r-- pfeiffer/pfeiffer 69 91 -r--r--r-- pfeiffer/pfeiffer 69 91 -r--r--r-- pfeiffer/pfeiffer 69 86 -r--r--r-- pfeiffer/pfeiffer 69 86 cpan/Compress-Raw-Bzip2/bzip2-src/randtable.c-r--r--r-- pfeiffer/pfeiffer 386 6 -r--r--r-- pfeiffer/pfeiffer 386 6 -r--r--r-- pfeiffer/pfeiffer 386 1 -r--r--r-- pfeiffer/pfeiffer 386 1 cpan/Compress-Raw-Bzip2/lib/Compress/Raw/Bzip2.pm-r--r--r-- pfeiffer/pfeiffer 10 783 -r--r--r-- pfeiffer/pfeiffer 10 783 -r--r--r-- pfeiffer/pfeiffer 1 1 009 -r--r--r-- pfeiffer/pfeiffer 1 1 009 …
Tarballs from the internet have a top directory of name-version/, which across versions would make every member have a different key. Exclude the 1st path element from the key by matching [^/]+/
before the last paren group:
pl -o 'piped {
keydiff $4
if s!^[^d]\S+ \K(.+?) +(\d+) (.{16}) [^/]+/(.+)!Form "%-20s %10d %s", $1, $2, $3!e;
} "tar", "-tvf", $_ ' *.tar *.tgz *.txz
pl -o 'p {
k $4
if s!^[^d]\S+ \K(.+?) +(\d+) (.{16}) [^/]+/(.+)!F "%-20s %10d %s", $1, $2, $3!e;
} "tar", "-tvf", $_ ' *.tar *.tgz *.txz
.dir-locals.el-r--r--r-- sawy er /sawy er 208 2018 -06 -27 13:17 -r--r--r-- St eve /Non e208 201 9 -1 0-24 22: 17 -r--r--r-- St eve /Non e208 201 9 -1 0-24 22: 17 -r--r--r-- St eve /Non e208 201 9 -1 0-21 13:20 .lgtm.yml-r--r--r-- sawy er /sawy er 347 2019-05 -11 11 :50 -r--r--r-- St eve /Non e347 2019- 1 0-24 22 :17 -r--r--r-- St eve /Non e347 2019- 1 0-24 22 :17 -r--r--r-- St eve /Non e347 2019- 1 0-21 13 :20 .metaconf-exclusions.txt-r--r--r-- sawy er /sawy er 1317 2019-05 -11 11 :50 -r--r--r-- St eve /Non e1317 2019- 1 0-24 22 :17 -r--r--r-- St eve /Non e1317 2019- 1 0-24 22 :17 -r--r--r-- St eve /Non e1317 2019- 1 0-21 13 :20 .travis.yml-r--r--r-- sawy er /sawy er 2203 2019-05 -11 11 :50 -r--r--r-- St eve /Non e2203 2019- 1 0-24 23 :27 -r--r--r-- St eve /Non e2203 2019- 1 0-24 23 :27 -r--r--r-- St eve /Non e2203 2019- 1 0-21 13 :20 AUTHORS-r--r--r-- sawy er /sawy er 48831 2019 -05 -11 11 :50 -r--r--r-- St eve /Non e48 864 2019 -1 0-24 23 :27 -r--r--r-- St eve /Non e48 927 2020 -02 -29 12 :55 -r--r--r-- St eve /Non e48 927 2020 -02 -11 14 :31 Artistic-r--r--r-- sawy er /sawy er 6321 2019-05 -11 11 :50 -r--r--r-- St eve /Non e6321 2019- 1 0-24 22 :17 -r--r--r-- St eve /Non e6321 2019- 1 0-24 22 :17 -r--r--r-- St eve /Non e6321 2019- 1 0-21 13 :20 Changes-r--r--r-- sawy er /sawy er 3168 2018 -06 -27 13 :17 -r--r--r-- St eve /Non e31 11 2019 -1 0-27 10 :52 -r--r--r-- St eve /Non e31 11 2019 -1 0-27 10 :52 -r--r--r-- St eve /Non e31 11 2019 -1 0-28 09 :05 …
Again, without the date and owner/group, which can also vary:
pl -o 'piped {
keydiff $2
if s!^[^d]\S+ \K.+? +(\d+) .{16} [^/]+/(.+)!Form "%10d", $1!e;
} "tar", "-tvf", $_ ' *.tar *.tgz *.txz
pl -o 'p {
k $2
if s!^[^d]\S+ \K.+? +(\d+) .{16} [^/]+/(.+)!F "%10d", $1!e;
} "tar", "-tvf", $_ ' *.tar *.tgz *.txz
AUTHORS-r--r--r-- 48 831 -r--r--r-- 48 864 -r--r--r-- 48 927 -r--r--r-- 48 927 Changes-r--r--r-- 31 68 -r--r--r-- 31 11 -r--r--r-- 31 11 -r--r--r-- 31 11 Configure-r-xr-xr-x 587 6 87 -r-xr-xr-x 587 6 87 -r-xr-xr-x 5878 25 -r-xr-xr-x 5878 25 …
Diff ELF Executables by Loaded Dependencies
You get the idea: you can do this for any command that outputs records with a unique key. This one looks at the required libraries and which file they came from. For a change, loop with -O and $A
to avoid the previous examples' confusion between outer $_
which were the CLI args, and the inner one, which were the read lines:
pl -O 'piped {
keydiff if s/^\t(.+\.so.*) => (.*) \(\w+\)/$2/;
} ldd => $ARGV ' exe1 exe2 lib*.so
pl -O 'p {
k if s/^\t(.+\.so.*) => (.*) \(\w+\)/$2/;
} ldd => $A ' exe1 exe2 lib*.so
It's even more useful if you use just the basename as a key because version numbers may change:
pl -O 'piped {
keydiff $2 if s/^\t((.+)\.so.* => .*) \(\w+\)/$1/;
} ldd => $ARGV ' exe1 exe2 lib*.so
pl -O 'p {
k $2 if s/^\t((.+)\.so.* => .*) \(\w+\)/$1/;
} ldd => $A ' exe1 exe2 lib*.so
A pig looking at an electric socket: "Oh no, who put you into that wall?" 🐽
Looking at PerlVERSION of a File
Print the first line (-P1) where the substitution was successful. To avoid the hassle of protecting them from (sometimes multiple levels of) Shell quoting, there are variables for single $q
& double $Q
:
pl -P1 's/(?:\bpackage\s+[\w:]+\s+|.+\bVERSION\s*=\s*)[v$Quote$quote]{0,2}([0-9.]+).+/$1/ ' pl
pl -P1 's/(?:\bpackage\s+[\w:]+\s+|.+\bVERSION\s*=\s*)[v$Q$q]{0,2}([0-9.]+).+/$1/ ' pl
0.91.1
For multiple files, add the filename, and reset (-r) the -P count for each file:
pl -rP1 's/(?:\bpackage\s+[\w:]+\s+|.+\bVERSION\s*=\s*)[v$Quote$quote]{0,2}([0-9.]+).+/$ARGV: $1/ ' *.pm
pl -rP1 's/(?:\bpackage\s+[\w:]+\s+|.+\bVERSION\s*=\s*)[v$Q$q]{0,2}([0-9.]+).+/$A: $1/ ' *.pm
Only POD or non-POD
You can extract either parts of a Perl file, with these commands. Note that they don't take the empty line before into account. If you want that, and you're sure the files adhere strictly to this convention, use the option -00P instead (not exactly as desired, the empty line comes after things but still, before next thing). If you want only the 1st POD (e.g., NAME & SYNOPSIS) use the option -P1 or -00P1:
pl -P '/^=\w/../^=cut/ ' file*
pl -P 'not /^=\w/../^=cut/ ' file*
Count Perl Code
This makes __DATA__
or __END__
the last inspected line of (unlike in perl -n
!) each file. It strips any comment (not quite reliably, also inside a string). Then it strips leading whitespace and adds the remaining length to print-at-end $R
:
pl -ln 'last if /^__(?:DATA|END)__/;
s/(?:^|\s+)#.*//s;
s/^\s+//;
$RESULT += length ' *.pm
pl -ln 'last if /^__(?:DATA|END)__/;
s/(?:^|\s+)#.*//s;
s/^\s+//;
$R += length ' *.pm
If you want the count per file, instead of $R
use either sort-lexically $RESULT{$ARGV}
(or $R{$A}
) or sort-numerically $NUMBER{$ARGV}
(or $N{$A}
).
Content of a Package
Pl's e
can print any item. Packages are funny hashes, with two colons at the end. Backslashing the variable passes it as a unit to Data::Dumper
, which gets loaded on demand in this case. Otherwise, all elements would come out just separated by spaces:
pl 'echo \%List::Util:: '
pl 'e \%List::Util:: '
{ BEGIN => *List::Util::BEGIN, EXPORT => *List::Util::EXPORT, EXPORT_FAIL => *List::Util::EXPORT_FAIL, EXPORT_OK => *List::Util::EXPORT_OK, EXPORT_TAGS => *List::Util::EXPORT_TAGS, ISA => *List::Util::ISA, RAND => *List::Util::RAND, REAL_MULTICALL => *List::Util::List::Util, VERSION => *List::Util::VERSION, XS_VERSION => *List::Util::XS_VERSION, '_Pair::' => *{'List::Util::_Pair::'}, all => *List::Util::all, any => *List::Util::any, bootstrap => *List::Util::bootstrap, first => *List::Util::first, head => *List::Util::head, import => *List::Util::import, max => *List::Util::max, maxstr => *List::Util::maxstr, mesh => *List::Util::mesh, mesh_longest => *List::Util::mesh_longest, mesh_shortest => *List::Util::mesh_shortest, min => *List::Util::min, minstr => *List::Util::minstr, none => *List::Util::none, notall => *List::Util::notall, pairfirst => *List::Util::pairfirst, pairgrep => *List::Util::pairgrep, pairkeys => *List::Util::pairkeys, pairmap => *List::Util::pairmap, pairs => *List::Util::pairs, pairvalues => *List::Util::pairvalues, product => *List::Util::product, reduce => *List::Util::reduce, reductions => *List::Util::reductions, sample => *List::Util::sample, shuffle => *List::Util::shuffle, sum => *List::Util::sum, sum0 => *List::Util::sum0, tail => *List::Util::tail, uniq => *List::Util::uniq, uniqint => *List::Util::uniqint, uniqnum => *List::Util::uniqnum, uniqstr => *List::Util::uniqstr, unpairs => *List::Util::unpairs, zip => *List::Util::zip, zip_longest => *List::Util::zip_longest, zip_shortest => *List::Util::zip_shortest }
Library Loading
Where does perl load from, and what exactly has it loaded?
pl 'echo \@INC, \%INC '
pl 'e \@INC, \%INC '
[ '/etc/perl', '/usr/local/lib/x86_64-linux-gnu/perl/5.36.0', '/usr/local/share/perl/5.36.0', '/usr/lib/x86_64-linux-gnu/perl5/5.36', '/usr/share/perl5', '/usr/lib/x86_64-linux-gnu/perl-base', '/usr/lib/x86_64-linux-gnu/perl/5.36', '/usr/share/perl/5.36', '/usr/local/lib/site_perl' ] { 'Carp.pm' => '/usr/lib/x86_64-linux-gnu/perl-base/Carp.pm', 'Data/Dumper.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.36/Data/Dumper.pm', 'Exporter.pm' => '/usr/lib/x86_64-linux-gnu/perl-base/Exporter.pm', 'Exporter/Heavy.pm' => '/usr/lib/x86_64-linux-gnu/perl-base/Exporter/Heavy.pm', 'List/Util.pm' => '/usr/lib/x86_64-linux-gnu/perl-base/List/Util.pm', 'XSLoader.pm' => '/usr/lib/x86_64-linux-gnu/perl-base/XSLoader.pm', 'bytes.pm' => '/usr/lib/x86_64-linux-gnu/perl-base/bytes.pm', 'constant.pm' => '/usr/lib/x86_64-linux-gnu/perl-base/constant.pm', 'feature.pm' => '/usr/lib/x86_64-linux-gnu/perl-base/feature.pm', 'overloading.pm' => '/usr/lib/x86_64-linux-gnu/perl-base/overloading.pm', 'sort.pm' => '/usr/share/perl/5.36/sort.pm', 'strict.pm' => '/usr/lib/x86_64-linux-gnu/perl-base/strict.pm', 'warnings.pm' => '/usr/lib/x86_64-linux-gnu/perl-base/warnings.pm', 'warnings/register.pm' => '/usr/lib/x86_64-linux-gnu/perl-base/warnings/register.pm' }
Same, for a different Perl version, e.g., if you have perl5.28.1 in your path. This time copy %INC
before calling e
, which for an array or hashref needs to load Data::Dumper
on the fly and thus its dependencies:
pl -V5.28.1 '$orig = {%INC}; echo \@INC, $orig, \%INC '
pl -V5.28.1 '$orig = {%INC}; e \@INC, $orig, \%INC '
[ '/etc/perl', '/usr/local/lib/x86_64-linux-gnu/perl/5.28.1', '/usr/local/share/perl/5.28.1', '/usr/lib/x86_64-linux-gnu/perl5/5.28', '/usr/share/perl5', '/usr/lib/x86_64-linux-gnu/perl/5.28', '/usr/share/perl/5.28', '/usr/local/lib/site_perl', '/usr/lib/x86_64-linux-gnu/perl-base' ] { 'Exporter.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/Exporter.pm', 'Exporter/Heavy.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/Exporter/Heavy.pm', 'List/Util.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/List/Util.pm', 'XSLoader.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/XSLoader.pm', 'feature.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/feature.pm', 'sort.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/sort.pm', 'strict.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/strict.pm', 'warnings.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/warnings.pm' } { 'Carp.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/Carp.pm', 'Data/Dumper.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/Data/Dumper.pm', 'Exporter.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/Exporter.pm', 'Exporter/Heavy.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/Exporter/Heavy.pm', 'List/Util.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/List/Util.pm', 'XSLoader.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/XSLoader.pm', 'bytes.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/bytes.pm', 'constant.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/constant.pm', 'feature.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/feature.pm', 'overloading.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/overloading.pm', 'sort.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/sort.pm', 'strict.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/strict.pm', 'warnings.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/warnings.pm', 'warnings/register.pm' => '/usr/lib/x86_64-linux-gnu/perl/5.28/warnings/register.pm' }
Configuration
You get %Config::Config
loaded on demand and returned by C
:
pl 'echo Config '
pl 'e C '
{ Author => '', CONFIG => 'true', Date => '', Header => '', Id => '', Locker => '', Log => '', PATCHLEVEL => '30', PERL_API_REVISION => '5', PERL_API_SUBVERSION => '0', PERL_API_VERSION => '30', … }
It returns a hash reference, from which you can look up an entry:
pl 'echo Config->{sitelib} '
pl 'e C->{sitelib} '
/usr/local/share/perl/5.36.0
You can also return a sub-hash, of only the keys matching any regexps you pass:
pl 'echo Config "random", qr/stream/ '
pl 'e C "random", qr/stream/ '
{ d_random_r => 'define', d_srandom_r => 'define', d_stdio_stream_array => undef, random_r_proto => 'REENTRANT_PROTO_I_St', srandom_r_proto => 'REENTRANT_PROTO_I_TS', stdio_stream_array => '' }
Tables
Number Bases
Perl natively handles the 4 different bases common to programming. If you want to list them side by side, quadruple them and f
them with the 4 corresponding formats. Note the alternate parameter index syntax "1:":
pl 'form "0b%08b 0%1:03o %1:3d 0x%1:02x"
for 0..0xff '
pl 'f "0b%08b 0%1:03o %1:3d 0x%1:02x"
for 0..0xff '
0b00000000 0000 0 0x00 0b00000001 0001 1 0x01 0b00000010 0002 2 0x02 0b00000011 0003 3 0x03 0b00000100 0004 4 0x04 0b00000101 0005 5 0x05 0b00000110 0006 6 0x06 0b00000111 0007 7 0x07 0b00001000 0010 8 0x08 0b00001001 0011 9 0x09 0b00001010 0012 10 0x0a 0b00001011 0013 11 0x0b 0b00001100 0014 12 0x0c 0b00001101 0015 13 0x0d 0b00001110 0016 14 0x0e 0b00001111 0017 15 0x0f 0b00010000 0020 16 0x10 0b00010001 0021 17 0x11 0b00010010 0022 18 0x12 0b00010011 0023 19 0x13 0b00010100 0024 20 0x14 0b00010101 0025 21 0x15 0b00010110 0026 22 0x16 0b00010111 0027 23 0x17 0b00011000 0030 24 0x18 0b00011001 0031 25 0x19 0b00011010 0032 26 0x1a 0b00011011 0033 27 0x1b 0b00011100 0034 28 0x1c 0b00011101 0035 29 0x1d 0b00011110 0036 30 0x1e 0b00011111 0037 31 0x1f 0b00100000 0040 32 0x20 0b00100001 0041 33 0x21 0b00100010 0042 34 0x22 … 0b11110101 0365 245 0xf5 0b11110110 0366 246 0xf6 0b11110111 0367 247 0xf7 0b11111000 0370 248 0xf8 0b11111001 0371 249 0xf9 0b11111010 0372 250 0xfa 0b11111011 0373 251 0xfb 0b11111100 0374 252 0xfc 0b11111101 0375 253 0xfd 0b11111110 0376 254 0xfe 0b11111111 0377 255 0xff
That makes a rather long table. You can get a better overview with 4 nested tables, looping over a preset @A
:
pl -OA '0..0x3f ' 'say join "\t",
map Form( "0b%08b 0%1:03o %1:3d 0x%1:02x", $ARGV+$_ ), 0, 0x40, 0x80, 0xc0 '
pl -OA '0..0x3f ' 'say join "\t",
map F( "0b%08b 0%1:03o %1:3d 0x%1:02x", $A+$_ ), 0, 0x40, 0x80, 0xc0 '
0b00000000 0000 0 0x00 0b01000000 0100 64 0x40 0b10000000 0200 128 0x80 0b11000000 0300 192 0xc0 0b00000001 0001 1 0x01 0b01000001 0101 65 0x41 0b10000001 0201 129 0x81 0b11000001 0301 193 0xc1 0b00000010 0002 2 0x02 0b01000010 0102 66 0x42 0b10000010 0202 130 0x82 0b11000010 0302 194 0xc2 0b00000011 0003 3 0x03 0b01000011 0103 67 0x43 0b10000011 0203 131 0x83 0b11000011 0303 195 0xc3 0b00000100 0004 4 0x04 0b01000100 0104 68 0x44 0b10000100 0204 132 0x84 0b11000100 0304 196 0xc4 0b00000101 0005 5 0x05 0b01000101 0105 69 0x45 0b10000101 0205 133 0x85 0b11000101 0305 197 0xc5 0b00000110 0006 6 0x06 0b01000110 0106 70 0x46 0b10000110 0206 134 0x86 0b11000110 0306 198 0xc6 0b00000111 0007 7 0x07 0b01000111 0107 71 0x47 0b10000111 0207 135 0x87 0b11000111 0307 199 0xc7 0b00001000 0010 8 0x08 0b01001000 0110 72 0x48 0b10001000 0210 136 0x88 0b11001000 0310 200 0xc8 0b00001001 0011 9 0x09 0b01001001 0111 73 0x49 0b10001001 0211 137 0x89 0b11001001 0311 201 0xc9 0b00001010 0012 10 0x0a 0b01001010 0112 74 0x4a 0b10001010 0212 138 0x8a 0b11001010 0312 202 0xca … 0b00111010 0072 58 0x3a 0b01111010 0172 122 0x7a 0b10111010 0272 186 0xba 0b11111010 0372 250 0xfa 0b00111011 0073 59 0x3b 0b01111011 0173 123 0x7b 0b10111011 0273 187 0xbb 0b11111011 0373 251 0xfb 0b00111100 0074 60 0x3c 0b01111100 0174 124 0x7c 0b10111100 0274 188 0xbc 0b11111100 0374 252 0xfc 0b00111101 0075 61 0x3d 0b01111101 0175 125 0x7d 0b10111101 0275 189 0xbd 0b11111101 0375 253 0xfd 0b00111110 0076 62 0x3e 0b01111110 0176 126 0x7e 0b10111110 0276 190 0xbe 0b11111110 0376 254 0xfe 0b00111111 0077 63 0x3f 0b01111111 0177 127 0x7f 0b10111111 0277 191 0xbf 0b11111111 0377 255 0xff
If you prefer to enumerate sideways, it's easier. @A
is in the right order so we can loop in chunks of 4:
pl -O4 -A '0..0xff ' 'form join( "\t", map "0b%08b 0%$_:03o %$_:3d 0x%$_:02x", 1..4 ), @$ARGV '
pl -O4 -A '0..0xff ' 'f join( "\t", map "0b%08b 0%$_:03o %$_:3d 0x%$_:02x", 1..4 ), @$A '
0b00000000 0000 0 0x00 0b00000001 0001 1 0x01 0b00000010 0002 2 0x02 0b00000011 0003 3 0x03 0b00000100 0004 4 0x04 0b00000101 0005 5 0x05 0b00000110 0006 6 0x06 0b00000111 0007 7 0x07 0b00001000 0010 8 0x08 0b00001001 0011 9 0x09 0b00001010 0012 10 0x0a 0b00001011 0013 11 0x0b … 0b11111000 0370 248 0xf8 0b11111001 0371 249 0xf9 0b11111010 0372 250 0xfa 0b11111011 0373 251 0xfb 0b11111100 0374 252 0xfc 0b11111101 0375 253 0xfd 0b11111110 0376 254 0xfe 0b11111111 0377 255 0xff
Inflation
Inflation is rearing its head. Here's the relative value of 1000 euros, dollars, … after n years, with 1-20% inflation:
pl -MList::MoreUtils=pairwise '@a = (1000) x (@b = map 1 - $_ / 100, 1..20);
form " " . (" " . (" %3d%%" x 5)) x 4, 1..20;
form "%2d" . (" " . (" %4d" x 5)) x 4, $_, pairwise { $a *= $b } @a, @b for 1..30 '
pl -MList::MoreUtils=pairwise '@a = (1000) x (@b = map 1 - $_ / 100, 1..20);
f " " . (" " . (" %3d%%" x 5)) x 4, 1..20;
f "%2d" . (" " . (" %4d" x 5)) x 4, $_, pairwise { $a *= $b } @a, @b for 1..30 '
1% 2% 3% 4% 5% 6% 7% 8% 9% 10% 11% 12% 13% 14% 15% 16% 17% 18% 19% 20% 1 990 980 970 960 950 940 929 920 910 900 890 880 870 860 850 840 830 820 810 800 2 980 960 940 921 902 883 864 846 828 810 792 774 756 739 722 705 688 672 656 640 3 970 941 912 884 857 830 804 778 753 729 704 681 658 636 614 592 571 551 531 512 4 960 922 885 849 814 780 748 716 685 656 627 599 572 547 522 497 474 452 430 409 5 950 903 858 815 773 733 695 659 624 590 558 527 498 470 443 418 393 370 348 327 6 941 885 832 782 735 689 646 606 567 531 496 464 433 404 377 351 326 304 282 262 7 932 868 807 751 698 648 601 557 516 478 442 408 377 347 320 295 271 249 228 209 8 922 850 783 721 663 609 559 513 470 430 393 359 328 299 272 247 225 204 185 167 9 913 833 760 692 630 572 520 472 427 387 350 316 285 257 231 208 186 167 150 134 10 904 817 737 664 598 538 483 434 389 348 311 278 248 221 196 174 155 137 121 107 11 895 800 715 638 568 506 450 399 354 313 277 245 216 190 167 146 128 112 98 85 12 886 784 693 612 540 475 418 367 322 282 246 215 188 163 142 123 106 92 79 68 13 877 769 673 588 513 447 389 338 293 254 219 189 163 140 120 103 88 75 64 54 14 868 753 652 564 487 420 362 311 267 228 195 167 142 121 102 87 73 62 52 43 15 860 738 633 542 463 395 336 286 243 205 174 146 123 104 87 73 61 50 42 35 16 851 723 614 520 440 371 313 263 221 185 154 129 107 89 74 61 50 41 34 28 17 842 709 595 499 418 349 291 242 201 166 137 113 93 76 63 51 42 34 27 22 18 834 695 577 479 397 328 270 222 183 150 122 100 81 66 53 43 34 28 22 18 19 826 681 560 460 377 308 251 205 166 135 109 88 70 56 45 36 29 23 18 14 20 817 667 543 442 358 290 234 188 151 121 97 77 61 48 38 30 24 18 14 11 21 809 654 527 424 340 272 217 173 137 109 86 68 53 42 32 25 19 15 11 9 22 801 641 511 407 323 256 202 159 125 98 77 60 46 36 28 21 16 12 9 7 23 793 628 496 391 307 240 188 146 114 88 68 52 40 31 23 18 13 10 7 5 24 785 615 481 375 291 226 175 135 103 79 61 46 35 26 20 15 11 8 6 4 25 777 603 466 360 277 212 162 124 94 71 54 40 30 23 17 12 9 7 5 3 26 770 591 452 345 263 200 151 114 86 64 48 36 26 19 14 10 7 5 4 3 27 762 579 439 332 250 188 140 105 78 58 43 31 23 17 12 9 6 4 3 2 28 754 567 426 318 237 176 131 96 71 52 38 27 20 14 10 7 5 3 2 1 29 747 556 413 306 225 166 121 89 64 47 34 24 17 12 8 6 4 3 2 1 30 739 545 401 293 214 156 113 81 59 42 30 21 15 10 7 5 3 2 1 1
ISO replaced 8 standards by one. Now we have 9 standards. 😢
ISO Paper SizesSince @A
is initially empty, -A isn't looping. We can do other things as a side effect, rather than in a separate -B. Use Perl's lovely list assignment to swap and alternately halve the numbers. Because halving happens before echoing, start with double size:
pl -oA '($w, $h) = (1189, 1682); 0..10 ' \
'form "A%-2d %4dmm x %4dmm", $_, ($w, $h) = ($h / 2, $w) '
pl -oA '($w, $h) = (1189, 1682); 0..10 ' \
'f "A%-2d %4dmm x %4dmm", $_, ($w, $h) = ($h / 2, $w) '
A0 841mm x 1189mm A1 594mm x 841mm A2 420mm x 594mm A3 297mm x 420mm A4 210mm x 297mm A5 148mm x 210mm A6 105mm x 148mm A7 74mm x 105mm A8 52mm x 74mm A9 37mm x 52mm A10 26mm x 37mm
You could widen the table to cover B- & C-formats, by extending each list of 2, to a corresponding list of 6, e.g., ($Aw, $Ah, $Bw, …)
. But a more algorithmic approach seems better. Triple the format (with cheat spaces at the beginning). The main program loops over @A
, thanks to -O, doing the same as above but on anonymous elements of @d
, which -A sets as a side effect:
pl -OA '@d = (["A", 1189, 1682], ["B", 1414, 2000], ["C", 1297, 1834]); 0..10 ' \
'form " %3s %4dmm x %4dmm" x 3,
map +("$$_[0]$ARGV", ($$_[1], $$_[2]) = ($$_[2] / 2, $$_[1])), @d '
pl -OA '@d = (["A", 1189, 1682], ["B", 1414, 2000], ["C", 1297, 1834]); 0..10 ' \
'f " %3s %4dmm x %4dmm" x 3,
map +("$$_[0]$A", ($$_[1], $$_[2]) = ($$_[2] / 2, $$_[1])), @d '
A0 841mm x 1189mm B0 1000mm x 1414mm C0 917mm x 1297mm A1 594mm x 841mm B1 707mm x 1000mm C1 648mm x 917mm A2 420mm x 594mm B2 500mm x 707mm C2 458mm x 648mm A3 297mm x 420mm B3 353mm x 500mm C3 324mm x 458mm A4 210mm x 297mm B4 250mm x 353mm C4 229mm x 324mm A5 148mm x 210mm B5 176mm x 250mm C5 162mm x 229mm A6 105mm x 148mm B6 125mm x 176mm C6 114mm x 162mm A7 74mm x 105mm B7 88mm x 125mm C7 81mm x 114mm A8 52mm x 74mm B8 62mm x 88mm C8 57mm x 81mm A9 37mm x 52mm B9 44mm x 62mm C9 40mm x 57mm A10 26mm x 37mm B10 31mm x 44mm C10 28mm x 40mm
What color is a mirror? It depends whom you ask. 😉
ANSI Background;Foreground Color TableYou get numbers to fill into "\e[BGm"
, "\e[FGm"
, or "\e[BG;FGm"
to get a color and close it with "\e[m"
. This shows two times twice 8 different colors for dim & bright and for background & foreground. Hence the multiplication of escape codes and of values to fill them.
This fills @A
in -A twice, the 2nd time looping over the 1st list, as though it came from the command line. It maps it to the 16-fold number format to print the header, swallowing every other number with 0-width. Then the main program loops over it pairwise with $A
, thanks to -o2, to print the body. It duplicates numbers with (N)x2
, once to go into the escape sequence, once to display them:
pl -o2A 1..8 -A '$_, $_+39, $_+8, $_+99 ' -B 'form "co: bg;fg"."%4d%.0s"x16, @ARGV;
$b = Form "\e[%dm%3d "x16, map +(($_)x2, ($_+60)x2), 30..37 ' \
'form "%2d: %4d; \e[%2:dm$b\e[m", @$_ '
pl -o2A 1..8 -A '$_, $_+39, $_+8, $_+99 ' -B 'f "co: bg;fg"."%4d%.0s"x16, @A;
$b = F "\e[%dm%3d "x16, map +(($_)x2, ($_+60)x2), 30..37 ' \
'f "%2d: %4d; \e[%2:dm$b\e[m", @$_ '
co: bg;fg 1 9 2 10 3 11 4 12 5 13 6 14 7 15 8 16 1: 40; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 9: 100; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 2: 41; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 10: 101; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 3: 42; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 11: 102; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 4: 43; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 12: 103; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 5: 44; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 13: 104; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 6: 45; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 14: 105; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 7: 46; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 15: 106; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 8: 47; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97 16: 107; 30 90 31 91 32 92 33 93 34 94 35 95 36 96 37 97
Terminal Rulers
If you need a ruler to better count the width of some other output, you can print out one of the following. These are either decimal, by groups of 5 or hex by groups of 4 and 8. The latter two do the same. But instead of one static ruler of length 100 or 256, they repeat and adapt. Depending on your terminal emulator and Shell, the variable $COLUMNS
may track the current width. If so, pass it in a single argument to "loop" over with -o, else provide the desired width yourself:
pl 'say map "$_...:.....", 0..9 '
pl 'say map "$_..:...|...:....", 0..9, "A".."F" '
pl -o 'say substr join( "", map "$_...:.....", 0..9 ) x ($_ / 100 + 1), 0, $_ ' $COLUMNS
pl -o 'say substr join( "", map "$_..:...|...:....", 0..9, "A".."F" ) x ($_ / 256 + 1), 0, $_ ' $COLUMNS
0...:.....1...:.....2...:.....3...:.....4...:.....5...:.....6...:.....7...:.....8...:.....9...:..... 0..:...|...:....1..:...|...:....2..:...|...:....3..:...|...:....4..:...|...:....5..:...|...:....6..:...|...:....7..:...|...:....8..:...|...:....9..:...|...:....A..:...|...:....B..:...|...:....C..:...|...:....D..:...|...:....E..:...|...:....F..:...|...:.... 0...:.....1...:.....2...:.....3...:.....4...:.....5...:.....6...:.....7...:..... 0..:...|...:....1..:...|...:....2..:...|...:....3..:...|...:....4..:...|...:....
Math
Minimum and Maximum
The List::Util
functions min
and max
are imported for you:
pl 'echo max 1..5 '
pl 'e max 1..5 '
5
If you have just several numbers on each line and want their minimums, you can autosplit (-a) to @F
:
pl -a 'echo min @FIELD ' file*
pl -a 'e min @FIELD ' file*
If on the same you just want the overall minimum, you can use the print-at-end variable $R
, which you initialise to infinity in a -B begin program:
pl -aB '$RESULT = "inf" ' '$RESULT = min $RESULT, @FIELD ' file*
pl -aB '$R = "inf" ' '$R = min $R, @FIELD ' file*
Likewise, for overall maximum, you start with negative infinity:
pl -aB '$RESULT = "-inf" ' '$RESULT = max $RESULT, @FIELD ' file*
pl -aB '$R = "-inf" ' '$R = max $R, @FIELD ' file*
Median, Quartiles, Percentiles
The median is the number where half the list is less, and half is greater. Similarly, the 1st quartile is where 25% are less and the 3rd where 25% are greater. Use a list slice to extract these 3 and a 97th percentile, by multiplying the fractional percentage with the list length:
pl -A 0..200 'echo @ARGV[map $_*@ARGV, .25, .5, .75, .97] '
pl -A 0..200 'e @A[map $_*@A, .25, .5, .75, .97] '
50 100 150 194
If you'd rather have names associated, assign them to hash slice in sort-numerically-at-end %N
, whose key order must match the percentages. Note how <…>
without wildcard characters is like qw(…)
:
pl -A 0..200 '@NUMBER{<lower median upper 97%>} = @ARGV[map $_*@ARGV, .25, .5, .75, .97] '
pl -A 0..200 '@N{<lower median upper 97%>} = @A[map $_*@A, .25, .5, .75, .97] '
50: lower 100: median 150: upper 194: 97%
80% of people consider themselves to be above average. 😂
Triangular Number, Factorial, and AveragesThe triangular number is defined as the sum of all numbers from 1 to n, e.g., 1 to 5. Factorial is the equivalent for products:
pl 'echo sum 1..5;
echo product 1..5 '
pl 'e sum 1..5;
e product 1..5 '
15 120
The sum of all list elements divided by the length of the list gives the linear average. Alternately the root mean square can be a fairer average because it accounts for the weights:
pl -A 11..200 'echo sum( @ARGV ) / @ARGV;
echo sqrt sum( map $_ ** 2, @ARGV ) / @ARGV '
pl -A 11..200 'e sum( @A ) / @A;
e sqrt sum( map $_ ** 2, @A ) / @A '
105.5 118.905424602917
Add Pairs or Tuples of Numbers
If you have a list of number pairs and want to add each 1st and each 2nd number, reduce
is your friend. Inside it map over the pair elements 0..1
:
pl 'echo reduce {
[map $a->[$_] + $b->[$_], 0..1]
} [1, 11], [2, 12], [3, 13] '
pl 'e reduce {
[map $a->[$_] + $b->[$_], 0..1]
} [1, 11], [2, 12], [3, 13] '
[ 6, 36 ]
If your list is a variable and is empty the result is undef
. You can insert a fallback zero element if you'd rather receive that for an empty list:
pl 'echo reduce {
[map $a->[$_] + $b->[$_], 0..1]
} [0, 0], @list '
pl 'e reduce {
[map $a->[$_] + $b->[$_], 0..1]
} [0, 0], @list '
[ 0, 0 ]
The above adds pairs because we iterate 0..1
. You can generalize this to tuples by iterating to the length of the 1st array:
pl 'echo reduce {
[map $a->[$_] + $b->[$_], 0..$#$a]
} [1, 11, 21], [2, 12, 22], [3, 13, 23] '
pl 'e reduce {
[map $a->[$_] + $b->[$_], 0..$#$a]
} [1, 11, 21], [2, 12, 22], [3, 13, 23] '
[ 6, 36, 66 ]
2 + 2 = 5 for extremely large values of 2. 😂
Big MathWith the bigint
, bignum
and bigrat
modules you can do arbitrary precision and semi-symbolic fractional math:
pl -Mbigint 'echo 123456789012345678901234567890 * 123456789012345678901234567890 '
pl -Mbigint 'e 123456789012345678901234567890 * 123456789012345678901234567890 '
15241578753238836750495351562536198787501905199875019052100
pl -Mbignum 'echo 1.23456789012345678901234567890 * 1.23456789012345678901234567890 '
pl -Mbignum 'e 1.23456789012345678901234567890 * 1.23456789012345678901234567890 '
1.52415787532388367504953515625361987875019051998750190521
pl -Mbigrat 'echo 1/23456789012345678901234567890 * 1/23456789012345678901234567890 '
pl -Mbigrat 'e 1/23456789012345678901234567890 * 1/23456789012345678901234567890 '
1/550220950769700970248437984536198787501905199875019052100
Primes
Fibonacci Numbers
These are, seeded with 1 and 1, all sums of the previous two. Show all up to a given maximum:
pl '$a = $_ = 1; echo;
while( $_ < $ARGV[0] ) {
echo; ($a, $_) = ($_, $a + $_)
} ' 50000
pl '$a = $_ = 1; e;
while( $_ < $A[0] ) {
e; ($a, $_) = ($_, $a + $_)
} ' 50000
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368
What's 3n plus n?
Collatz' unproven & unrefuted conjecture is that the function f(n) = if odd: 3n + 1 else n/2
will always lead to 1, when applied repeatedly to any natural number n
. Since if odd: 3n + 1
is even, we can skip it and go straight to the postsuccessor (3n + 1)/2 = (2n + n-1 + 2)/2 = n + int(n/2) + 1
. Loop with -o over remaining args in $_
. Having simplified the factor 3, inline the function as more efficient bit-ops:
pl -o 'echo "---" if $ARGIND; echo;
while( $_ > 1 ) {
if( $_ & 1 ) { ++($_ += $_ >> 1) } else { $_ >>= 1 }
echo;
} ' 23 27
pl -o 'e "---" if $I; e;
while( $_ > 1 ) {
if( $_ & 1 ) { ++($_ += $_ >> 1) } else { $_ >>= 1 }
e;
} ' 23 27
23 35 53 80 40 20 10 5 8 4 2 1 --- 27 41 62 31 47 71 107 161 242 121 182 91 137 206 103 155 233 350 175 263 395 593 890 445 668 334 167 251 377 566 283 425 638 319 479 719 1079 1619 2429 3644 1822 911 1367 2051 3077 4616 2308 1154 577 866 433 650 325 488 244 122 61 92 46 23 35 53 80 40 20 10 5 8 4 2 1
Add some fancy indent to show the turns, starting far enough right to let the snakes wiggle in both directions:
pl -o 'echo "---" if $ARGIND;
$i = 40;
while( 1 ) {
form "%*d", $i, $_;
last if $_ < 2;
if( $_ & 1 ) { ++$i; ++($_ += $_ >> 1) } else { --$i; $_ >>= 1 }
} ' 15 48 49
pl -o 'e "---" if $I;
$i = 40;
while( 1 ) {
f "%*d", $i, $_;
last if $_ < 2;
if( $_ & 1 ) { ++$i; ++($_ += $_ >> 1) } else { --$i; $_ >>= 1 }
} ' 15 48 49
15 23 35 53 80 40 20 10 5 8 4 2 1 --- 48 24 12 6 3 5 8 4 2 1 --- 49 74 37 56 28 14 7 11 17 26 13 20 10 5 8 4 2 1
Powers of 2 only get halved, so check only as long as the result has more than one bit on (n & (n - 1)
). Furthermore, this tricky problem alternates between the binary & ternary realms which never meet, as no power of three is even. At least we can show both bases, their square and product bases, as well as, out of habit, decimal. For bases use bigint
as of Perl 5.30. That would also allow astronomical numbers . Sadly, f
can't go beyond 64 bits, so this is also how you'd get around that. Also add direction indicators:
pl -OMbigint -B 'form $f = "%s %26s %16s %14s %12s %9s %8s", " ", @b = <2 3 4 6 9 10> ' '
$ARGV += 0; $b = 2; echo "---" if $ARGIND;
while( 1 ) {
form $f, qw(\\ / +)[$b], map $ARGV->to_base($_), @b;
last unless $ARGV & ($ARGV - 1);
if( $b = $ARGV & 1 ) { ++($ARGV += $ARGV >> 1) } else { $ARGV >>= 1 }
} ' 255 511
pl -OMbigint -B 'f $f = "%s %26s %16s %14s %12s %9s %8s", " ", @b = <2 3 4 6 9 10> ' '
$A += 0; $b = 2; e "---" if $I;
while( 1 ) {
f $f, qw(\\ / +)[$b], map $A->to_base($_), @b;
last unless $A & ($A - 1);
if( $b = $A & 1 ) { ++($A += $A >> 1) } else { $A >>= 1 }
} ' 255 511
2 3 4 6 9 10 + 11111111 100110 3333 1103 313 255 / 101111111 112012 11333 1435 465 383 / 1000111111 210022 20333 2355 708 575 / 1101011111 1011222 31133 3555 1158 863 / 10100001111 1202222 110033 5555 1688 1295 / 11110010111 2122222 132113 12555 2588 1943 / 101101100011 10222222 231203 21255 3888 2915 / 1000100010101 12222222 1010111 32125 5888 4373 / 1100110100000 22222222 1212200 50212 8888 6560 \ 110011010000 11111111 303100 23104 4444 3280 \ 11001101000 2020202 121220 11332 2222 1640 \ 1100110100 1010101 30310 3444 1111 820 \ 110011010 120012 12122 1522 505 410 \ 11001101 21121 3031 541 247 205 / 100110100 102102 10310 1232 372 308 \ 10011010 12201 2122 414 181 154 \ 1001101 2212 1031 205 85 77 / 1110100 11022 1310 312 138 116 \ 111010 2011 322 134 64 58 \ 11101 1002 131 45 32 29 / 101100 1122 230 112 48 44 \ 10110 211 112 34 24 22 \ 1011 102 23 15 12 11 / 10001 122 101 25 18 17 / 11010 222 122 42 28 26 \ 1101 111 31 21 14 13 / 10100 202 110 32 22 20 \ 1010 101 22 14 11 10 \ 101 12 11 5 5 5 / 1000 22 20 12 8 8 --- + 111111111 200221 13333 2211 627 511 / 1011111111 1001102 23333 3315 1042 767 / 10001111111 1120122 101333 5155 1518 1151 / 11010111111 2100222 122333 11555 2328 1727 / 101000011111 10112222 220133 15555 3488 2591 / 111100101111 12022222 330233 25555 5288 3887 / 1011011000111 21222222 1123013 42555 7888 5831 / 10001000101011 102222222 2020223 104255 12888 8747 / 11001101000001 122222222 3031001 140425 18888 13121 / 100110011100010 222222222 10303202 231042 28888 19682 \ 10011001110001 111111111 2121301 113321 14444 9841 / 11100110101010 202020202 3212222 152202 22222 14762 \ 1110011010101 101010101 1303111 54101 11111 7381 / 10101101000000 120012002 2231000 123132 16162 11072 \ 1010110100000 21121001 1112200 41344 7531 5536 \ 101011010000 10210112 223100 20452 3715 2768 \ 10101101000 1220021 111220 10224 1807 1384 \ 1010110100 221122 22310 3112 848 692 \ 101011010 110211 11122 1334 424 346 \ 10101101 20102 2231 445 212 173 / 100000100 100122 10010 1112 318 260 \ 10000010 11211 2002 334 154 130 \ 1000001 2102 1001 145 72 65 / 1100010 10122 1202 242 118 98 \ 110001 1211 301 121 54 49 / 1001010 2202 1022 202 82 74 \ 100101 1101 211 101 41 37 / 111000 2002 320 132 62 56 \ 11100 1001 130 44 31 28 \ 1110 112 32 22 15 14 \ 111 21 13 11 7 7 / 1011 102 23 15 12 11 / 10001 122 101 25 18 17 / 11010 222 122 42 28 26 \ 1101 111 31 21 14 13 / 10100 202 110 32 22 20 \ 1010 101 22 14 11 10 \ 101 12 11 5 5 5 / 1000 22 20 12 8 8
This lead to exciting findings here.
Separate Big Numbers with Commas, …
Loop and print with line-end (-opl) over remaining args in $_
. If reading from stdin or files, instead of arguments, use only -pl. After a decimal dot, insert a comma before each 4th commaless digit. Then do the same backwards from end or decimal dot, also for Perl style with underscores:
n='1234567 12345678 123456789 1234.5678 3.141 3.14159265358 '
pl -opl '1 while s/[,.]\d{3}\K(?=\d)/,/;
1 while s/\d\K(?=\d{3}(?:$|[.,]))/,/ ' $n
pl -opl '1 while s/[._]\d{3}\K(?=\d)/_/;
1 while s/\d\K(?=\d{3}(?:$|[._]))/_/ ' $n
1,234,567 12,345,678 123,456,789 1,234.567,8 3.141 3.141,592,653,58 1_234_567 12_345_678 123_456_789 1_234.567_8 3.141 3.141_592_653_58
The same for languages with a decimal comma, using either a dot or a space as spacer:
n='1234567 12345678 123456789 1234,5678 3,141 3,141592653589 '
pl -opl '1 while s/[,.]\d{3}\K(?=\d)/./;
1 while s/\d\K(?=\d{3}(?:$|[.,]))/./ ' $n
pl -opl '1 while s/[, ]\d{3}\K(?=\d)/ /;
1 while s/\d\K(?=\d{3}(?:$|[ ,]))/ / ' $n
1.234.567 12.345.678 123.456.789 1.234,567.8 3,141 3,141.592.653.589 1 234 567 12 345 678 123 456 789 1 234,567 8 3,141 3,141 592 653 589
Web
HTML Encoding
Sometimes you need to en- or decode HTML entities, e.g. for HTML displayed in a <pre>
. Only the 1st 2 characters are essential, but often these & < > " '
get encoded:
echo "<p class=\"example\">Planet pl's plucky & pleasant plasticity</p> " |
pl -pB '%ent = split /\b/, "&<lt>gt\"quot${q}apos";
$ent = join "", keys %ent ' \
's/([$ent])/&$ent{$1};/go '
echo "<p class="example">Planet pl's plucky &amp; pleasant plasticity</p> " |
pl -pB '%ent = split /\b/, "amp<<gt>quot\"apos$q";
$ent = join "|", keys %ent ' \
's/&(?:#x([0-9a-f]+)|#([0-9]+)|($ent));/$3 ? $ent{$3} : chr( $1 ? hex $1 : $2 )/egio '
<p class="example">Planet pl's plucky &amp; pleasant plasticity</p> <p class="example">Planet pl's plucky & pleasant plasticity</p>
URL Encoding
URL (or URI, or percent) encoding is used to embed arbitrary data, like another URL, into a URL. This makes it hard to read. So, let's decode it, also treating the non-standard, but widespread '+':
echo https%3a%2f%2frt%2ecpan%2eorg%2FDist%2FDisplay%2ehtml%3FStatus%3D%5F%5FActive%5F%5F%3BQueue%3DApp-pl |
pl -p 'tr/+/ /;
s/%([0-9a-f]{2})/chr hex $1/egi '
https://rt.cpan.org/Dist/Display.html?Status=__Active__;Queue=App-pl
Many characters pose no problem and are often left unencoded. But let's encode according to rfc3986, whereby only a very limited set of characters is exempt. Here -l matters, otherwise the newline would also get encoded:
echo 'https://rt.cpan.org/Dist/Display.html?Status=__Active__;Queue=App-pl ' |
pl -pl 's/[^0-9a-z~_.-]/Form "%%%02X", ord $&/egi '
echo 'https://rt.cpan.org/Dist/Display.html?Status=__Active__;Queue=App-pl ' |
pl -pl 's/[^0-9a-z~_.-]/F "%%%02X", ord $&/egi '
https%3A%2F%2Frt.cpan.org%2FDist%2FDisplay.html%3FStatus%3D__Active__%3BQueue%3DApp-pl
What do you call a sheep with no legs?
The h
function deals with the nerdy details and outputs as a hosts file. This canonically sorts the file by address type (localhost, link local, private, public), version (IPv4, IPv6) and address. You tack on any number of IP-addresses or hostnames, either as Perl arguments or on the command-line via @A
:
pl 'hosts qw(perl.org 127.0.0.1 perldoc.perl.org cpan.org) '
pl 'h qw(perl.org 127.0.0.1 perldoc.perl.org cpan.org) '
127.0.0.1 localhost 139.178.67.96 perl.org 146.75.118.132 perldoc.perl.org 151.101.1.55 cpan.org 151.101.65.55 cpan.org 151.101.129.55 cpan.org 151.101.193.55 cpan.org 2a04:4e42:8d::644 perldoc.perl.org
pl 'hosts @ARGV ' perl.org 127.0.0.1 perldoc.perl.org cpan.org
pl 'h @A ' perl.org 127.0.0.1 perldoc.perl.org cpan.org
If you don't want it to merge & sort, call h
for individual addresses:
pl 'hosts for qw(perl.org 127.0.0.1 perldoc.perl.org cpan.org) '
pl 'h for qw(perl.org 127.0.0.1 perldoc.perl.org cpan.org) '
139.178.67.96 perl.org 127.0.0.1 localhost 146.75.118.132 perldoc.perl.org 2a04:4e42:8d::644 perldoc.perl.org 151.101.1.55 cpan.org 151.101.65.55 cpan.org 151.101.129.55 cpan.org 151.101.193.55 cpan.org
pl -o hosts perl.org 127.0.0.1 perldoc.perl.org cpan.org
pl -o h perl.org 127.0.0.1 perldoc.perl.org cpan.org
If your input comes from files, collect it in a list and perform at end (-E):
pl -lnE 'hosts @list ' 'push @list, $_ ' file*
pl -lnE 'h @list ' 'push @list, $_ ' file*
Miscellaneous
There are various other examples here.
Renumber Shell Parameters
If you want to insert another parameter before $2
, you have to renumber $2
- $8
respectively to $3
- $9
. The same applies to Perl regexp match variables. This matches and replaces them, including optional braces. Apply in your editor to the corresponding region:
echo 'random Shell stuff with $1 - $2 - x${3}yz ' |
pl -p 's/\$\{?\K([2-8])\b/$1 + 1/eg '
random Shell stuff with $1 - $3 - x${4}yz
System Errors
Assign Unix error numbers to Perl's magic variable $!
, which in string context gives the corresponding message. Instead of explicit individual numbers, you could fill @A
programmatically with -A 1..133
(the biggest errno on my system):
pl -o 'form "%3d %1:s", $! = $_ ' 1 14 64 111
pl -o 'f "%3d %1:s", $! = $_ ' 1 14 64 111
1 Operation not permitted 14 Bad address 64 Machine is not on the network 111 Connection refused
Find Palindromes
Lottery: a tax on people who are bad at math. 😂
Generate a Random UUIDThis gives a hex number with the characteristic pattern of dashes. The hex format takes only the integral parts of the random numbers. If you need to further process the UUID, you can retrieve it instead of echoing, by giving a scalar context, e.g., $x = form …
:
pl '$x = "%04x";
form "$x$x-$x-$x-$x-$x$x$x", map rand 0x10000, 0..7 '
pl '$x = "%04x";
f "$x$x-$x-$x-$x-$x$x$x", map rand 0x10000, 0..7 '
9ed6347a-f52d-74eb-6f7f-609ea30ed42a
To be RFC 4122 conformant, the 4 version & 2 variant bits need to have standard values. As a different approach, -o "loops" over the one parameter in $_
. This transforms the template into a format:
pl -o '@u = map rand 0x10000, 0..7;
($u[3] /= 16) |= 0x4000;
($u[4] /= 4) |= 0x8000;
form s/x/%04x/gr, @u ' xx-x-x-x-xxx
pl -o '@u = map rand 0x10000, 0..7;
($u[3] /= 16) |= 0x4000;
($u[4] /= 4) |= 0x8000;
f s/x/%04x/gr, @u ' xx-x-x-x-xxx
be9957bb-1c70-46c1-8530-db3f7fb17a93
Why should you trust atoms? They make up everything. 😂
Generate a Random PasswordIf you can't convince 'em, confuse 'em! 😉
Just another pl hacker,Just another Perl hacker, adapted. This obfuscated mock turtle soup JAPH is left for you to figure out. You may wonder y "y", y things start or end in "y":
pl -ploiy y' ya-zy You, Turtleneck phrase Jar. Yoda? Yes! 'y yhvjfumcjslifrkfsuoplie
Just another pl hacker,
If canned goods expire, are they canned bads? 🙃
CANNED COMMANDSIt's nice to have so many examples, but copying some of the more frequently used ones in time of need would be cumbersome. Luckily there are better ways.
Let's explore two ways to make them more accessible from the Shell. You can put these in one of your Shell startup files like .bashrc to have them permanently available. The first, more compact but also limited, way is with aliases. The second, more flexible and powerful, way is with functions. However, depending on how flexible you want them to be, they require more or less Shell programming know how.
That smart pig's alias is Mr. Cunning Ham. 🐽
Shell AliasesRight in the spirit of one-liners are Shell aliases. These have drawbacks:
You will usually need to quote them. If you pass in Perl code containing dollar signs, you should single quote them to protect them from the Shell when running and yet again to protect them from the Shell when defining the alias.
Their value literally replaces that one word at the beginning of a command. You can add arguments only at the end.
Your IDE might not give you syntax highlighting for the value.
Either you can alias just initial options you use frequently. In that case you'd give some Perl code when invoking the alias, e.g., here to process the output of find:
alias pl0='pl -nl0 '
find -type f -print0 |
pl0 '... '
Or you alias a full command, in which case the Shell passes only the run-time arguments in @A
. Since there are no dollars it doesn't matter which quote type you embed within the other:
alias hosts="pl 'hosts @ARGV' "
alias h="pl 'h @A' "
Here's an unnecessarily complex example where both the inner and outer strings need single quoting. We clumsily achieve that by leaving the outer single quoted string, appending a backslashed single quote and reentering the single quote again. Possible but a function would have been more readable here:
alias whereis='pl '\''$p = $ENV{PATH} =~ s!/*:!,!gr;
@x = grep { -f && -x } map <{$p}/$_>, @ARGV;
exec ls => -fl => @x if @x '\'
alias whereis='pl '\''$p = $ENV{PATH} =~ s!/*:!,!gr;
@x = grep { -f && -x } map <{$p}/$_>, @A;
exec ls => -fl => @x if @x '\'
Here are a few of the key-based diff examples. The 3rd name is unfortunate, as CSV commonly means "comma separated values," so "c" for "colon" was taken. That name might not work in all Shells, in which case you could use diffCsv
:
alias diffcsv="pl -nB 'echo for @ARGV' 'k if s/(.+?),//' "
alias difftsv="pl -nB 'echo for @ARGV' 'k if s/(.+?)\t//' "
alias diff:sv="pl -nB 'echo for @ARGV' 'k if s/^([^#].*?)://' "
alias diffelf='pl -OB "echo for @ARGV" '\''p { k $2 if s/^\t((.+)\.so.* => .*) \(\w+\)/$1/ } ldd => $ARGV '\'
alias diffcsv="pl -nB 'e for @A' 'k if s/(.+?),//' "
alias difftsv="pl -nB 'e for @A' 'k if s/(.+?)\t//' "
alias diff:sv="pl -nB 'e for @A' 'k if s/^([^#].*?)://' "
alias diffelf='pl -OB "e for @A" '\''p { k $2 if s/^\t((.+)\.so.* => .*) \(\w+\)/$1/ } ldd => $A '\'
We can import some of the pl functionality directly to the Shell. My favourite is the smart e
, only by short name to avoid collision. Shell meta-characters like *
need quoting. I chose to treat multiple arguments as though they were one, therefore needing a comma if I have more than one expression. Since the expression needs eval
I can even have multiple statements, the last one being the list that e
sees:
alias e="pl 'e eval qq{@A}; die \$@ if \$@' "
e 10 / 3, '7 * 3 '
e '$x = 5; 10 / $x, $x * 2 '
3.33333333333333 21 2 10
Template has a prototype, for the 1st argument to default to $_
. This is how to still pass all alias arguments as a list:
alias template="pl '&template( @ARGV )' "
template "Don't {{verb }} the {{noun }}, make the {{noun }} {{verb }}! " verb count noun days
alias t="pl '&t( @A )' "
t "Don't {{verb }} the {{noun }}, make the {{noun }} {{verb }}! " verb count noun days
Don't count the days, make the days count!
On the Shell, you may be more interested in reading from a file, whose name shall get passed backslashed as a ref (multiply, because of "
):
alias Template="pl -B '\$file = shift' '&Template( \\\$file, @ARGV )' "
Template filename key value ...
alias T="pl -B '\$file = shift' '&T( \\\$file, @A )' "
T filename key value ...
I can't function without my glasses. Especially when they're empty! 🤓
Shell FunctionsFunctions (here using the classical syntax understood by various Shells) directly contain normal Shell code. They avoid the double quoting problem of aliases. In the simplest case, you just wrap any pl command like this, with a special sigil meaning pass the function's parameters to pl here:
somename() {
pl ... "$@ "
}
You can jazz up some more key-based diff examples as functions. That way we can have options, like -d for "eliminate date" or for zip -x for "extract hex part of filename."
diffzip() {
local prg='if s/ [0-9a-f]{8}\K (.+)// '
if [ "x$1 " = x-x ]; then # randomized hex web resource
shift
prg='$n if s/.{16} ([0-9a-f]{8}) (.+?)(?:\.([0-9a-f]{20})(\..[a-z]+))?$/if( $3 ) { $n = "$2.\$x$4"; "$1 \$x=$3" } else { $n = $2; $1 }/e '
elif [ "x$1 " = x-d ]; then # dateless
shift
prg='$2 if s/.{16} ([0-9a-f]{8}) (.+)/$1/ '
fi
pl -oB 'echo for @ARGV ' 'piped { keydiff '"$prg "' } "unzip", "-vqq", $_ ' "$@ "
}
difftar() {
local prg='s/^\S+ \K(.+?) +(\d+) (.{16}) (.+)/sprintf "%-20s %10d %s", $1, $2, $3/e; keydiff $4 '
if [ "x$1 " = x-d ]; then # dateless
shift
prg='s/^\S+ \K(.+?) +(\d+) .{16} (.+)/sprintf "%-20s %10d", $1, $2/e; keydiff $3 '
fi
pl -oB 'echo for @ARGV ' 'piped { '"$prg "' } "tar", "-tvf", $_ ' "$@ "
}
diffzip() {
local prg='if s/ [0-9a-f]{8}\K (.+)// '
if [ "x$1 " = x-x ]; then # randomized hex web resource
shift
prg='$n if s/.{16} ([0-9a-f]{8}) (.+?)(?:\.([0-9a-f]{20})(\..[a-z]+))?$/if( $3 ) { $n = "$2.\$x$4"; "$1 \$x=$3" } else { $n = $2; $1 }/e '
elif [ "x$1 " = x-d ]; then # dateless
shift
prg='$2 if s/.{16} ([0-9a-f]{8}) (.+)/$1/ '
fi
pl -oB 'e for @A ' 'p { k '"$prg "' } "unzip", "-vqq", $_ ' "$@ "
}
difftar() {
local prg='s/^\S+ \K(.+?) +(\d+) (.{16}) (.+)/sprintf "%-20s %10d %s", $1, $2, $3/e; k $4 '
if [ "x$1 " = x-d ]; then # dateless
shift
prg='s/^\S+ \K(.+?) +(\d+) .{16} (.+)/sprintf "%-20s %10d", $1, $2/e; k $3 '
fi
pl -oB 'e for @A ' 'p { '"$prg "' } "tar", "-tvf", $_ ' "$@ "
}
COMPATIBILITY
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 likeexit
don't
goto LINE
, butnext LINE
is fineusing
pop
, etc. to implicitly modify@A
works in -B begin code but not in your main program (which gets compiled to a function)RGV shenanigans with unbalanced braces won't work
Work Is Never Done On Windows Systems 😉
Windows NotesDo yourself a favour and get a real Shell, e.g., from Cygwin, git, MinGW, MSYS, or WSL! If you can't avoid command.com 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.