Inside the Unix system, you will find many small, hyper-specialized commands that does one thing (and does it well). You can chain these commands to work together to solve a complex problem. Wouldn't it be great if you can use these commands from inside Vim?
Definitely. In this chapter, you will learn how extend Vim to work seamlessly with external commands.
Vim has a bang (!
) command that can do three things:
- Read the STDOUT of an external command into the current buffer.
- Write the content of your buffer as the STDIN to an external command.
- Execute an external command from inside Vim.
Let's go through each of them.
The syntax to read the STDOUT of an external command into the current buffer is:
:r !cmd
:r
is Vim's read command. If you use it without !
, you can use it to get the content of a file. If you have a file file1.txt
in the current directory and you run:
:r file1.txt
Vim will put the content of file1.txt
into the current buffer.
If you run the :r
command followed by a !
and an external command, the output of that command will be inserted into the current buffer. To get the result of the ls
command, run:
:r !ls
It returns something like:
file1.txt
file2.txt
file3.txt
You can read the data from the curl
command:
:r !curl -s 'https://jsonplaceholder.typicode.com/todos/1'
The r
command also accepts an address:
:10r !cat file1.txt
Now the STDOUT from running cat file1.txt
will be inserted after line 10.
The command :w
, in addition to saving a file, can be used to pass the text in the current buffer as the STDIN for an external command. The syntax is:
:w !cmd
If you have these expressions:
console.log("Hello Vim");
console.log("Vim is awesome");
Make sure you have node installed in your machine, then run:
:w !node
Vim will use node
to execute the JavaScript expressions to print "Hello Vim" and "Vim is awesome".
When using the :w
command, Vim uses all texts in the current buffer, similar to the global command (most command-line commands, if you don't pass it a range, only executes the command against the current line). If you pass :w
a specific address:
:2w !node
Vim only uses the text from the second line into the node
interpreter.
There is a subtle but significant difference between :w !node
and :w! node
. With :w !node
, you are "writing" the text in the current buffer into the external command node
. With :w! node
, you are force-saving a file and naming the file "node".
You can execute an external command from inside Vim with the bang command. The syntax is:
:!cmd
To see the content of the current directory in the long format, run:
:!ls -ls
To kill a process that is running on PID 3456, you can run:
:!kill -9 3456
You can run any external command without leaving Vim so you can stay focused on your task.
If you give !
a range, it can be used to filter texts. Suppose you have the following texts:
hello vim
hello vim
Let's uppercase the current line using the tr
(translate) command. Run:
:.!tr '[:lower:]' '[:upper:]'
The result:
HELLO VIM
hello vim
The breakdown:
.!
executes the filter command on the current line.tr '[:lower:]' '[:upper:]'
calls thetr
command to replace all lowercase characters with uppercase ones.
It is imperative to pass a range to run the external command as a filter. If you try running the command above without the .
(:!tr '[:lower:]' '[:upper:]'
), you will see an error.
Let's assume that you need to remove the second column on both lines with the awk
command:
:%!awk "{print $1}"
The result:
hello
hello
The breakdown:
:%!
executes the filter command on all lines (%
).awk "{print $1}"
prints only the first column of the match.
You can chain multiple commands with the chain operator (|
) just like in the terminal. Let's say you have a file with these delicious breakfast items:
name price
chocolate pancake 10
buttermilk pancake 9
blueberry pancake 12
If you need to sort them based on the price and display only the menu with an even spacing, you can run:
:%!awk 'NR > 1' | sort -nk 3 | column -t
The result:
buttermilk pancake 9
chocolate pancake 10
blueberry pancake 12
The breakdown:
:%!
applies the filter to all lines (%
).awk 'NR > 1'
displays the texts only from row number two onwards.|
chains the next command.sort -nk 3
sorts numerically (n
) using the values from column 3 (k 3
).column -t
organizes the text with even spacing.
Vim has a filter operator (!
) in the normal mode. If you have the following greetings:
hello vim
hola vim
bonjour vim
salve vim
To uppercase the current line and the line below, you can run:
!jtr '[a-z]' '[A-Z]'
The breakdown:
!j
runs the normal command filter operator (!
) targetting the current line and the line below it. Recall that because it is a normal mode operator, the grammar ruleverb + noun
applies.!
is the verb andj
is the noun.tr '[a-z]' '[A-Z]'
replaces the lowercase letters with the uppercase letters.
The filter normal command only works on motions / text objects that are at least one line or longer. If you had tried running !iwtr '[a-z]' '[A-Z]'
(execute tr
on inner word), you will find that it applies the tr
command on the entire line, not the word your cursor is on.
Vim is not an IDE. It is a lightweight modal editor that is highly extensible by design. Because of this extensibility, you have an easy access to any external command in your system. Armed with these external commands, Vim is one step closer from becoming an IDE. Someone said that the Unix system is the first IDE ever.
The bang command is as useful as how many external commands you know. Don't worry if your external command knowledge is limited. I still have a lot to learn too. Take this as a motivation for continuous learning. Whenever you need to modify a text, look if there is an external command that can solve your problem. Don't worry about mastering everything, just learn the ones you need to complete the current task.