How to Type Less and Do More in Terminals

Henry Huang
May 2, 2020 · 8 min read
Type Less and Do More

Shells like bash, zsh come with many great features to save repeated typing, but most of them are buried in those informative, yet lengthy man pages. This article demonstrates some of the most time-saving features and commands that I heavily use every day.

NOTE: The demonstrations are made in bash, but most of them are applicable in zsh as well.

Before We Jump In

Here we go:

# add 127.0.0.1 www.example.com
$ vim /etc/hosts
$ sudo !!
$ curl www.example.com/test.php
curl: (7) Failed to connect to localhost port 80: Connection refused
$ vim /etc/apache2/apache2.conf
$ sudo !!
$ sudo service apache2 restart
* Restarting web server apache2
* The apache2 configtest failed.
...
$ /etc/apa*
$ cp apache2.conf{,.broken}
$ sudo vim !!:1 # fix the config error
$ !?service
$ !cu
...
<title>404 Not Found</title>
...
$ ~/web-project
$ vim test.php
$ !c
<?php echo "hello world"; ?>
$ -
$ # add PHP handler
(reverse-i-search)`vi': vim apache2.conf
vim apache2.conf
$ !s
$ !c
hello world
$ -
$ !v
$ cat !$
<?php echo "hello world - ready to commit"; ?>
$ git add !$
$ !c
hello world - ready to commit
$ mkdir -p uploads
$ >uploads/.git_keep
$ git add !$
$ git commit -m 'Initial commit'
$ ./watchdog.sh >&-

If you understand exactly what happened in the above terminal session, then don’t waste your precious time. Go read other great articles!

The above is a demonstration of typing less and doing more, and is equivalent to the following:

# add 127.0.0.1 www.example.com
$ vim /etc/hosts
$ sudo vim /etc/hosts
$ curl www.example.com/test.php
curl: (7) Failed to connect to localhost port 80: Connection refused
$ vim /etc/apache2/apache2.conf
$ sudo vim /etc/apache2/apache2.conf
$ sudo service apache2 restart
* Restarting web server apache2
* The apache2 configtest failed.
...
$ cd /etc/apache2
$ cp apache2.conf apache2.conf.broken
$ sudo vim apache2.conf # fix the config error
$ sudo service apache2 restart
$ curl www.example.com/test.php
...
<title>404 Not Found</title>
...
$ cd ~/web-project
$ vim test.php
$ curl www.example.com/test.php
<?php echo "hello world"; ?>
$ cd /etc/apache2
$ vim apache2.conf # add PHP handler
$ sudo service apache2 restart
$ curl www.example.com/test.php
hello world
$ cd ~/web-project
$ vim test.php
$ cat test.php
<?php echo "hello world - ready to commit"; ?>
$ git add test.php
$ curl www.example.com/test.php
hello world - ready to commit
$ mkdir -p uploads
$ touch uploads/.keep
$ git add uploads/.git_keep
$ git commit -m 'Initial commit'
$ ./watchdog.sh >/dev/null

The second session clearly has a lot of repeated typing and takes much more keystrokes (more than 300), only to achieve the same thing in the first session, which contains some greek letters. If you’re ready to switch to the first session to type less and do more, read along!

Borrowing Words from Command History (!!, !$, !-2:$)

“!!” Represents the Last Command

user@local:~$ vim /etc/hosts

The above command often fails with frustration because you forgot to prefix it with sudo. Now, instead of furiously hitting that up arrow, Ctrl + A (to bring you to the beginning of the command) and type the bloody sudo, you can simply type:

user@local:~$ sudo !!

It works exactly the same as sudo vim /etc/passwd.

It’s not because you expressed your anger with that furious !!, but because !! represents the last command (vim /etc/passwd).

Another example would be to write the last command into a shell script:

user@local:~$ make ARCH=mips CROSS_COMPILE=mips-linux- O=./build/mipseb -j8
user@local:~$ echo !! >> build-mips.sh

“!$ “ Represents the Last Word in the Last Command

user@local:~$ vim ~/.bin/my-custom-command.sh
user@local:~$ chmod +x !$

chmod +x !$ expands to chmod +x ~/.bin/my-custom-command.sh because !$ represents the last word in the last command.

More Like These

  • !-2:$: the last word in the second-to-last command
  • !?keyword: the latest command that CONTAINS (not starts with) the word keyword
  • <pattern>:p: print the expansion (e.g. !!:p prints the previous command, !-2:$:p prints the last word in the second-to-last command, !?service prints the first command history that contains the word service, etc)

To read all of them, go to the HISTORY EXPANSION section of bash’s man page (man bash).

NOTE: Be super careful when you do !r, because you might accidentally do rm -rf and ruin your day. When you’re not sure, use Ctrl + r instead (will be covered later). Or use !r:p to see what it actually expands to.

Brace Expansion

foo{1,2,3} and foo{1..3} both expand to foo1 foo2 foo3

Examples:

user@local:~$ mkdir folder{1,2,3}
user@local:~$ ls
folder1/ folder2/ folder3/
user@local:~$ mkdir -p folder{1,2,3}/subfolder{1..5}
user@local:~$ tree
.
├── folder1
│ ├── subfolder1
│ ├── subfolder2
│ ├── subfolder3
│ ├── subfolder4
│ └── subfolder5
├── folder2
│ ├── subfolder1
│ ├── subfolder2
│ ├── subfolder3
│ ├── subfolder4
│ └── subfolder5
└── folder3
├── subfolder1
├── subfolder2
├── subfolder3
├── subfolder4
└── subfolder5
user@local:~$ cp httpd.conf{,.bak}
user@local:~$ touch requirements/{common,dev,test}.txt
user@local:~$ for ip in 192.168.0.{1..254}; do nc -nz $ip 80 2>&- && echo $ip is serving at port 80; done

Search Previous Commands (Ctrl + r)

user@local:~$ vim /path/to/a/file/in/a/very/deep/folder
...

5 minutes later, you need to vim that file in that very deep folder again. But:

  • It’s far away from you (you have to hit like 100 ↑ to get to it)
  • It’s so long that even TAB completion isn’t that helpful (vim /path/to/partial<TAB>/paths<TAB>/giveup)

Now, hit Ctrl + r to bring up the search prompt:

user@local:~$ # Ctrl + r
(reverse-i-search)`':

