Skip to main content

Using word modifiers with Bash history in Linux

So you've mastered parsing Bash history? Now you're ready to explore Bash modifiers available to the history command.
Image
Bash history word modifiers

Photo by Pressmaster from Pexels

In my article, Parsing Bash history, I demonstrated how to reference items in your shell history by line number as well as by word position. Once you've mastered parsing Bash history by line and word index, you can benefit from the modifiers available to the history command.

The histexpand shell option

Before discussing history parsing, it's worth mentioning that the exclamation point (!) notation is only available to your Bash shell thanks to the histexpand option. Because histexpand is enabled by default, many users never give it much consideration. However, it's the option that enforces expansion of a ! to the list of history entries.

Should the option be toggled off, you lose this functionality. For example:

$ echo foo
$ set +o histexpand
$ !-2
bash: !-2: command not found
$ set -o histexpand
$ !-3
echo foo
foo

Generally, histexpand is on by default, except in non-interactive shells (for instance, in a shell script). Should history shorthand ever fail you, you can either activate histexpand as a shell option or else use the full history command implied by histexpand:

$ echo foo
$ set +o histexpand
$ history -p !-2
echo foo
foo

In a shell script, you must activate history and provide a location where history can be saved:

HISTFILE=/tmp/history.log
set -o histexpand
set -e

## some code here

## clean up
rm /tmp/history.log

An advantage of leaving histexpand deactivated is that you lose the verbosity of the results of history actions. For instance, with histexpand activated, the first thing returned to your shell after a history command is the expanded version of what's being run:

$ set -o histexpand
$ echo foo
foo
$ !!
echo foo
foo

With histexpand toggled off, your history command is longer, but the results are more terse:

$ set +o histexpand
$ echo foo
foo
$ !!
foo

Whether you find a use for history in a shell script is up to you, but history modifiers have some interesting shortcuts that make them particularly convenient in some cases.

History modifiers

Modifiers to history selections are designated by a colon (:) followed by one modifier character. Some modifiers may be combined with others.

Filename suffix

To drop the suffix of an argument, use the r modifier.
This is similar to basename.

$ ls -lgGh aLongFileNameWith-a_complex-syntax-1.tgz
-rw-r--r-- 1 0 Mar  9 11:23 aFileNameWith-a_complex-syntax-1.rev.tgz

$ echo !!:2:r
echo aLongFileNameWith-a_complex-syntax-1
aLongFileNameWith-a_complex-syntax-1

The r modifier isn't super-intelligent. It just finds the final dot in a filename and chops off the suffix, which sometimes may not match what you think of as the file suffix, so in some ways, basename offers greater flexibility.

$ set +o histexpand
$ ls -lgGh aLongFileNameWith-a_complex-syntax-1.tar.gz
$ echo !!:2:r
aLongFileNameWith-a_complex-syntax-1.tar

The opposite result can be attained with the e modifier:

$ set +o histexpand
$ echo aLongFileNameWith-a_complex-syntax-1.tgz
aLongFileNameWith-a_complex-syntax-1.tgz
$ echo !!:2:e
.tgz

Filenames

A simple version of dirname is available with the h modifier:

$ set +o histexpand
$ echo src/spacer.sh
src/spacer.sh
$ echo !!^:h
src

And the other side of that expression is available with the t modifier:

$ set +o histexpand
$ echo src/spacer.sh
src/spacer.sh
$ echo !!^:t
spacer.sh

String substitution

Possibly the most broadly useful modifier of history is string substitution. For instance, any admin of a mixed environment has typed this into a Red Hat Enterprise Linux box at some point:

$ sudo apt install tmux

Such a mistake is easy to fix with string substitution:

$ ^apt^dnf
sudo dnf install tmux

You can even add on some options:

$ ^apt^dnf -y
sudo dnf -y install tmux

This history syntax is shorthand for the s modifier. The full syntax is, for example, !!:s/apt/dnf and therefore can be useful for items several lines back in your history. Assume you issued a useful command three commands back, and want to run a similar command now:

$ !-3:s/vi/emacs/
sudo dnf install -y emacs

Like sed, this substitution by default only occurs once, but you can make it global (in relation to the line you're recalling) with the g modifier:

$ pip update pip
[...]
$ !!:gs:/pip/pip3/
pip3 update pip3

Safety first: how to perform a history dry-run

Pressing Return after a modified history line can sometimes be stressful, especially when the line you want to re-run is nestled between particularly dangerous commands or affects important files. If you want to verify the command you're about to modify and run, you can use the p modifier at the tail end of a history modification.

$ !-32:0-:s/mv/trash/:p
trash important.txt status.txt pay.txt

The command you see as a result is the expanded item from history, but it hasn't been run yet. You can run it manually, by pressing the Up arrow or Ctrl+P, now that it's been resolved for you. Using verification (p) is generally a good idea if you're new to using the history command in production.

Wrapping up

Your Bash history can be useful for many things, whether it's looking back at what you've been doing all day, or just making your work more efficient by reusing chunks of information you've already worked to figure out. The more you practice with history modifiers, the more useful they become. Allow yourself to (carefully) stumble through some as you learn the syntax, and soon you'll be recalling commands and arguments with fewer key-presses than ever before. 

[ Want to try out Red Hat Enterprise Linux? Download it now for free. ]

Topics:   Bash   Linux  
Author’s photo

Seth Kenlon

Seth Kenlon is a UNIX geek and free software enthusiast. More about me

Try Red Hat Enterprise Linux

Download it at no charge from the Red Hat Developer program.