How do you apply a macro x amount of times per line where x depends on the line - vim

Say I have something like
a & 1234567890
b & 1234567890
c & 1234567890
d & 1234567890
e & 1234567890
f & 1234567890
Is there a way to use a vim macro such that I can run a macro/command x amount of times per line, where x depends on the line?
In this case, I run 2wx^ on each line x times, where x is the line number such that the result becomes
a & 234567890
b & 34567890
c & 4567890
d & 567890
e & 67890
f & 7890
Thanks in advance

If your macro is recorded in register q, then you can run:
:exec 'normal ' . line('.') . '#q'
on any line you want. Your macro will want the cursor kept on 1st column before being run.
You can also probably do it - better - in a different way, if you describe what you want to do. For example, perhaps you could skip the macro altogether and use something like this instead:
:exec 'normal ^2w' . line('.') . 'xj'
If you need a line offset (e.g. of 1), you could use:
:let nr = line('.') - 1 | execute 'normal ^2w' . nr . 'xj'

Related

how to replace the first column of a tab delimited file

690070 690070 A
690451 690451 B
690571 690571 C
690578 690578 D
690637 690637 F
How can I replace the first column values with a sequential number, starting from 1...n. So it becomes:
1 690070 A
2 690451 B
3 690571 C
4 690578 D
5 690637 F
Can this be done in Vim or some linux command?
You can use awk or vim macro.
awk is really great for such text manipulation
awk '{count++; print count " " $2 " "$3;}' data.stat > /tmp/data.stat && mv /tmp/data.stat data.stat
in Vim:
:let i=1 | g/^[^/\t]*\t/s//\= i. "\t"/ | let i=i+1
Reference
Update
For splitting the first two columns and saving into another file,
I recommend using awk as in Tomáš Šíma's answer, specifically:
awk '{print $1 "\t" $2;}' data.stat > newfilename.txt
If you want to to do everything in Vim:
Copy the current file to a new one
:w newfilename.txt
Open the newly copied file:
:o newfilename.txt
Split the first two columns of the rest of the line:
:%s/^\([^\t]*\)\t\([^\t]*\).*$/\1\t\2/g
Save your edits of course
:w newfilename.txt

Writing text blocks using nested iteration in Vim

Let's say I'd like to write a 5x5 text block, such as
aaaaa
aaaaa
aaaaa
aaaaa
aaaaa
And I want to do it using nested iteration.
In pseudocode it would look like
do five times ((do five times (type 'a')) change line)
So my first guess was to simply convert that as
5 ((5 (i a esc)) enter)
But I can't do that, because Vim doesn't support use of parentheses for specifying execution order. And simply typing
5 5 i a esc enter
will of course not work, since that will just produce a single line with 55 'a's and a newline.
So my question is: Is there a way to write text blocks using nested iteration in Vim? I know that there are other ways to write text blocks, but I want to know if this is possible, just out of curiosity.
You cannot do this directly, you need to use a register, expression, or macro:
qq5aa<Esc>a<CR><Esc>q4#q
qq - record macro
5aa<Esc> - insert 5 a's
a<CR><Esc> - insert line break
q4#q - stop recording, repeat 4 more times
I do not normally like one-liners, but this seems to work:
:for i in range(5) | for j in range(5) | execute 'normal ia' | endfor | execute "normal A\<CR>" | endfor
and this is a lot shorter:
:for i in range(5) | execute 'normal 5aa' | put='' | endfor
<esc> i a <esc> x 5p dd 5P
esc - switch to normal mode
i - switch to insert mode
a - print "a"
esc - switch to normal mode
x - deleta "a"
5p - paste a 5 times ("aaaaa")
dd - delete line "aaaaa"
5P - paste line "aaaaa" 5 times
:norm 5oaaaaa
is the simplest way I could think of to obtain a 5x5 matrix of a's but I don't think it satisfies your curiosity.
One could also do:
:norm Oaaaaa
5#:
but that's not really recursive either.
So… I don't know!

in Linux: merge two very big files

