Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I would like the shell to warn me for potential errors I'm about to make.

How can I prevent a command cmd2 from being executed after cmd1.

For example:

$ tar tf file.tar
$ rm !$

Here, I just forgot that I listed the file instead of extracting its content.

I'm looking for a generic solution, that makes a choice based on the name and return value of the previous command, and on the current command.

share|improve this question
    
Experience. Commit that mistake a few times... you'll stop doing it soon, and then you'll commit a different oops. Wisdom comes from Experience, Experience comes from failure. –  lornix Jul 10 '12 at 9:46
    
@lornix Thanks for your advice; I'm still looking for a technical solution. –  Antoine Lecaille Jul 10 '12 at 10:10
    
Adding the alias rm='rm -i' command (see @uther below) to your shell startup scripts is the best way. The computer is not clairvoyant (yet!), and it just follow instructions. The best advice anyone could give is slow down and pay attention to what you're doing. Not trying to be snotty or anything. We've ALL made those mistakes, and likely spent hours cleaning up (or recreating) the mess. I wouldn't want a computer that held my hand anyways. I'm in charge, and if I do stupid things, I'm responsible. (did you know you can delete the ENTIRE \Windows subdir while it's running?) –  lornix Jul 10 '12 at 10:34

4 Answers 4

up vote 3 down vote accepted

You're asking for a lot with a general answer, but it should be possible to do it relatively easily to some extent.

However, bash has at least two general mechanisms that can help solve that. You can set the variable PROMPT_COMMAND and this will be executed after anything you run in your prompt:

$ PROMPT_COMMAND='echo BOFH says hi!'
BOFH says hi!
$ man man
BOFH says hi!
$

We can use this to save the last entered command and then reread it, so we can store it in memory for further use (history expansion does not work in this case):

$ PROMPT_COMMAND='history -a; tail -n1 ~/.bash_history'
PROMPT_COMMAND='history -a; tail -n1 ~/.bash_history'
$ wc < test.sh | grep .
 5 11 63
wc < test.sh | grep .

Now comes the logic part, which is completely up to you. Let's use just very simplistic logic to prevent the example in the question. The thing to remember is, that bash functions still work in there, so you don't have to cram everything on a single line, in a single variable.

$ # define the logic check
$ checkSanity() { grep -q "tar  *[tf][[:alpha:]]*[tf] " <<< "$@" && echo "be careful, don't delete this"; }
checkSanity() { grep -q "tar  *[tf][[:alpha:]]*[tf] " <<< "$@" && echo "be careful, don't delete this"; }
$ # feed it the last command
$ PROMPT_COMMAND='history -a; last=$(tail -n1 ~/.bash_history); checkSanity "$last"'
$ # simple test
$ tar tf test.sh 2>/dev/null 
be careful, don't delete this
$ tar xf test.sh 2>/dev/null 
$

Of course, this approach is useful to prevent PEBKAC (especially if you add sleep 3 at the end), but it can't break the next command on its own.

If that's really what you want, trap the DEBUG signal (eg. trap 'echo "I am not deterministic, haha"' DEBUG), since it runs beforehand. Be careful with combining these two approaches, since the output/action will be doubled:

$ df
/.../
$ trap 'tail -n1 ~/.bash_history' DEBUG
df
df
trap 'tail -n1 ~/.bash_history' DEBUG
trap 'tail -n1 ~/.bash_history' DEBUG

To make the trap break a command, you'll have to enable extdebug (shopt -s extdebug). You also don't need to save and reread history constantly, but can inspect $BASH_COMMAND to get the command about to be run. Then you just need to make sure the logic checker return 1 when it detects something bad and 0 otherwise.

          extdebug
                  If set, behavior intended for use by debuggers is enabled:
    /.../
                  2.     If  the command run by the DEBUG trap returns a non-zero value, the next
                         command is skipped and not executed.


$ checkSanity() { if grep -q "tar  *[tf][[:alpha:]]*[tf] " <<< "$1"; then  echo "be careful, don't delete this"; return 1; fi; }
$ trap 'checkSanity "$BASH_COMMAND"' DEBUG
$ # simple test
$ tar tf test.sh 2>/dev/null 
be careful, don't delete this
$ tar xf test.sh 2>/dev/null 
$
share|improve this answer

In bash you can set shopt -s histverify which would stop the history from being expanded and executed in one step. Here the above $ rm !$ would expand to $ rm file.tar on the first press of Enter, allowing you to check or edit before pressing Enter again to execute.
A more general solution would veer into "Do What I Mean" territory, requiring a program to second-guess your intentions.

share|improve this answer
    
In this case, it would not be helpful because I almost always expand the !$ directly with TAB; it doesn't remind me that this pattern is probably dangerous. –  Antoine Lecaille Jul 8 '12 at 20:59
1  
This is the only reasonable answer. The brilliant part of the command line is it does what you say without faffing. –  Alex Chamberlain Jul 12 '12 at 12:00

In this case, you would benefit from error warnings from the rm command.

In your shell initialization scripts (assuming bash, so ~/.bashrc), you could alias rm to either -i or -I.

alias rm="rm -i"

From man 1 rm

   -i     prompt before every removal

   -I     prompt once before removing more than three files, or when
          removing recursively.  Less intrusive than -i, while still
          giving protection against most mistakes

The more generic question of, "How do I protect my system from myself" has been discussed before. In a nutshell, pay attention to what you are doing, and only run commands as root when necessary.

share|improve this answer
    
rm was just an example. I'm interested in a more generic solution. –  Antoine Lecaille Jul 8 '12 at 14:35
    
@Antoine See my edit. –  uther Jul 8 '12 at 14:47

In your specific case, I would do:

tar tf file.tar
if [ -f file ]; then   # Assuming you meant to extract a file
  rm -f file.tar
else
  echo "file is missing. Command failed."
fi

If you want to check for errors and only run the next command if the first succeeded

some_command && another_command

Although in your specific case, the second command would still run as it did not error out even though it didn't do what you wanted.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.