Learn-Vim/ch14_external_commands.md

216 lines
6.3 KiB
Markdown
Raw Normal View History

# Ch14. External Commands
2020-09-03 15:55:15 +00:00
Inside the Unix system, you will find many small, hyper-specialized commands where each does one thing 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?
In this chapter, you will learn how extend Vim to work seamlessly with external commands.
2020-11-13 01:02:04 +00:00
## The Bang Command
2020-09-03 15:55:15 +00:00
Vim has a bang (`!`) command that can do three things:
1. Read the STDOUT of an external command into the current buffer.
2. Write the content of your buffer as the STDIN to an external command.
3. Execute an external command from inside Vim.
2020-11-14 20:44:37 +00:00
## Reading The STDOUT Of A Command Into Vim
2020-09-03 15:55:15 +00:00
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 commmand 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
```
2021-01-18 23:03:02 +00:00
Now the STDOUT from running `cat file1.txt` will be inserted after line 10.
2020-09-03 15:55:15 +00:00
2020-11-14 20:44:37 +00:00
## Writing The Buffer Content Into An External Command
2020-09-03 15:55:15 +00:00
In addition to saving a file, you can also use the write command (`:w`) 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](https://nodejs.org/en/) installed in your machine, then run:
```
:w !node
```
2020-12-07 14:21:01 +00:00
Vim will use `node` to execute the Javascript expressions to print "Hello Vim" and "Vim is awesome".
2020-09-03 15:55:15 +00:00
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".
2020-11-14 20:44:37 +00:00
## Executing An External Command
2020-09-03 15:55:15 +00:00
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.
2020-11-13 01:02:04 +00:00
## Filtering Texts
2020-09-03 15:55:15 +00:00
If you give `!` a range, it can be used to filter texts. Suppose you have this:
```
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 the `tr` 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 (`%`).
2020-12-07 14:21:01 +00:00
- `awk "{print $1}"` prints only the first column of the match. In this case, the word "hello".
2020-09-03 15:55:15 +00:00
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.
2020-11-14 20:44:37 +00:00
## Normal Mode Command
2020-09-03 15:55:15 +00:00
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:
2020-12-07 14:21:01 +00:00
- `!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 rule `verb + noun` applies.
2020-09-03 15:55:15 +00:00
- `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.
2020-11-14 20:44:37 +00:00
## Learn External Commands The Smart Way
2020-09-03 15:55:15 +00:00
Vim is not an IDE. It is a lightweight modal editor that is highly extensible by design. Because of this extensibility, you have easy access to any external command in your system. With this, 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 filter a text, look if there is an external command that can solve your problem. Don't worry about mastering everything about a particular command. Just learn the ones you need to complete the current task.