I am trying to change a text file with data in 'document' form into unnormalized csv. The data are a list of hymn authors ('document header') and for each author a list of one or more hymns they have written ('document line'). The specific operation I am struggling with is taking each 'document header' and prepending it to the one or more 'document lines' that follow it.
Example data
For example, I want to turn this
Beskow Natanael
140 Ack saliga dag
399 Tränger i dolda djupen ner
478 Ditt verk är stort
479 Kärlek av höjden
Bexell Göran
103 Herren lever våga tro det
Béze Théodore de
283 Lovsjung nu alla länder Gud
360 Såsom hjorten ivrigt längtar
into
Beskow Natanael,140,Ack saliga dag
Beskow Natanael,399,Tränger i dolda djupen ner
Beskow Natanael,478,Ditt verk är stort
Beskow Natanael,479,Kärlek av höjden
Bexell Göran,103,Herren lever våga tro det
Béze Théodore de,283,Lovsjung nu alla länder Gud
Béze Théodore de,360,Såsom hjorten ivrigt längtar
The specific change that I am struggling with is taking the 'document header' and prepending it to each 'document line' so I am leaving the other changes (removing the header, comma between hymn number and hymn title, etc.) to one side for the moment.
Question
I could write a function to do this change, but I find that I am collecting quite a number of ad hoc functions for different editing operations, so I would prefer to learn how to do it with a global
command, or, if it is not a suitable operation to perform with global
, to understand why. How can I do this operation with global
; and, if I can't, why?
What I have tried
I have tried using the :global
command to execute different combinations of normal
commands.
1) Yank, move, put
g/^\S/normal! y$jPa,
I take this to mean
- for each line beginning with non-whitespace
- yank to end-of-line into unnamed register
- move down one line and put before from unnamed register
- append a comma
This works, but only for each first 'document line'. The result is
Beskow Natanael
Beskow Natanael, 140 Ack saliga dag
399 Tränger i dolda djupen ner
478 Ditt verk är stort
479 Kärlek av höjden
Bexell Göran
Bexell Göran, 103 Herren lever våga tro det
Béze Théodore de
Béze Théodore de, 283 Lovsjung nu alla länder Gud
360 Såsom hjorten ivrigt längtar
To repeat the put operation on each 'document line' I think I would need to know how many there are and put in a loop, and I don't see an easy way to do that without writing a custom function.
2) Yank, visually select, substitute
To avoid having to count I thought I could instead visually select the 'document lines' and insert the 'document header' with a substitute over the visual selection.
g/^\S/normal! y$v/^\S^M:s/^\s/\=@" . "," . submatch(0)
I intend this command to mean the following
- for each line beginning with non-whitespace
- yank to the end of line
- visually select until the next line beginning with non-whitespace
- substitute over visual selection
- match beginning-of-line + whitespace
- substitute contents of unnamed register + a comma + the entire match (i.e., the whitespace)
This command does not work at all.
- It does not change the buffer at all.
- It leaves me in visual mode, with the last character on the first line selected.
- Hitting
/<Up>
to look at the last search pattern shows/^\S^M:s/^\s/\=@" . "," . submatch(0)
. This tells me that the^M
does not execute the search as I had hoped, but rather everything following/
is interpreted as part of the pattern. - Checking the unnamed register after running the command, it contains the last 'document header' in the buffer. This tells me that it goes through the buffer and performs the yank operation.
If, however, I type out the normal mode commands manually they work as expected.
\t
has to be redefined):BEGIN {FS="\t";OFS=","} /^[^\t]/{s=$0;next} {print s,$2,$3}
- Since this is "OT" I just provide this comment. I think it's worth to get a feeling when it's appropriate to switch tools.awk
better and I am grateful for the example, but a) it would be a dubious investment to learn an external tool for something that is well within Vim's purview, and b) your command is actually more complex than the simplest working solution within Vim, especially if I add the 'padding' of invokingawk
from within Vim as a filter so I can continue editing the text in Vim afterwards.!G
command. - WRT learning new things; it's your choice whether you learn vim-specifics or a (POSIX) standard tool.