NAME
pl-examples - Perl One-Liner Examples
EXAMPLES
To steal ideas from one person is plagiarism. To steal from many is research. ;-)
Here'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 Christiansen, Peteris Krumins, CatOnMat, joyrexus, Richard Socher, eduonix, Geoff Broadwell (Windows syntax), Oracle, PerlMonks, perloneliner, Sundeep Agarwal: part 1, part 2 & cookbook) or videos (Walt Mankowski, Techalicious, David Oswald). This is no attempt to appropriate ownership, just to show how things are even easier and more concise with pl.
Dealing with Files
- Heads ...
-
People say the back of my head looks really nice -- but I don't see it. :-)
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*
- ... or Tails?
-
What has a head, a tail, but no legs? A penny. :-)
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(cho)
it for you, capitalized to not add another newline (yes, Perl is case sensitive):pl -e Echo '' 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*
- Print Only 1st Occurrence of Each Line
-
Poets create worlds through a minimal of words. -- Kim Hilliker |/|
This counts repetitions of lines in a hash. Print only when the expression is true (-P), i.e., the count was 0:
pl -P '!$a{$_}++' file*
If you want this per file, you must empty the hash in the end-of-file -e code:
pl -Pe '%a = ()' '!$a{$_}++' file*
- Remove Empty Lines
-
Or, actually the opposite, printing back to the same files (-Pi) all lines containing non-whitespace
\S
:pl -Pi '/\S/' file*
- 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. Thelast
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
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;
(orclose A;
) before therename
.For Java it's a bit more complicated because the full name is split into a
package
followed by aclass
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
- 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, replaceunlink
withe(cho)
:pl -o 'unlink if $ARGIND < $#ARGV' 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(RGV)
, 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
File Statistics
42% of statistics are made up! :-)
- Count 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(UMBER)
:find -type f -print0 | pl -0ln 'm@[^/.](\.[^/.]*)?$@; ++$NUMBER{$1 // "none"}'
- Count Files per Directory per Suffix
-
There are three types of people: those who can count and those who can't. (-:
Match 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(ESULT)
. 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@^(?:\./)?(.+)/.*?[^/.](\.[^/.]*)?$@; ++$RESULT{$1}{$2 // "none"}'
This is the same pivoted, grouping by suffix and counting per directory:
find -type f -print0 | pl -0ln 'm@^(?:\./)?(.+)/.*?[^/.](\.[^/.]*)?$@; ++$RESULT{$2 // "none"}{$1}'
This is similar but stores in sort-by-number-at-end
%N(UMBER)
. Therefore, it sorts by frequency, only secondarily by directory & suffix (pl sorts stably):find -type f -print0 | pl -0ln 'm@^(?:\./)?(.+)/.*?[^/.](\.[^/.]*)?$@; ++$NUMBER{"$1 " . ($2 // "none")}'
The function
N(umber)
can trim%N(UMBER)
, 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 'Number 80' 'm@^(?:\./)?(.+)/.*?[^/.](\.[^/.]*)?$@; ++$NUMBER{"$1 " . ($2 // "none")}'
- 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(UMBER)
, which gets sorted numerically at the end:find -type f -printf "%s.%f\0" | pl -0lF\\. '$NUMBER{@FIELD > 2 ? ".$FIELD[-1]" : "none"} += $FIELD[0]'
- Count Files per Date
-
I feel more like I do now than I did a while ago. (-:
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%N(UMBER)
:find -type f -printf "%TY-%Tm-%Td\n" | pl -ln '++$NUMBER{$_}'
- Count Files per Date with Rollup
-
Learn sign language! It's very handy. :-)
Rollup 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(ESULT)
:find -type f -printf "%TY-%Tm-%Td\n" | pl -ln 'do { ++$RESULT{$_} } while s/[0-9]+(?=[-_]*$)/"_" x length $&/e'
Diff Several Inputs by a Unique Key
Always remember you're unique, just like everyone else. :-)
The function k(eydiff)
stores the 2nd arg or chomped $_
in %K(EYDIFF)
keyed by 1st arg or $1
and the arg counter $ARGIND
(or $I
). Its sibling K(eydiff)
does the same using 1st arg or 0 as an index into @F(IELD)
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(eydiff)
by default takes the 1st field as your unique key:pl -F, Keydiff *.csv
This is similar but removes the key from the stored value, so it doesn't get repeated for each file. Note how
k(eydiff)
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
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 -n 'keydiff if s/(.+?)\t//' *.tsv
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*
- Diff Several zip Archives by Member Name
-
Growing old you forget to zip up your fly. Later you forget to unzip your fly. 8-)
This uses the same mechanism as the CSV example. Additionally, through the
p(iped)
block, it reads the output ofunzip -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
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
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
- Diff Several Tarballs by Member Name
-
Actually I'm very different. But I rarely find time for it. -- von Horváth :-)
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
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
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
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
- 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(RGV)
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
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
Looking at Perl
A pig looking at an electric socket: "Oh no, who put you into that wall?" :)
- VERSION 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(uote)
& double$Q(uote)
:pl -P1 's/(?:\bpackage\s+[\w:]+\s+|.+\bVERSION\s*=\s*)[v$Quote$quote]{0,2}([0-9.]+).+/$1/' pl
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
- 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 inperl -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(ESULT)
:pl -ln 'last if /^__(?:DATA|END)__/; s/(?:^|\s+)#.*//s; s/^\s+//; $RESULT += length' *.pm
If you want the count per file, instead of
$R(ESULT)
use either sort-lexically$RESULT{$ARGV}
(or$R{$A}
) or sort-numerically$NUMBER{$ARGV}
(or$N{$A}
). - Content of a Package
-
Pl's
e(cho)
can print any item. Packages are funny hashes, with two colons at the end. Backslashing the variable passes it as a unit toData::Dumper
, which gets loaded on demand in this case. Otherwise, all elements would come out just separated by spaces:pl 'echo \%List::Util::'
- Library Loading
-
Where does perl load from, and what exactly has it loaded?
pl 'echo \@INC, \%INC'
Same, for a different Perl version, e.g., if you have perl5.28.1 in your path. This time copy
%INC
before callinge(cho)
, which for an array or hashref needs to loadData::Dumper
on the fly and thus its dependencies:pl -V5.28.1 '$orig = {%INC}; echo \@INC, $orig, \%INC'
- Configuration
-
You get
%Config::Config
loaded on demand and returned byC(onfig)
:pl 'echo Config'
It returns a hash reference, from which you can look up an entry:
pl 'echo Config->{sitelib}'
You can also return a sub-hash, of only the keys matching any regexps you pass:
pl 'echo Config "random", qr/stream/'
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(orm)
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'
That makes a rather long table. You can get a better overview with 4 nested tables, looping over a preset
@A(RGV)
:pl -OA '0..0x3f' 'say join "\t", map Form( "0b%08b 0%1:03o %1:3d 0x%1:02x", $ARGV+$_ ), 0, 0x40, 0x80, 0xc0'
If you prefer to enumerate sideways, it's easier.
@A(RGV)
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'
- 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'
- ISO Paper Sizes
-
ISO replaced 8 standards by one. Now we have 9 standards. :-(
Since
@A(RGV)
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)'
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(RGV)
, 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'
- ANSI Background;Foreground Color Table
-
What color is a mirror? It depends whom you ask. ;-)
You 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(RGV)
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(RGV)
, 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", @$_'
- 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
Math
- Minimum and Maximum
-
The
List::Util
functionsmin
andmax
are imported for you:pl 'echo max 1..5'
If you have just several numbers on each line and want their minimums, you can autosplit (-a) to
@F(IELD)
:pl -a 'echo min @FIELD' file*
If on the same you just want the overall minimum, you can use the print-at-end variable
$R(ESULT)
, which you initialise to infinity in a -B begin program:pl -aB '$RESULT = "inf"' '$RESULT = min $RESULT, @FIELD' file*
Likewise, for overall maximum, you start with negative infinity:
pl -aB '$RESULT = "-inf"' '$RESULT = max $RESULT, @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]'
If you'd rather have names associated, assign them to hash slice in sort-numerically-at-end
%N(UMBER)
, whose key order must match the percentages. Note how<...>
without wildcard characters is likeqw(...)
:pl -A 0..200 '@NUMBER{<lower median upper 97%>} = @ARGV[map $_*@ARGV, .25, .5, .75, .97]'
- Triangular Number, Factorial, and Averages
-
80% of people consider themselves to be above average. :-)
The 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'
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'
- 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 elements0..1
:pl 'echo reduce { [map $a->[$_] + $b->[$_], 0..1] } [1, 11], [2, 12], [3, 13]'
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'
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]'
- Big Math
-
2 + 2 = 5 for extremely large values of 2. :-)
With the
bignum
andbigrat
modules you can do arbitrary precision and semi-symbolic fractional math:pl -Mbignum 'echo 123456789012345678901234567890 * 123456789012345678901234567890' pl -Mbignum 'echo 1.23456789012345678901234567890 * 1.23456789012345678901234567890' pl -Mbigrat 'echo 1/23456789012345678901234567890 * 1/23456789012345678901234567890'
- Primes
-
This calculates all primes in a given range, e.g., 2 to 99:
pl 'echo grep { $a = $_; all { $a % $_ } 2..$_/2; } 2..99'
- 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
- Collatz 3n + 1
-
What's 3n plus n? Hmm, sounds 4n to me. (-:
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 numbern
. Sinceif 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
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
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 usebignum
as of Perl 5.30, which would also allow astronomical numbers. Sadly,f(orm)
can't go beyond 64 bits, so this is also how you'd get around that. Also add direction indicators:pl -OMbignum -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
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
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
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'
- 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'
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'
- DNS Lookup
-
What do you call a sheep with no legs? A cloud. *;=;
The
h(osts)
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(RGV)
:pl 'hosts qw(perl.org 127.0.0.1 perldoc.perl.org cpan.org)' pl 'hosts @ARGV' perl.org 127.0.0.1 perldoc.perl.org cpan.org
If you don't want it to merge & sort, call
h(osts)
for individual addresses:pl 'hosts for qw(perl.org 127.0.0.1 perldoc.perl.org cpan.org)' pl -o hosts 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*
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'
- 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(RGV)
programmatically with-A 1..133
(the biggest errno on my system):pl -o 'form "%3d %1:s", $! = $_' 1 14 64 111
- Find Palindromes
-
This assumes a dictionary on your machine. It loops over the file printing each match -P. It eliminates trivia like I, mom & dad with a minimum length:
pl -Pl 'length > 3 and $_ eq reverse' /usr/share/dict/words
- Generate a Random UUID
-
Lottery: a tax on people who are bad at math. :-)
This 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'
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
- Generate a Random Password
-
Why should you trust atoms? They make up everything. :-)
Use
say
, which doesn't put spaces between its arguments. Generate twelve random characters from 33 to 126, i.e., printable ASCII characters. Note thatrand 94
will always be less than 94 andchr
will round it down:pl 'say map chr(33 + rand 94), 1..12'
- Just another pl hacker,
-
If you can't convince 'em, confuse 'em! ;-)
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