I'm wondering if this is possible, more for curiosity's sake, but also it might be useful to know.

I have several ascii data tables and I want to combine them all into a single file. So I combined all of the files into a single file with the file name to signify the start of the new data. What I'd like to do is search for that filename as a sort of delimiter, yank it, and put it as a column into all subsequent rows.

For example:

1 1 1 1
2 2 2 2
3 3 3 3
4 4 4 4
5 5 5 5
6 6 6 6
7 7 7 7
8 8 8 8

And turn it into:

file-1.txt 1 1 1 1
file-1.txt 2 2 2 2
file-1.txt 3 3 3 3
file-1.txt 4 4 4 4
file-2.txt 5 5 5 5
file-2.txt 6 6 6 6
file-2.txt 7 7 7 7
file-2.txt 8 8 8 8

It might be easier to do outside of vi, but I thought I might ask.

(I suppose another question along the same vein I'm not sure about is if you can put a yanked word as a column over several rows)


vim (it's ugly)

g /\S/ if getline('.') =~# '^file' | d | else | norm! PJ | endif
  • /\S/ match none blank line
  • if getline('.') =~# '^file' | d delete line starts with "file" to default register. Note that default register is line wise after deletion.
  • norm! PJ paste above current line from default register, join them with a space. J insert one space if 1st line doesn't end with ., ! or ?, other wise it insert two spaces. also check :h 'joinspaces'. As D.Ben Knoble pointed out, you can replace it with put! | join if you want to avoid normal command.

But vim is not a proper tool to do this kind of job, awk is much better.


awk '/^file/{p=$0; next} {print p, $0}' filename
  • 1
    Not bad, the join, forgot about that. Could be put | join – D. Ben Knoble May 29 '19 at 0:39
  • 1
    @D.BenKnoble Updated. It's put! in this case. – dedowsdi May 29 '19 at 0:47
  • pretty cool! Thanks. – user22456 May 29 '19 at 17:04

You had a secondary question about inserting a word as a column in a table. It's possible using Visual select in block mode and we can use this method, as you probably surmised, to solve your primary problem as shown by the following series of commands.

The first two answers use approaches that are generally better (easier to follow, less error prone) than what follows here. Consider this a bonus answer...for educational purposes.

Put the cursor on a file line (e.g. file-1.txt) to start. Note that where you see ^R that means to enter Ctrl+R. Similarly for <Esc> enter ESC...

  • DJ - delete-yank the text and move everything up a row with the Join.
  • ^V/^file/-1 - start Visual select (block mode) and extend it to the line preceding the next delimeter line.
  • I^R" <ESC> - Do a block insert of the filename (found in unnamed register ") followed by a space. Every selected row will get this text. That's our column insert magic.
  • '>j - Go to the line after the last line of previously selected text. We're ready for the next iteration.

We can create a recursive macro out of a slight variation of these commands. Play the macro and the whole file will be converted into desired format. One caveat: you must have a blank line at the end of file. This is preventable but I was in a hurry. :)

Here's the macro (almost) ready for insertion into register q....

DJ^V/^file\|^$/-1^MI^R" <Esc>'>j@q

I say "almost ready" because you can't just copy/paste this into Vim. Like before ^R means Ctrl-R but to actually enter this text so you can yank it into register q you'll need to precede each control-character (and <Esc>) with Ctrl-V. So where it says ^M you'll actually enter Ctrl+V then Ctrl+M.

  • Great trick! I'll certainly use this "column insert magic." I've been using vim for years but nothing with registers. I have some reading to do. – user22456 May 29 '19 at 18:54
  • Registers mostly play a supporting role here (Visual block mode select/edit is the star) but regardless reading up on them is a good idea. They make a lot of things easy that would otherwise be difficult or impossible. And a few of them are pretty "magic"...especially the expression register "= (I wouldn't start with that one, though! Relatively advanced stuff.) – B Layer May 29 '19 at 20:47

The general pattern we'll be using is: insert text at the beginning of a range of lines.

We have two options:

  • :[range]normal! Itext
  • :[range]substitute/^/text

I tend to think of normal! first, because of the insertion, but the substitute should work just as well. We're going to use substitute here, because it's easier to insert dynamic text at the end of.

Start by defining a function to do most of the work:

function Work() abort
  " delete and capture the file.txt line
  delete a
  " trim newlines and add a space
  let @a = trim(@a) . ' '
  " insert the value on all lines up to the next '.txt' line
  .,/\.txt\|\%$/-1 substitute/^/\=@a

Edit: I was able to eliminate the range error by using \%$ as I had been trying to do in my experiments. But the key was that we need a blank line at the end.

We need a bit of prep, because the function uses the pattern /.txt\|\%$/ as part of a range, so we need a blank line at the end if none exists—:s/\%$/\r, or alternately, :call setline('$', ''), :call append('$', ''), or even :$normal! o. We can inspect visually, of course, but for the scripter:

if len(getline('$')) > 0
  call append('$', '') " or whichever variant you prefer

Then do it with a global command:

:global/.txt/call Work()

Altogether, then, create a script-file called whatever_you_want.vim with the following contents:

function Work() abort
  " delete and capture the file.txt line
  delete a
  " trim newlines and add a space
  let @a = trim(@a) . ' '

if len(getline('$')) > 0

global/.txt/call Work()

Now start vim with

$ vim /path/to/your/file.txt -S /path/to/vimscript.vim

Optionally, add to the end of your script


if you're quite sure of the results.

The script has the benefit of being embarrassingly repeatable.

N.B. This function was crafted specifically to your example. It will definitely fail if any of your "data" contains .txt somewhere.

Your Answer

