I have a log file full of arbitrary lines, interspersed with some lines which are a proper xml sequence, with all the tags on a single line (for each sequence). I want to simply process this so that all the xml lines become broken out in easy to read format. There is a utility in linux called xmllint that looks useful here.

I am able to do this easily manually in vim, by searching for

!! xmllint --format --recover - 


:! cmd 

doesn't work here, but

:.! cmd 

does. I'm not sure if that's because the first one is legacy "Ex" mode, while adding the range (.) makes the second one a Vim filter?

What I want is to be able to do it automatically, not by saving manual commands in a recorded register and replaying it possibly tens or hundreds of times, but O(1) commands to simply find all the lines with "xml" and filter them through this external command, replacing their text in the buffer with the nicely formatted version.

This should definitely be possible given the power and style of vim, but I've tried for hours and can't do it.

The idea is something like

:%g/xml/normal!!:$xmllint --format --recover -

I've tried with and without normal, and all the variations of : and ! to try to filter through an external command.

Most of the time, it does absolutely nothing to the buffer, nothing even to undo, even though the :g part is definitely matching. Other times, like

:%g/xml/!xmllint --format --recover -

it waits forever until I send a Ctrl-C for each match. Basically it seems to be waiting for xmllint which is stuck waiting at STDIN.

How can I take a working :%g/pattern/ command and make it replace the matching lines with the output of an external command (a command which itself takes the aforementioned matching line as its input) ?

  • 2
    Compare :h :! and :h :range!. – Antony Jan 5 '17 at 0:25
  • 1
    % and g don't go together - did you mean :g/xml/ .! xmllint --format --recover -? – muru Jan 5 '17 at 12:26
  • Thanks. Yes, I did notice that about :! vs :range! while I was doing my research. It confused me a bit because most other commands in vim with an optional range are not so different from eachother. It's especially hard to mentally process for me because of the similarities between these two different commands, also. About :%g, you are correct that the % is unnecessary because it is the default, but it works just the same, specifying the whole range. :h :g – Starman Jan 5 '17 at 15:26

I do not have xmllint or your file handy, but the following works for me. My VIM buffer is filled with


I then run the command :g/foo/.! cat -n which produces

     1  foo
     1  foo

as expected.

  • 1
    Replacing with multiple lines works just fine. Try :g/foo/ .!ls – Antony Jan 6 '17 at 10:50
  • I don't know what went wrong there last time. Replacing multiple lines is indeed possible. I removed that part from my answer. – Octaviour Jan 9 '17 at 9:03

I have something that works in O(1) time, but I don't think it's the best solution, and it especially bothers me that I have to type out a long sequence of characters without simply being able to cut-paste.

(Feel free to submit an answer that simply refines this one, teaching me how to cut paste better in Ubuntu)

qa0!$xmllint --format --recover -^M
:g/xml/normal @a 

This works in one pass. Note that ^M is actually a literal "enter" key press, inserted by typing , not by typing caret (^) or M.

See how ridiculous this is? I CAN use a :g command, but can only specify the action command by sucking the commands (that should go in the suffix of the :g/pat/cmd) into a named register, and then executing that using "normal @". I don't understand why it doesn't work if I cut and paste the stuff in the @ to the end of the "normal" command on the :g line.

I am still hoping for a proper answer using a one-line :g command, or something similar. Maybe some compile flag or other setting needs to change to allow the one-line :g command?

  • It's also problematic that I have to type out lots of characters for this solution, I can't seem to keep them in a text file and just cut and paste. If I middle-click into gvim, it seems to paste my clipboard buffer in insert mode no matter what I was doing. Except if I'm in command mode. So technically I can manually type ":" and get to command mode, and paste the rest of the :g command to save some typing. But it's still awkward. And a display of :reg makes "a look very different if I use middle click to paste stuff during the creation of that line. – Starman Jan 5 '17 at 16:29
  • @muru already gave you the answer in the comments. – Antony Jan 5 '17 at 22:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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