Now, search for that command by typing its prefix (vi in this case):

(reverse-i-search)`vi': vim /etc/passwd

Oops, vim /etc/passwd happens to be the very last command starting with vi, but it’s not the right command we’re looking for. Don’t worry, we have 2 options here:

Option1: Keep Hitting Ctrl + r

(reverse-i-search)`vi': vim /path/to/a/file/in/a/very/deep/folder

If there are too many commands to iterate through, go with option 2.

Option2: Type longer prefix

(reverse-i-search)`vim /p': vim /path/to/a/file/in/a/very/deep/folder

To bring this up to the next level, you can do a fuzzy search with this amazing tool: fzf.

Redirecting Standard Output and Standard Error

user@local:~$ ./run.sh >/dev/null 2>&1
user@local:~$ ./run.sh &>/dev/null

Suppressing stdout:

user@local:~$ ./run.sh >/dev/null
user@local:~$ ./run.sh >&-

To find out more, read the REDIRECTION section of bash’s man page (man bash).

Directory Traversal

Previous Directory (-)

user@local:~$ cd /path/to/foo
user@local:foo$ cd /path/to/bar
# let's go back to foo
user@local:bar$ cd -
# let's go back to bar
user@local:foo$ cd -
user@local:bar$

shopt -s autocd

user@local:~$ echo 'shopt -s autocd' >> ~/.bashrc
user@local:~$ exec -l $SHELL # reload the shell

user@local:~$ ls
folder1/ folder2/ folder3/
# cd into folder1 by simply typing its name
user@local:~$ folder1
# cd into folder2
user@local:folder1$ ../folder2
# cd into previous directory, folder1
user@local:folder2$ -
# cd to home (~)
user@local:folder1$ ~
user@local:~$

If these directory traversal tricks aren’t enough for you, you might want to take a look at autojump, which autocompletes, learns and jumps without intermediate directories to the destination in your mind. Kinda magic, kinda cool.

The Fastest Way to Create an Empty File

The > symbol tells bash to open the file for writing, so it creates the file for you. This saves you 5 chars each time you use it.

NOTE: This is NOT equivalent to touch, and it will TRUNCATE the file if the file already exists.

Command Line Navigation

  • Ctrl + a: go to the beginning of the line
  • Ctrl + e: go to the end of the line
Navigating the Command Line, borrowed from codeproject

However, the above navigation only works in emacs mode (set -o emacs, the default setting in bash). I personally use vi mode (set -o vi).

Shorten your Most Commonly Used Commands

The following used to be my top 5:

$ awk '{print $1}' < ~/.bash_history | sort | uniq -c | sort -k1nr | head -n5
92 cd
74 ls
66 git
58 docker
31 go

The top 4 have been shortened as follows:

  • cd is totally eliminated by using shopt -s autocd (if you have fzf, use Alt + c to fuzzy search for a directory and cd into it)
  • ls is shorted as l: alias l='ls -alh'
  • git is mostly used as git status, and it’s shortened to g by sourcing the following bash snippet (modified from colbycheeze’s dotfiles). When you type g, it actually evaluates to git status. And autocompletion for git still works because of the eval $(complete ...) part in the end. If you don’t like typing git commands at all, you might find lazygit very useful.
g() {
if [ $# -gt 0 ]; then
cmd="git"
for arg in "$@"; do
cmd+=" \"${arg//\"/\\\"}\""
done
eval $cmd
else
git status
fi
}
# Complete g like git
_completion_loader git 2>/dev/null
eval $(complete -p git | sed 's/ git$/ g/g')
  • docker is similar to g: typing d evaluates to docker ps, and autocompletion still works. If you don’t like typing docker commands at all, there’s also lazydocker.
d() {
if [ $# -gt 0 ]; then
cmd="docker"
for arg in "$@"; do
cmd+=" \"${arg//\"/\\\"}\""
done
eval $cmd
else
docker ps
fi
}
# Complete d like docker
_completion_loader docker 2>/dev/null
eval $(complete -p docker | sed 's/ docker$/ d/g')

Conclusion

$ vim /etc/hosts
$ sudo !!
$ vim index.php
$ git add !$
$ cp apache2.conf{,.bak}
$ mkdir 192.168.{1..254}.{1..254}
# ctrl + r
(reverse-i-search)`vi': vim /etc/passwd
# ctrl + e
# ctrl + a
$ ./run.sh &>/dev/null$ shopt -s autocd
$ /etc/apache2
$ cd /path/to/new/dir
$ -
$ >/var/www/html/test
$ curl localhost/test

Mac O’Clock

The best stories for Apple owners and enthusiasts

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store