I would like to merge two files (one is space delimited and the other tab delimited) keeping only the records that are matching between the two files:
File 1: space delimited
A B C D E F G H
s e id_234 4 t 5 7 9
r d id_45 6 h 3 9 10
f w id_56 2 y 7 3 0
s f id_67 2 y 10 3 0
File 2: tab delimited
I L M N O P
s e 4 u id_67 88
d a 5 d id_33 67
g r 1 o id_45 89
I would like to match File 1 field 3 ("C") with file 2 field 5 ("O"), and merge the files like this:
File 3: tab delimited
I L M N O P A B D E F G H
s e 4 u id_67 88 s f 2 y 10 3 0
g r 1 o id_45 89 r d 6 h 3 9 10
There are entries in file 1 that don't appear in file 2, and vice versa, but I only want to keep the intersection (the common ids).
I don't really care about the order.
I would prefer not to use join because these are really big unsorted files and join requires to sort by common field before, which takes a very long time and much memory.
I have tried with awk but unsuccessfully
awk > file3 'NR == FNR {
f2[$3] = $2; next
}
$5 in f2 {
print $0, f2[$2]
}' file2 file1
Can someone please help me?
Thank you very much
Hmm.. you'll ideally be looking to avoid an n^2 solution which is what the awk based approach seems to require. For each record in file1 you have to scan file2 to see if occurs. That's where the time is going.
I'd suggest writing a python (or similar) script for this and building a map id->file position for one of the files and then querying that whilst scanning the other file. That'd get you an nlogn runtime which, to me at least, looks to be the best you could do here (using a hash for the index leaves you with the expensive problem of seeking to the file pos).
In fact, here's the Python script to do that:
f1 = file("file1.txt")
f1_index = {}
# Generate index for file1
fpos = f1.tell()
line = f1.readline()
while line:
id = line.split()[2]
f1_index[id] = fpos
fpos = f1.tell()
line = f1.readline()
# Now scan file2 and output matches
f2 = file("file2.txt")
line = f2.readline()
while line:
id = line.split()[4]
if id in f1_index:
# Found a matching line, seek to file1 pos and read
# the line back in
f1.seek(f1_index[id], 0)
line2 = f1.readline().split()
del line2[2] # <- Remove the redundant id_XX
new_line = "\t".join(line.strip().split() + line2)
print new_line
line = f2.readline()
If sorting the two files (on the columns you want to match on) is a possibility (and wouldn't break the content somehow), join is probably a better approach than trying to accomplish this with bash or awk. Since you state you don't really care about the order, then this would probably be an appropriate method.
It would look something like this:
join -1 3 -2 5 -o '2.1,2.2,2.3,2.4,2.5,2.6,1.1,1.2,1.4,1.5,1.6,1.7,1.8' <(sort -k3,3 file1) <(sort -k5,5 file2)
I wish there was a better way to tell it which columns to output, because that's a lot of typing, but that's the way it works. You could probably also leave off the -o ... stuff, and then just post-process the output with awk or something to get it into the order you want...

Vim: Replace n with n+1

How do I replace every number n that matches a certain pattern with n+1? E.g. I want to replace all numbers in a line that are in brackets with the value+1.
1 2 <3> 4 <5> 6 7 <8> <9> <10> 11 12
should become
1 2 <4> 4 <6> 6 7 <9> <10> <11> 11 12
%s/<\zs\d\+\ze>/\=(submatch(0)+1)/g
By way of explanation:
%s " replace command
"""""
< " prefix
\zs " start of the match
\d\+ " match numbers
\ze " end of the match
> " suffix
"""""
\= " replace the match part with the following expression
(
submatch(0) " the match part
+1 " add one
)
"""""
g " replace all numbers, not only the first one
Edit:
If you only want to replace in specific line, move your cursor on that line, and execute
s/<\zs\d\+\ze>/\=(submatch(0)+1)/g
or use
LINENUMs/<\zs\d\+\ze>/\=(submatch(0)+1)/g
(replace LINENUM with the actual line number, eg. 13)
In vim you can increment (decrement) the numeric digit on or after the cursor by pressing
NUMBER<ctrl-a> to add NUMBER to the digit
(NUMBER<ctrl-x> to substract NUMBER from the digit)
If only incrementing (decrementing) by one you don't need to specify NUMBER. In your case I would use a simple macro for this:
qaf<<ctrl-a>q
100<altgr-q>a
Here a brief explanation of the macro: It uses the find (f) commant to place the cursor on the opening < bracket. It is not necessary to position the cursor on the digit. When press the number on the cursor or the nearest number after the cursor will get incremented.
If you want an even shorter series of commands you can position your curser ONCE by pressing f<, increment the number with ctrl-a and then just repeatedly press ;.. The ; command repeats the last cursor movement i.e. the find command. The . command repeats the last text changing command.
Check out this link for further information or use the built in documentation: h: ctrl-a.

How to extract out lines with a pattern and put them in the end of the file

Suppose I have the following text.
a test1
b test2
a test3
b test4
what is the command to use to extract out the lines that start with the letter a and put them in the end of the file like this?
b test2
b test4
a test1
a test3
when I used :g/^a/d and p, the only last match is pasted:
b test2
b test4
a test3
You're only seeing a test3 at the end, because :d sets (not appends) the default register. Since :g executes the given command once per line that matches the pattern, only the last line is in the default register when you paste.
The canonical way to do this would be to use the :move (abbreviated as :m) command -- :g/^a/m $. For every line that matches ^a, move it just past the last line ($).
A slight modification to your initial approach would be to have :d append to a register, and then paste that register afterward.
:let #a='' " Clear register a
:g/^a/d A " For every line that matches ^a, delete it
" and append the contents to register a
:$put a " Paste the contents of register a after the last line
The last part could also be done using the normal mode command "ap.

Resources