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
$
Upon further testing, I suspect the &
is messing with your results. As you point out, &>/dev/null
is bash syntax, not sh syntax. As a result, sh
is creating a subshell and backgrounding it. Sure, the subshell's echo
creates stderr, but my theory is that:
- cron is not catching the subshell's stderr, and
- the backgrounding of the subshell always completes successfully, thus bypassing your
|| echo ...
.
... causing the cron job to have no output and thus no mail. Based on my reading of the vixie-cron source, it would seem that the job's stderr and stdout would be captured by cron, but it must be getting lost by the subshell.
Test it yourself in a /bin/sh environment (assuming you do not have a file named 'bar' here):
(grep foo bar) &
echo $?
Best Answer
I knew I was grasping at straws, but UNIX never fails!
Here's how I managed it:
Then at the
(gdb)
prompt I ran the command,call write_history("/tmp/foo")
which will write this history to the file/tmp/foo
.I then detach from the process.
And quit
gdb
.And sure enough...
For easy future re-use, I wrote a bash script, automating the process.