NAME
pl - Swiss Army Knife of Perl One-Liners
SYNOPSIS
Just one small script extends perl -E
with many bells & whistles: Various one-letter commands & magic variables (with meaningful aliases too) take Perl programming to the command line. List::Util is fully imported. Unless you pass a program on the command line, starts a simple Perl Shell.
DESCRIPTION
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
.
-b doesn't do a BEGIN
block. Rather it is in the same scope as your main PERLCODE. So you can use it to initialise my
variables. Whereas, if you define a my variable in a -n, -p, -o or -O loop, it's a new variable each time. This echoes "a c" because -e does do an END block, which is a closure of the first $inner
variable:
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
diff | d { ... }
Multifile diff on unique key fields. If you have Algorithm::Diff with color highlighting.
Sorting
Hashes are sorted numerically at the end, if unambiguously possible. That is if either all numbers are unsigned hex (including undescores) or all numbers are valid Perl literals or all numbers are decimal as Perl parses them from strings (including leading zeroes). Otherwise, or if several keys have the same numeric value (e.g. 8, +8, 8e0, 0b100, 0B1_00, 010, 0x8), sorting is textual.
Detecting hex numbers sorts wrongly if you only have words like "babe", "bad" & "be". If you still do other numbers including 0xabc, you can turn it off by:
$sort_hex = 0;
$H = 0;
If you want strictly text sorting only:
$sort_txt = 1;
$H = 1;
EXAMPLES
Looking at Perl
- Content of a Package
-
Pl's
echo
ore
can print any item. Packages are funny hashes, with two colons at the end. Backslashing the variable passes it as a unit toData::Dumper
. Otherwise all elements would come out just separated by spaces:pl 'echo \%List::Util::' pl 'e \%List::Util::'
- Library Loading
-
Where does perl load from, and what exactly has it loaded?
pl 'echo \@INC, \%INC' pl 'e \@INC, \%INC'
Same, for a different Perl version, e.g. if you have perl5.20.0 in your path:
pl -V5.20.0 'echo \@INC, \%INC' pl -V5.20.0 'e \@INC, \%INC'
- Configuration
-
You get
%Config::Config
loaded on demand and returned byconfig
orc
:pl 'echo config' pl 'e c'
It returns a hash reference, from which you can lookup an entry:
pl 'echo config->{sitelib}' pl 'e c->{sitelib}'
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 qr/random/, "stream"'
File statistics
- Count files per suffix
-
Find and pl both use the 0 option to allow funny filenames, including newlines. Sum up encountered suffixes in sort-value-numerically-at-end hash
%number
or%n
:find -print0 | pl -0ln '++$number{/(\.[^\/.]+)$/ ? $1 : "none"}' find -print0 | pl -0ln '++$n{/(\.[^\/.]+)$/ ? $1 : "none"}'
- Count files per directory per suffix
-
Match to last / & after a dot following something, i.e. not just a dot-file. "" is the suffix for suffixless files. Stores in sort-by-key-and-stringify-at-end
%string
or%s
. So count in a nested hash of directory & suffix:find -type f -print0 | pl -0ln '/^(.+)\/.+?(?:\.([^.]*))?$/; ++$string{$1}{$2}' find -type f -print0 | pl -0ln '/^(.+)\/.+?(?:\.([^.]*))?$/; ++$s{$1}{$2}'
This is the same, but groups by suffix and counts per directory:
find -type f -print0 | pl -0ln '/^(.+)\/.+?(?:\.([^.]*))?$/; ++$string{$2}{$1}' find -type f -print0 | pl -0ln '/^(.+)\/.+?(?:\.([^.]*))?$/; ++$s{$2}{$1}'
This is similar, but stores in sort-by-number-at-end
%n
. Since this matches suffixes optionally, a lone dot indicates no suffix. The downside is that it is neither sorted by directory, nor by suffix:find -type f -print0 | pl -0ln '/^(.+)\/.+?(?:\.([^.]*))?$/; ++$n{"$1 .$2"}'
This avoids the lone dot:
find -type f -print0 | pl -0ln '/^(.+)\/.+?(?:\.([^.]*))?$/; ++$n{length($2) ? "$1 .$2" : "$1 none"}'
- Sum up file-sizes per suffix.
-
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 -name '*.*' -type f -printf "%s.%f\0" | pl -0lanF\\. '$n{".$F[-1]"} += $F[0]'
This is similar, but also deals with suffixless files:
find -type f -printf "%s.%f\0" | pl -0lanF\\. '$n{@F == 2 ? "none" : ".$F[-1]"} += $F[0]'
- Count files per date
-
Incredibly, 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%number
or%n
:find -printf '%TY-%Tm-%Td\n' | pl -ln '++$number{$_}' find -printf '%TY-%Tm-%Td\n' | pl -ln '++$n{$_}'
- Count files per date with rollup
-
todo Rollup means, additionally to the previous case 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.
find -printf '%TY-%Tm-%Td\n' | pl -ln '++$string{$_}; ++$string{$_} while s/[0-9]+(?=[-_]*$)/"_" x length $&/e' find -printf '%TY-%Tm-%Td\n' | pl -ln '++$s{$_}; ++$s{$_} while s/[0-9]+(?=[-_]*$)/"_" x length $&/e'
Diff several files by a unique key
The function diff
or d
fills %d
keyed by what you return and the arg counter $I
. At the end only the rows differing between files are shown. If you have Algorithm::Diff
the exact difference gets colour-highlighted.
- Diff several csv, tsv or passwd files by 1st field
-
This assumes no comma in key field and no newline in any field. Else you need a csv-parser package:
pl -anF, 'diff { $FIELD[0] }' *.csv pl -anF, 'd { $F[0] }' *.csv
This is similar, but removes the key from the stored value, so it doesn't get repeated for each file:
pl -n 'diff { s/(.+?),//; $1 }' *.csv pl -n 'd { s/(.+?),//; $1 }' *.csv
A variant of csv is tsv, with tab as separator. Tab is
\t
, which must be escaped from the Shell as\\t
:pl -anF\\t 'diff { $FIELD[0] }' *.tsv pl -anF\\t 'd { $F[0] }' *.tsv pl -n 'diff { s/(.+?)\t//; $1 }' *.tsv pl -n 'd { s/(.+?)\t//; $1 }' *.tsv
The same, with a colon as separator, if you want to compare passwd files from several hosts:
pl -anF: 'diff { $FIELD[0] }' /etc/passwd passwd* pl -anF: 'd { $F[0] }' /etc/passwd passwd* pl -n 'diff { s/(.+?)://; $1 }' /etc/passwd passwd* pl -n 'd { s/(.+?)://; $1 }' /etc/passwd passwd*
- Diff several zip archives by member name
-
This uses the same mechanism as the csv example. Addidionally it reads the output of
unzip -vql
for each archive through thepipe
orp
block. That has a fixed format, except for tiny members, which can report -200%, screwing the column by one:pl -o 'piped { diff { s/^.{56,57}\K (.+)//; $1 } if / Defl:/ } "unzip", "-vql", $_' *.zip pl -o 'p { d { s/^.{56,57}\K (.+)//; $1 } if / Defl:/ } "unzip", "-vql", $_' *.zip
If you do a clean build of java, many class files will have the identical crc, but still differ by date. This excludes the date:
pl -o 'piped { diff { s/^.{31,32}\K.{16} ([\da-f]{8}) (.+)/$1/; $2 } if / Defl:/ } "unzip", "-vql", $_' *.jar pl -o 'p { d { s/^.{31,32}\K.{16} ([\da-f]{8}) (.+)/$1/; $2 } if / Defl:/ } "unzip", "-vql", $_' *.jar
- Diff several tarballs by member name
-
This is like the zip example. But tar gives no checksum, so this is not very reliable. Each time a wider file size was seen columns shift right. Reformat the columns, so this doesn't show up as a differenceq:
pl -o 'piped { diff { s/^\S+ \K(.+?) +(\d+) (.{16}) (.+)/sprintf "%-20s %10d %s", $1, $2, $3/e; $4 }} "tar", "-tvf", $_' *.tar *.tgz *.txz pl -o 'p { d { s/^\S+ \K(.+?) +(\d+) (.{16}) (.+)/sprintf "%-20s %10d %s", $1, $2, $3/e; $4 }} "tar", "-tvf", $_' *.tar *.tgz *.txz
Again without the date:
pl -o 'piped { diff { s/^\S+ \K(.+?) +(\d+) .{16} (.+)/sprintf "%-20s %10d", $1, $2/e; $3 }} "tar", "-tvf", $_' *.tar *.tgz *.txz pl -o 'p { d { s/^\S+ \K(.+?) +(\d+) .{16} (.+)/sprintf "%-20s %10d", $1, $2/e; $3 }} "tar", "-tvf", $_' *.tar *.tgz *.txz
Tables
- ANSI foreground;background colour table
-
How to generate a table, hardly a one-liner... You get numbers to fill into
"\e[FGm"
,"\e[BGm"
or"\e[FG;BGm"
to get a colour and close it with"\e[m"
. There are twice twice 8 different colors for dim & bright and for foreground & background. Hence the multiplication of escape codes and of values to fill them.This fills
@A
(alias to@ARGV
) in-b
, as though it had been given on the command line. It maps it to the 16fold number format to print the header. Then the main PERLCODE loops over it with$A
(alias to$ARGV
), thanks to-O
, to print the body. All numbers are duplicated with(N)x2
, once to go into the escape sequence, once to be displayed:pl -Ob '@A = map +($_, $_+8), 1..8; f "co: fg;bg"."%5d"x16, map $_, @A' \ 'f "%2d: \e[%dm%d; ".("\e[%dm%4d "x16)."\e[m", $A, ($A + ($A > 8 ? 81 : 29))x2, map +(($_)x2, ($_+60)x2), 40..47'
This does the same, but explicitly loops over lists
@co & @bg
:pl '@co = map +($_, $_+8), 1..8; @bg = map +(($_)x2, ($_+60)x2), 40..47; f "co: fg;bg"."%5d"x16, map $_, @co; f "%2d: \e[%dm%d; ".("\e[%dm%4d "x16)."\e[m", $_, ($_ + ($_ > 8 ? 81 : 29))x2, @bg for @co'
Miscellaneous
- Split up numbers with commas, dots or underscores
-
Loop (-o) over remaining args in
$_
. After a decimal dot, insert a comma before each 4th comma-less digit. Then do the same backwards from end or decimal dot:pl -o '1 while s/[,.]\d{3}\K(?=\d)/,/; 1 while s/\d\K(?=\d{3}(?:$|[.,]))/,/; e' 1234567 12345678 123456789 1234567890 1234.5678 3.141 3.14159265358
The same for languages with a decimal comma:
pl -o '1 while s/[,.]\d{3}\K(?=\d)/./; 1 while s/\d\K(?=\d{3}(?:$|[.,]))/./; e' 1234567 12345678 12345678 1234567890 1234,5678 3,141 3,141592653589
The same for Perl style output:
pl -o '1 while s/[._]\d{3}\K(?=\d)/_/; 1 while s/\d\K(?=\d{3}(?:$|[._]))/_/; e' 1234567 12345678 123456789 1234567890 1234.5678 3.141 3.14159265358
- DNS lookup
-
# todo sub h, IPv6 /:/? # sort by raw ip 0(127) 2(169.254) 4(10) 4(172.16 - 172.31) 4(192.168) 6other # 1(??) 3(fe80::/10) 5(fd) 7other # sorted by type (localhost, link local, private, public), version (v4, v6) and IP
This one is beyond words. It deals with the nerdy
gethost...
and outputs as a hosts file. You tack on any number of IP-addresses or hostnames:pl 'h qw(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 be sorted, call
h
for individual addresses:pl 'h for qw(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