Skip to content

Instantly share code, notes, and snippets.

@rmascarenhas
Created November 16, 2012 02:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rmascarenhas/4083353 to your computer and use it in GitHub Desktop.
Save rmascarenhas/4083353 to your computer and use it in GitHub Desktop.
Undoing your last command
# undo: reverts the last command, as long as there is a registered handler
# for it.
#
# Usage:
#
# $ undo
#
# There are no options.
#
# There are by default registered handlers for the following commands:
#
# * cp
# * mv
# * mkdir
#
# To use this function, load this file somewhere in your bash startup script.
# If you have more commands that you think that can be undone, say, the `foo`
# command, just define the `foo_undo` function with the following signature:
#
# foo_undo() {
# $1: the command itself, in this case, `foo`
# $2: the rest of what was passed to the command, which can include
# options (switches) and arguments. If `foo` was invoked as:
# $ foo -x bar
# then $2 would be `-x bar`.
# }
#
# and it will be seamlessly integrated with `undo`.
#
# Helpers
_handler_exists() {
command -v "$1" >/dev/null 2>&1
}
# Default handlers
# cp handler. Take the file from last argument and remove it.
cp_undo() {
local dest=`echo "$2" | awk '{ print $NF }'`
rm -i "$dest"
}
# mv handler. Take the last two arguments and move them back.
mv_undo() {
for argument in $2; do
[[ $argument =~ ^-.*$ ]] && continue
if [[ -z $src ]]; then
local src=$argument
else
local dest=$argument
fi
done
mv -i $dest $src
}
pushd_undo() {
popd
}
rm_undo() {
echo "Can't (easily) restore files :-("
}
# mkdir handler. Removes the created directory
mkdir_undo() {
local dest=`echo "$2" | awk '{ print $NF }'`
rmdir "$dest"
}
undo() {
# Get last command
local last=`history | tail -2 | sed -e 's/ *[[:digit:]]* \(.*\)/\1/; $d'`
local command=`echo $last | cut -d' ' -f1`
local args=`echo $last | cut -d' ' -s -f2-`
command=`basename "$command"`
if _handler_exists "${command}_undo"; then
"${command}_undo" "$command" "$args"
else
echo "No handler for command $command" >&2
fi
}

undo.sh

The main point of the script is to undo your last command. So many times have I mistyped a mv command! With undo, you can go back to the state you were previously:

$ ls
afile             another_file      many_files_here   yet_another_file

$ mv afile bfile

$ # aw man, this was not what I wanted!

$ undo

$ ls
afile             another_file      many_files_here   yet_another_file

Of course, undo doesn't understand all the commands you can ever type. It ships with a few handlers for often-used commands which have a natural fit for being undone.

Namely, there are handlers for cp, mv, mkdir, push and popd.

Custom handlers

If you have more commands that you often type and want to define how to undo it, all you have to do is define a function called command_undo, changing command for the actual command name.

Let's say you want to define that a touch command can be undone by removing the touched file. All you have to do is:

$ touch_undo() {
> rm "$2"
}

$ ls
afile             another_file      many_files_here   yet_another_file

$ touch test_file

$ undo

$ ls
afile             another_file      many_files_here   yet_another_file

Instalation

undo is a shell function, and if you wish to use it, source it in your shell initialization script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment