Integrate Shell Commands Into Vi Workflow

Exclamation Points are the Magic Wands of Vi, Learn Them

You seriously don’t know Vi until you know how to fully integrate the shell and transform lines by passing them to shell commands that then replace those lines with their output.

Shell integration is the single most powerful aspect of Vi and yet it is hardly known, documented, or even used by veteran users.

Unfortunately, those who don’t understand the power of full shell integration build stupid things like NeoVim.

Magic wands are a mnemonic device (originally used by Rob Muhlestein) for using the exclamation point to send the current line, section, or page to any command that can be run from the shell and replace those lines with the output of that command.

In fact, once you master the keystrokes you can use the same to accomplish other tasks that use the ex command line — like finding and replacing — by just backspacing out the exclamation point. The automatic ex line range inference is a really powerful shortcut that is built into all Vi versions.

Yeah, you don’t need all those Vimscript macros. No really, you don’t. Just use the shell instead.

Line Wand

The line wand is by far the most frequent wand you will use. It is the fastest and most common way to extend vi. Just spam bang twice to send the current line to the shell command or just replace the current line with the output of a utility command like date, cal or any text that comes out of any command.

Send Line to Bash: !!bash

Before:

for i in {1..20}; do echo Item $i.; done

After:

Item 1.
  Item 2.
  Item 3.
  Item 4.
  Item 5.
  Item 6.
  Item 7.
  Item 8.
  Item 9.
  Item 10.
  Item 11.
  Item 12.
  Item 13.
  Item 14.
  Item 15.
  Item 16.
  Item 17.
  Item 18.
  Item 19.
  Item 20.

Send Line to Calculator: !!bc

This requires the bc program to be installed, which is by default on most Linux systems.

Before:

232432 * 2342342 / 234

After:

2326646306

Send Line to Python3: !!python3

Before:

[print(f"Item {x}.") for x in range (1,21)]

After:

Item 1.
  Item 2.
  Item 3.
  Item 4.
  Item 5.
  Item 6.
  Item 7.
  Item 8.
  Item 9.
  Item 10.
  Item 11.
  Item 12.
  Item 13.
  Item 14.
  Item 15.
  Item 16.
  Item 17.
  Item 18.
  Item 19.
  Item 20.

Section Wand

Sending a section is a great way to run part of a larger script (say for configuration) without having to cut it out and put it into another file. If you use this easy way, don’t forget to undo it later to replace it with the original code (instead of the output of the lines of code).

Send Section to Bash: !}bash

Before:

for i in {1..20}; do
      echo Item $i.
  done

After:

Item 1.
  Item 2.
  Item 3.
  Item 4.
  Item 5.
  Item 6.
  Item 7.
  Item 8.
  Item 9.
  Item 10.
  Item 11.
  Item 12.
  Item 13.
  Item 14.
  Item 15.
  Item 16.
  Item 17.
  Item 18.
  Item 19.
  Item 20.

Send Section to Sort: !}sort

Before:

* [\@zerostheory](https://twitch.tv/zerostheory)
  * [\@OGLinuk](https://twitch.tv/OGLinuk)
  * [\@elementhttp](https://twitch.tv/elementhttp)
  * [\@elremingu](https://twitch.tv/elremingu)
  * Jovan Vladislav
  * [\@mtheory11dim](https://twitch.tv/mtheory11dim)
  * [\@smartrefigerator](https://twitch.tv/smartrefigerator)
  * [\@returntodust](https://twitch.tv/returntodust)
  * [\@MousePotato](https://twitch.tv/MousePotato)
  * [\@unres1gned](https://twitch.tv/unres1gned)

After:

* [\@elementhttp](https://twitch.tv/elementhttp)
  * [\@elremingu](https://twitch.tv/elremingu)
  * Jovan Vladislav
  * [\@MousePotato](https://twitch.tv/MousePotato)
  * [\@mtheory11dim](https://twitch.tv/mtheory11dim)
  * [\@OGLinuk](https://twitch.tv/OGLinuk)
  * [\@returntodust](https://twitch.tv/returntodust)
  * [\@smartrefigerator](https://twitch.tv/smartrefigerator)
  * [\@unres1gned](https://twitch.tv/unres1gned)
  * [\@zerostheory](https://twitch.tv/zerostheory)

Line Number Wand: !:<lineafter>

Line number wand is just a larger version of the section wand but allows blank lines to be included. It takes more keystrokes, however. Start with a bang and follow it up with a colon and the line after the last line that you want to include.

For the following example imagine the first line is line 10 of the file and you want to send it to pandoc for rending as HTML. You would position your cursor on the first # (on line 10) and type !:22<enter> (because there are 11 lines to send). Then type pandoc<enter> after the ! as the command to receive the lines and replace them.

# This is a title with *emphasis*

  * one
  * two
  * three

  How about a paragraph

  ## Thursday, March 12, 2020, 7:46:39PM

  something
<h1 id="this-is-a-title-with-emphasis">This is a title with <em>emphasis</em></h1>
  <ul>
  <li>one</li>
  <li>two</li>
  <li>three</li>
  </ul>
  <p>How about a paragraph</p>
  <h2 id="thursday-march-12-2020-74639pm">Thursday, March 12, 2020, 7:46:39PM</h2>
  something

Creating Transformational Filter Commands

Rather than using a Vim-only macro or even tying yourself to Vim at all you can write simple shell scripts and use them much faster than any hotkey combination you could set and with greater flexibility. Anything you can code into an executable command that can run from the shell can be inserted using this method.

Commenting Something Out

Sure you could to :.,+10s/^/# and make a mapping for that, or use visual mode and spam your way through the same sort of thing— or you could just to !}cmt and be done with it. Your fingers will thank you later. Plus you already are used to using the wands for everything else.

Before:

for i in {1..20}; do
      echo Item $i
  done

Here’s the command to add to your PATH:

#!/bin/bash
  while IFS= read -r line; do
    echo "${1:-#} $line"
  done <&1

And then !}cmt

# for i in {1..20}; do
  #  echo Item $i
  # done

No mess. No fuss. No Vimrc bloat. Just another command that follows the Unix philosophy of doing one thing really well that integrates easily. You can even integrate your cmt with other scripting that has nothing to do with your Vi editing session, which you could never do if you can built it into your bloated vimrc file.

You can change the comment character just by calling cmt // instead.

By the way, this makes the power of Perl and Bash regular expressions really obvious.