Shell Tips
2020-12-01
Interactive Use
Editing the Command Line
Most interactive shells offer a variety of navigation and editing commands and associated key bindings. However, especially for long command lines, nothing beats a real text editor. Fortunately, you can invoke your text editor of choice to edit the command line!
Bash offers the function edit-and-execute-command
,
which is bound to C-x C-e
by default. The command
is executed as soon as the editing session finishes.
Zsh offers the function edit-command-line
. This
function can be made available by running the following commands
(tip: put these in your .zshrc):
autoload -U edit-command-line
zle -N edit-command-line
bindkey '\C-x\C-e' edit-command-line
Of course, a different key binding can be chosen, but
C-x C-e
is unbound by default and matches what Bash
uses. After editing, the command appears on the command line and can
be edited or executed as usual.
Searching in History
One way to quickly re-run a previously executed
command is by searching for it in history. Bash offers
reverse-search-history
, and Zsh offers
history-incremental-search-backward
, both of which are by
default bound to C-r
. The commands search history as
you type. If you find the command you want to run, you can do so
by pressing return. If you want to edit it first, you can use the
movement keys to start editing it. If you want to abort the search,
you can do so using C-g
.
Issuing the history search command again while searching will go to the previous matching command. For example, if you had run the commands:
make foo.c
make bar.c
C-r make
will find make
bar.c
. Pressing C-r
again at that point will find
make foo.c
. If you find that you have gone too far back,
you can reverse the search direction using C-x s
.
Making the Prompt Stand Out
With commands that output lots of text, it can sometimes be hard to see where the output from a command starts or ends. To make this easier, you can make the prompt stand out by, for example, making it a different color. For example, in Zsh, you could do:
export PS1='%F{cyan}%n%f@%F{cyan}%m %1~%f$ '
Here, %F{cyan}
sets the foreground color
to cyan, and %f
sets it back to its default value. The
expressions %n
, %m
, and %1~
refer to the username, the machine name, and the current directory,
respectively, with only the last part of the current directory
displayed, and ~
substituted for the home directory.
This gives a prompt that looks like:
output from some command is here and some more text user@host ~$ some command the second command also outputs multiple lines of text user@host ~$
Indicating Failed Commands in the Prompt
Some programs do not exactly make it clear if
they succeeded or failed. To make this visually apparent, you can
configure your shell to display exit status in the prompt if the
previous command failed. In Zsh, you can use something like
%(?..%F{red}(%?%)%f )
to show the exit status in
parentheses and in red if it was not equal to 0. For example,
my prompt looks like:
user@host ~$ true user@host ~$ false (1) user@host ~$
The expression above parses as: If the exit status
is equal to 0 (
%(?
), then (.
) expand to the
empty string, else (.
) expand %F{red}(%?%)%f
, and a close paren ()
) to end the
conditional.
The expression inside the else-part of the conditional parses as:
Set the foreground color to red (
The close paren needs to have a percent sign in front of it to avoid
being interpreted as the close paren of the conditional.%F{red}
), an
open paren ((
), the last command's exit status
(%?
), a close paren (%)
), a space,
and set the foreground color back to normal (%f
).
Combined with the tip about using color to make the
prompt stand out, this leads to a PS1 of '%(?..%F{red}(%?%)%f
)%F{cyan}%n%f@%F{cyan}%m %1~%f$
', which is some of the less
legible code I've ever written.
Previous Directory
Need to return to the directory you were just in?
cd -
(cd minus) will do that. This works in Bash and
Zsh and probably other shells.
If you know in advance you are going to have to return to the
directory you are currently in, you can use pushd
instead of cd
. This puts the current directory on
a stack. You can return to the most recently pushed directory
using popd
, or you can inspect the stack using
dirs
. In Zsh, dirs -v
shows numbers
with the directories on the stack, and you can return to a directory
by number by using cd +number
, where
number is the number shown by dirs -v
. Here
is an example:
user@host ~$ : Initially, dirs only shows the current directory
user@host ~$ dirs
~
user@host ~$ : Do some work on a project
user@host ~$ cd project/src
user@host src$ : Build it and look at the documentation
user@host src$ pushd ..
user@host project$ make
user@host project$ pushd doc/html
user@host html$ w3m index.html
user@host html$ : Take a look in /tmp
user@host html$ pushd /tmp
user@host /tmp$ : Find source directory in the stack
user@host /tmp$ dirs -v
0 /tmp
1 ~/project/doc/html
2 ~/project
3 ~/project/src
user@host /tmp$ cd +3
~/project/src
user@host src$ dirs -v
0 ~/project/src
1 ~/project/doc/html
2 ~/project
If you find this useful, you may also want to use
setopt autopushd
, which makes cd
push the
current directory onto the stack (like pushd
does). That
way, you can change directories without having to think about if the
current directory is one you might want to return to; it's always
remembered.