First, some context.
There is an old, but very good
vi/ex tutorial series by Walter Alan Zintz. Recently, I stumbled upon a modernized version of it, adapted for Vim usage by Barry Arthur and Israel Chauca:
https://dahu.github.io/vim_waz_ere
It's still incomplete, but already useful. Among other topics, they expanded the chapter on the substitute command and even provided some Vim-specific exercises not found in the original series.
One of those exercises deals with the following dataset:
Code:
@items = [
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15]
]
There are two questions:
1. What command would remove the 3rd column of numbers?
Unlike sed, ed, edbrowse, vile and elvis, the substitute command in Vim doesn't have the number flag that allows you to specify the Nth match.
So an obvious sed/ed solution s/\s*\w*,//3 won't work in Vim. What they suggest instead is setting the start of the match, an equivalent to lookbehind:
Code:
s/\v/(.{-},){2}\zs.{-},//
A rather inelegant solution compared to sed/ed.
There's a bonus question, too:
2. What command would double the numbers in the third column instead of removing them?
As a solution they suggest using substitute with an expression, a powerful, but somewhat esoteric Vim feature:
Code:
s/\v%(\d+,\s*){2}\zs\d+/\=submatch(0)*2
That got me thinking whether a transformation like this was possible with sed, and what it would look like. Awk is certainly better suited for this kind of task, but that's beyond the point.
My first idea was to use substitute with the
e flag, like this:
Code:
sed -E '
/^\s*\[/{
h
s/^([^,]+,){2}([^,]+).*/expr \2 + \2/e
G
s/^(.+)\n(([^,]+,){2}\s*)[^,]+/\2\1/
}'
This is probably as practical as it goes with sed.
Of course, there is plenty of information both in
the GNU sed manual and at
http://sed.sf.net on how to do arithmetic in sed proper, but it's anything other than practical. Nevertheless, after some thought (and using those sources as reference) I came up with this:
Code:
#!/bin/sed -Ef
# Double a natural number
# Carry is indicated by upper case
y/0123456789/abcdeFGHIJ/
s/^[F-J]/A&/
s/[a-e][F-J]/\u&/g
s/[F-J]([a-e]|$)/\l&/gI
y/abcdefghijABCDEFGHIJ/02468024681357913579/
For an example like above, where all numbers are less than 55, this will do as well:
Code:
#!/bin/sed -Ef
# Quick'n'dirty doubling of a number
# Only numbers up to 54 are allowed
/^([0-4]?[0-9]|5[0-4])$/!{
i\
Number out of range. Acceptable are numbers 0 - 54.\
Invalid input occurred at this line:
=
q
}
s/[5-9]/@&/
y/123456789/246802468/
/@/{
s/$/_8967452301/
s/(.?)@(.*)_.*\1(.).*/\3\2/
}
The latter script could be modified to work with numbers of any length, but that would involve looping, making it inefficient compared to the upper case technique:
Code:
#!/bin/sed -Ef
# Double a natural number
# @ indicates carry
s/[5-9]/@&/g
y/123456789/246802468/
/@/s/$/_8967452301/
:loop
/@/{
s/(.?)@(.*_.*\1(.).*)/\3\2/
b loop
}
/_/s/_.*//
What do you think?