1

I'm trying to modify this file, by extracting the very first line in the file and prepend it to each of the consecutive lines until end of the file

black
widow
card
Friday
berry

I used ex command (which doesn't work as expected) which prepends the string black from 2nd line onward without deleting the first line.

 ex -sc '2,$s/^/black: /|x' file

What I want to do?

  1. Dynamically get the string black in the replacement part s//<>
  2. Remove the first line after getting the string black in the buffer

Expected output would be

black: widow
black: card
black: Friday
black: berry
2
  • What do you mean by "dynamically"? Do you want a function that will do this? Or do you want something else?
    – 3N4N
    Commented Apr 19, 2019 at 13:57
  • @klaus: What I meant was black: was hard-coded in my example, how can I identify it with the ex command itself - For e.g. get the first line and store it in a variable and use in the replacement section
    – Inian
    Commented Apr 19, 2019 at 14:09

4 Answers 4

3

@D.BenKnoble has already given you a terrific answer explaining all the ins and outs of batch mode, but in case you were looking for a one liner similar to the one you already tried, here's one that I think is a bit simpler than the existing ones in the current answers:

ex -sc 'norm!ggy$dd' -c '%norm!i^R0: ' -c 'x' file

The first :normal command moves to the start of the file gg, yanks the contents of the line y$, and then deletes the line itself dd. We yank before deleting because we don't want the newline.

We run the second :normal command on every line by means of a % range, inserting the contents of the yank register "0—i.e. the original first line—followed by the required colon and space.

N.B. The slight technicality here is that the ^R you see in the middle of the command is actually a literal CTRL-R character—which you can probably enter in your shell by typing Ctrl-VCtrl-R: you cannot copy/paste the whole command from this answer. For a copy-pastable version, we need to add the character using a string literal and then execute it:

ex -sc 'norm!ggy$dd' -c 'exe "%norm!i\<C-R>0: "' -c 'x' file

The third simply saves the file and quits Vim, as in the command you already tried.

Extra credit: Finally, here's a version that replaces a little complexity in the first ex command with a bunch in the second:

ex -sc '1d a' -c '%norm!i^Ra^[kJi:' -c 'x' file
                         ^^ ^^

This time we have two literal control characters: a CTRL-R and an ESC (denoted by ^[, entered via Ctrl-VEsc), which we use to remove the newline included when we used the ex :delete command to delete the entire line into register "a.

1
  • Thanks, don't know i can enter literal character in shell. Nice trick with J to deal with line break.
    – dedowsdi
    Commented Apr 24, 2019 at 23:34
4
+25

Using ex

Ex is the command-line successor to the venerable ed, a line-oriented editor. You can access ex-mode from vim by typing Q (unless you have it mapped). gQ gives an improved ex-mode. See :help Ex-mode.

You can also start ex from the command line, just like vim:

ex {file}

Then you type your commands, and ex does them.

All the :-style commands available in vim are available to ex.

Scripting ex

There are scriptable ways of giving commands to ex. We focus on

  1. sourcing a file
  2. standard input

Sourcing a file

Write a (vim)script in a file, then do

ex -S {script.vim} {file}

Your script is sourced (executed line for line) on the file. When it's finished, you'll be left in ex as normal. We'll discuss the niceties of ending scripts at the end.

One of the benefits of this approach is the ability to easily edit and comment your code (e.g., from vim).

Another benefit is the ability to source these scripts from inside vim (via :source script.vim), and undo them (u) as desired.

Standard Input

Since you give commands to ex on standard input, you can use any shell technique to pass commands to it. For example, with bash, any of the following should work:

ex {file} <<EOF
{script text}
EOF

# or

ex {file} <{script.vim}

# or

ex {file} < <(cat <<DOG
{script text}
DOG
)

This has the benefit that the entire contents of the 'ex-script' can be placed in (human-readable!) form in a shell script.

Script considerations

What is the purpose of the script?

If it is intended to perform some changes to a file on disk, it should end with the line wq or a variation thereof, in order to write and quit.

If the script is just a pre-cursor to normal editing (and for you only), you can end with visual to drop you into Normal-mode of full-on vim.

Otherwise, just let it end and drop you into interactive mode.

Solution

Finally, a script to suit your needs:

" Delete first line into register z
1 delete z
" Eliminate newlines
let @z = substitute(@z, '\v\n', '', 'g')
" Put the contents from the first line on all the others
" cf. :help sub-replace-special
%substitute/^/\=printf('%s: ', @z)/
" Optional
" write
" quit

If using a heredoc syntax in the shell, you may need to eliminate the comments entirely (delete them), since shell-quoting and comments may get in the way of vim seeing the comments. In interactive and source use, commented lines are perfectly fine.


I've run ex -S script.vim test.txt with the bottom two lines uncommented, where test.txt was

black is the color of my true love's hair
widow
card
Friday
berry

And received as the new version

black is the color of my true love's hair: widow
black is the color of my true love's hair: card
black is the color of my true love's hair: Friday
black is the color of my true love's hair: berry
5
  • I've not left a detailed explanation of the script itself other than the comments; hopefully it is self-explanatory, and the help provides good enough reference. If not, I can update it. I focused on the ex usage, since that seemed to be OPs point of difficulty.
    – D. Ben Knoble
    Commented Apr 22, 2019 at 23:08
  • 4
    Perhaps a bit easier: :%s/^/\=@z.': '/ no need for the printf() function. Commented Apr 23, 2019 at 6:04
  • @ChristianBrabandt good call on the beginning of line replacement. I do tend to find string concatenation hard to read, and the printf makes it clear where things go, letting me see the structure of the whole string. YMMV
    – D. Ben Knoble
    Commented Apr 23, 2019 at 13:13
  • Can ex read command from stdin ? I tried, but failed. The man page says -S doesn't support -.
    – dedowsdi
    Commented May 3, 2019 at 12:25
  • -S takes the name of a file. You can use process substitution to give it one though. ex -S <(create your contents here. I’m not sure if ex -S - or ex -S /dev/stdin would work... but ` { cat - > /tmp/tempfile && ex -S /tmp/tmpfile }` would work and could read from stdin or a pipe. Dont forget to remove tmpfile when done via trap.
    – D. Ben Knoble
    Commented May 3, 2019 at 15:02
3
ex -s +'norm! gg"adiwdd' +'exec printf("%%s/^/%s : /", @a)' +'x' ex_txt
  • norm! gg"adiwdd delete black in 1st line to register a, delete first line
  • exec printf("%%s/^/%s : /", @a) add content of register a to start of every line. You can get content of register a via @a or getreg('a') in ex mode . check :h printf() if you have problem with %%.
  • x save and exit

I'm new to this area, there might be other ways to access register in ex mode.

update

vim -u NONE -Es +'norm! gg"adiwdd' +'exec printf("%%s/^/%s : /", @a)' +'x' ex_txt

OP had to add -u NONE to make it work, that's very weird, i have no idea what kind of setting or plugin would make ex broken.

4
  • I tried this on my sample file, and it remained the same after running the command. may be I'm missing something in my vim config? I'm running Vim 7.4
    – Inian
    Commented Apr 22, 2019 at 11:10
  • trye vim -u NONE -Es +'norm! gg"adiwdd' +'exec printf("%%s/^/%s : /", @a)' +'x' ex_txt
    – dedowsdi
    Commented Apr 22, 2019 at 11:32
  • Yes it worked. Could you update the answer with the same? I'll continue to watch out for other answers that could make this processing simpler. But appreciate your efforts for this. Thanks!
    – Inian
    Commented Apr 22, 2019 at 11:37
  • I guess your vimrc or plugins has something to do with this. You mensioned that some command got hanged, that's not true, it's because of -s supress ex mode output, if that happens again, you can use x or q to exit. Try ex -u NONE -s +'norm! gg"adiwdd' +'exec printf("%%s/^/%s : /", @a)' +'x' ex_txt
    – dedowsdi
    Commented Apr 22, 2019 at 11:50
2

You can use registers (see :h reg) to store a word of a line. For you particular case, you would have to perform the following set of commands:

:norm! gg"zyiw
:norm! gg"_dd
:%s/^/<C-r>z: /
  • The first command yanks the first word into the register z from the first line
  • The second command deletes the first line into blackhole register.
  • The third command is a general substitute command where <C-r> lets you insert content of a register (see :h i_CTRL-R)

Of course, you can do this some other more general and secure way, but I guess that was not your query. You wanted to know how to store contents.

7
  • Thanks but you can provide an ex mode equivalent of it. I’m planning to use it from the command line
    – Inian
    Commented Apr 19, 2019 at 14:23
  • 1
    No, sorry. But I don't see myself using vim ex commands from command line. I always use sed. You can then pipe the sed commands together to achieve this.
    – 3N4N
    Commented Apr 19, 2019 at 14:25
  • 2
    These being ex commands, you should be able to feed them together in an ex command line.
    – D. Ben Knoble
    Commented Apr 19, 2019 at 15:51
  • 1
    You don't really need to use register z or the blackhole register in this answer. Yanks are stored in the yank register, which isn't touched by the delete.
    – Rich
    Commented Apr 24, 2019 at 15:11
  • 1
    Also, :1d is a bit simpler than :norm! ggdd (See :help :delete)
    – Rich
    Commented Apr 24, 2019 at 15:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.