App-pl
view release on metacpan or search on metacpan
pod/examples.pod view on Meta::CPAN
> 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
=item 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 C<\\> 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 C<%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]'
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
=item Count Files per Date
I<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 C<-%Td>. Sum up encountered dates in
sort-value-numerically-at-end hash C<%N(UMBER)>:
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
=item Count Files per Date with Rollup
I<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 C<%R(ESULT)>:
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
=back
=head2 Diff Several Inputs by a Unique Key
I<Always remember you're unique, just like everyone else. :-)>
The function C<k(eydiff)> stores the 2nd arg or chomped C<$_> in C<%K(EYDIFF)>
keyed by 1st arg or C<$1> and the arg counter C<$ARGIND> (or C<$I>). Its
sibling C<K(eydiff)> does the same using 1st arg or 0 as an index into
C<@F(IELD)> for the 1st part of the key. At the end only the rows differing
between files are shown. If you write to a terminal or specify B<--color> the
difference gets color-highlighted in per-character detail with
C<Algorithm::Diff>, or in just one red blob without. There are examples for
L<how to alias|canned-commands/Shell Aliases> these as canned commands.
=over
=item Diff Several csv, tsv or passwd Files by 1st Field
pod/examples.pod view on Meta::CPAN
> n/a
> H Hydrogen 1:alkali metal 1
> 4
> Be Beryllium 2:alkaline earth metal 9.012
> Pl Perlium 2:pl basis 5.32.0
> n/a
> 8
> O Oxygen 16:O & chalcogen 16
> O Oxygen 16:O & chalcogen 16
> O Oxygen 16:O and chalcogen 16
> 41
> Nb Niobium 5:no name 92.906
> n/a
> Nb Columbium 5:no name 93
> 42
> Ve Veritasium 6:an element of truth i
> n/a
> n/a
> 74
> W Tungsten 6:transition metal 183.84
> W Wolfram 6:transition metal 183.8
> n/a
> 80
> Hg Mercury 12:no name 200.592
> Hg Quicksilver 12:no name 200.6
> Hg Hydrargyrum 12:no name 201
> 110
> n/a
> Ds 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*
=item Diff Several zip Archives by Member Name
I<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. Addidionally, through the
C<p(iped)> block, it reads the output of C<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
> AUTHORS
> 48831 Defl:N 22282 54% 2019-05-11 11:50 cc2a1286
> 48864 Defl:N 22297 54% 2019-10-24 23:27 b793bcc5
> 48927 Defl:N 22338 54% 2020-02-29 12:55 8cecd35e
> 48927 Defl:N 22338 54% 2020-02-11 14:31 8cecd35e
> Artistic
> 6321 Defl:N 2400 62% 2019-05-11 11:50 fa53ec29
> 6321 Defl:N 2400 62% 2019-10-24 22:17 fa53ec29
> 6321 Defl:N 2400 62% 2019-10-24 22:17 fa53ec29
> 6321 Defl:N 2400 62% 2019-10-21 13:20 fa53ec29
> Changes
> 3168 Defl:N 1273 60% 2018-06-27 13:17 66a9af3e
> 3111 Defl:N 1246 60% 2019-10-27 10:52 f826c349
> 3111 Defl:N 1246 60% 2019-10-27 10:52 f826c349
> 3111 Defl:N 1246 60% 2019-10-28 09:05 f826c349
> ...
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. There are examples for L<how to
combine|canned-commands/Shell Functions> 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
> AUTHORS
> 48831 Defl:N 22282 54% cc2a1286
> 48864 Defl:N 22297 54% b793bcc5
> 48927 Defl:N 22338 54% 8cecd35e
> 48927 Defl:N 22338 54% 8cecd35e
> Changes
> 3168 Defl:N 1273 60% 66a9af3e
> 3111 Defl:N 1246 60% f826c349
> 3111 Defl:N 1246 60% f826c349
> 3111 Defl:N 1246 60% f826c349
> Configure
> 587687 Defl:N 148890 75% 144c0f25
> 587687 Defl:N 148890 75% 144c0f25
> 587825 Defl:N 148954 75% 6761d877
> 587825 Defl:N 148954 75% 6761d877
> INSTALL
> 108059 Defl:N 37351 65% 45af5545
> 108085 Defl:N 37371 65% e5f2f22b
> 107649 Defl:N 37211 65% 9db83c1e
> 107649 Defl:N 37211 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
=item Diff Several Tarballs by Member Name
I<Actually I'm very different. But I rarely find time for it. --B< >von HorvE<0xe1>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 C<d>.
Each time a wider owner/group or file size was seen, columns shift right. So
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 20948 2018-06-27 13:17
> -r--r--r-- pfeiffer/pfeiffer 20948 2019-10-24 22:17
> -r--r--r-- pfeiffer/pfeiffer 21287 2020-02-29 12:55
> -r--r--r-- pfeiffer/pfeiffer 21287 2020-02-12 18:41
> cpan/Compress-Raw-Bzip2/bzip2-src/huffman.c
> -r--r--r-- pfeiffer/pfeiffer 6991 2018-06-27 13:17
> -r--r--r-- pfeiffer/pfeiffer 6991 2019-10-24 22:17
> -r--r--r-- pfeiffer/pfeiffer 6986 2020-02-29 12:55
> -r--r--r-- pfeiffer/pfeiffer 6986 2020-02-12 18:41
> cpan/Compress-Raw-Bzip2/bzip2-src/randtable.c
> -r--r--r-- pfeiffer/pfeiffer 3866 2018-06-27 13:17
> -r--r--r-- pfeiffer/pfeiffer 3866 2019-10-24 22:17
> -r--r--r-- pfeiffer/pfeiffer 3861 2020-02-29 12:55
> -r--r--r-- pfeiffer/pfeiffer 3861 2020-02-12 18:41
> cpan/Compress-Raw-Bzip2/fallback/constants.h
> -r--r--r-- pfeiffer/pfeiffer 7238 2018-06-27 13:17
> -r--r--r-- pfeiffer/pfeiffer 7238 2019-10-24 22:17
> -r--r--r-- pfeiffer/pfeiffer 7238 2019-10-24 22:17
> -r--r--r-- pfeiffer/pfeiffer 7238 2019-10-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 20948
> -r--r--r-- pfeiffer/pfeiffer 20948
> -r--r--r-- pfeiffer/pfeiffer 21287
> -r--r--r-- pfeiffer/pfeiffer 21287
> cpan/Compress-Raw-Bzip2/bzip2-src/huffman.c
> -r--r--r-- pfeiffer/pfeiffer 6991
> -r--r--r-- pfeiffer/pfeiffer 6991
> -r--r--r-- pfeiffer/pfeiffer 6986
> -r--r--r-- pfeiffer/pfeiffer 6986
> cpan/Compress-Raw-Bzip2/bzip2-src/randtable.c
> -r--r--r-- pfeiffer/pfeiffer 3866
> -r--r--r-- pfeiffer/pfeiffer 3866
> -r--r--r-- pfeiffer/pfeiffer 3861
> -r--r--r-- pfeiffer/pfeiffer 3861
> cpan/Compress-Raw-Bzip2/lib/Compress/Raw/Bzip2.pm
> -r--r--r-- pfeiffer/pfeiffer 10783
> -r--r--r-- pfeiffer/pfeiffer 10783
> -r--r--r-- pfeiffer/pfeiffer 11009
> -r--r--r-- pfeiffer/pfeiffer 11009
> ...
Tarballs from the internet have a top directory of F<name-version/>, which
across versions would make every member have a different key. So exclude the
1st path element from the key by matching C<[^/]+/> 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-- sawyer/sawyer 208 2018-06-27 13:17
> -r--r--r-- Steve/None 208 2019-10-24 22:17
> -r--r--r-- Steve/None 208 2019-10-24 22:17
> -r--r--r-- Steve/None 208 2019-10-21 13:20
> .lgtm.yml
> -r--r--r-- sawyer/sawyer 347 2019-05-11 11:50
> -r--r--r-- Steve/None 347 2019-10-24 22:17
> -r--r--r-- Steve/None 347 2019-10-24 22:17
> -r--r--r-- Steve/None 347 2019-10-21 13:20
> .metaconf-exclusions.txt
> -r--r--r-- sawyer/sawyer 1317 2019-05-11 11:50
> -r--r--r-- Steve/None 1317 2019-10-24 22:17
> -r--r--r-- Steve/None 1317 2019-10-24 22:17
> -r--r--r-- Steve/None 1317 2019-10-21 13:20
> .travis.yml
> -r--r--r-- sawyer/sawyer 2203 2019-05-11 11:50
> -r--r--r-- Steve/None 2203 2019-10-24 23:27
> -r--r--r-- Steve/None 2203 2019-10-24 23:27
> -r--r--r-- Steve/None 2203 2019-10-21 13:20
> AUTHORS
> -r--r--r-- sawyer/sawyer 48831 2019-05-11 11:50
> -r--r--r-- Steve/None 48864 2019-10-24 23:27
> -r--r--r-- Steve/None 48927 2020-02-29 12:55
> -r--r--r-- Steve/None 48927 2020-02-11 14:31
> Artistic
> -r--r--r-- sawyer/sawyer 6321 2019-05-11 11:50
> -r--r--r-- Steve/None 6321 2019-10-24 22:17
> -r--r--r-- Steve/None 6321 2019-10-24 22:17
> -r--r--r-- Steve/None 6321 2019-10-21 13:20
> Changes
> -r--r--r-- sawyer/sawyer 3168 2018-06-27 13:17
> -r--r--r-- Steve/None 3111 2019-10-27 10:52
> -r--r--r-- Steve/None 3111 2019-10-27 10:52
> -r--r--r-- Steve/None 3111 2019-10-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-- 48831
> -r--r--r-- 48864
> -r--r--r-- 48927
> -r--r--r-- 48927
> Changes
> -r--r--r-- 3168
> -r--r--r-- 3111
> -r--r--r-- 3111
> -r--r--r-- 3111
> Configure
> -r-xr-xr-x 587687
> -r-xr-xr-x 587687
> -r-xr-xr-x 587825
> -r-xr-xr-x 587825
> ...
=item 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 B<-O> and C<$A(RGV)> to avoid the previous
examples' confusion between outer C<$_> 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
pod/examples.pod view on Meta::CPAN
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 -OMbignum -B 'f $f = "%s %26s %16s %14s %12s %9s %8s", " ", @b = qw(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 L<here|https://perl1liner.sourceforge.io/Collatz/>.
=item Separate Big Numbers with Commas, ...
Loop and print with line-end (B<-opl>) over remaining args in C<$_>. If
reading from stdin or files, instead of arguments, use only B<-pl>. After a
decimal dot, insert a comma before each 4th comma-less 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
( run in 1.352 second using v1.01-cache-2.11-cpan-39bf76dae61 )