A way to specify a command to run if the previous fails - linux

Is it possible to trap error (unknown command) from the CLI, and do something in the case an error occured ?
To be more precise, I search a way to do something like this:
if [ previousCommandFails ] ; then
echo lastCommand >> somewhere.txt
fi
Echo is just an example to say that I need to access this lastCommand.
I want it to be a default behaviour in my computer, so the code must be placed somewhere like ~/.bashrc.

You can try the following solution. I don't guarantee that it's a good solution but it may help with your case.
Create a small script which can test the previous command i.e. test.sh with content:
if [ $? -ne 0 ]
then
history 1 >> /path/to/failed_commands.txt
fi
Then set this variable:
PROMPT_COMMAND+="source /path/to/test.sh"
PROMPT_COMMAND If set, the value is executed as a command prior to
issuing each primary prompt.

It depends on what you call fail. If it is just returning a non 0 value, I am afraid that you have to explicitely test it after each command, or use a specialized shell (*).
But trap can be used to execute a specific command when a signal is received:
trap action signal
If this is not enough, you will have to get the source of a shell (posix shell or bash) and tweak it for meet you needs...

Related

Cross-platform method to detect whether /dev/tty is available & functional

I have a bash script from which I want to access /dev/tty, but only when it's available.
When it's not available (in my case: when running my script in GitHub Actions) then when I try to access it I get /dev/tty: No such device or address, and I'm trying to detect that in advance to avoid the error and provide fallback behaviour instead.
To do so I need a bash test that can detect cleanly this case, and which will work reliably across platforms (i.e. not using the tty command, which has issues on Mac).
I'm currently using [[ -e "/dev/tty" ]] which doesn't work - it appears to return true even on GitHub Actions, where it seems that /dev/tty exists but accessing it will fail. What should I use instead?
After testing lots of promising but not quite perfect suggestions (see the other answers), I think I've found my own solution that does exactly fit my needs:
if sh -c ": >/dev/tty" >/dev/null 2>/dev/null; then
# /dev/tty is available and usable
else
# /dev/tty is not available
fi
To explain:
: >/dev/tty does nothing (using the : bash built-in) and outputs the nothing to /dev/tty, thereby checking that it exists & it's writable, but not actually producing any visible output. If this succeeds, we're good.
If we do that at the top level without a /dev/tty, bash itself produces a noisy error in our output, complaining about /dev/tty being unusable. This can't be redirected and silenced because it comes from bash itself, not the : command.
Wrapping that with sh -c "..." >/dev/null 2>/dev/null runs the test in a bash subshell, with stdout/stderr removed, and so silences all errors & warnings while still returning the overall exit code.
Suggestions for further improvements welcome. For reference, I'm testing this with setsid <command>, which seems to be a good simulation of the TTY-less environment I'm having trouble with.
Try this approach :
if test "$(ps -p "$$" -o tty=)" = "?"; then
echo "/dev/tty is not available."
else
echo "/dev/tty is available."
fi
Instead of spawning a new shell process to test if /dev/tty can really be opened for writing (test -w lies, you know?), you can try to redirect stdout to /dev/tty from a subshell like so:
if (exec < /dev/tty) ; then
# /dev/tty is available
else
# no tty is available
fi
This is POSIX syntax and should work in any shell.
It seems that adapting this answer from this question on ServerFault (entitled How can I check in bash if a shell is running in interactive mode?, which is close to your question albeit not an exact duplicate) could be a solution for your use case.
So, could you try writing either:
[ -t 0 ] && [ -t 1 ] && echo your code
or [ -t 0 ] && echo your code ?
For completeness, here is one link documenting this POSIX flag -t, which is thus portable:
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
-t file_descriptor
True if file descriptor number file_descriptor is open and is associated with a terminal.
False if file_descriptor is not a valid file descriptor number, or if file descriptor number file_descriptor is not open, or if it is open but is not associated with a terminal.
Furthermore, if you use bash (not just a POSIX-compliant shell), you might want to combine this idea with the special 255 file descriptor number: [ -t 255 ].
Source: On Unix&Linux-SE,
That 255 file descriptor is an open handle to the controlling tty and is only used when bash is run in interactive mode.
[…]
− In Bash, what is file descriptor 255 for, can I use it? (by #mosvy)
Beyond the other answers mentioned in this thread (and as an alternative to the other idea involving $-, which did not seem to work for you), what about this other idea mentioned in the bash manual?
if [ -z "$PS1" ]; then
echo This shell is not interactive
else
echo This shell is interactive
fi

Bash Script - How take a result of command and use it

I would like to know how to do this, if for example, when executing a command in a script, retrieve the message linked to this command.
For example "AZERTY already exists, please check the error" is the message that comes out after a command (wrong), how do I so that if my BASH script sees this info, tell it to stop the script?
You almost certainly should not check the error message. Instead just do:
cmd || exit
If cmd fails and writes the message "AZERTY already exists, please check the error" to stderr, then that message will appear and the script will exit with whatever non-zero value cmd exited with. Some commands return non-zero values that have meaning, and you may want to suppress that (for consistent return values from your script) or change that with something like:
cmd || exit 1
On the other hand, if cmd is poorly written and returns a zero value when it "fails" (I'm putting that in quotes since a reasonable definition of "fail" is "return non-zero"), then that is a bug in cmd which should be fixed.
I recommend using strict mode in all your bash scripts. http://redsymbol.net/articles/unofficial-bash-strict-mode/
Basically put this at the top
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
The option you specifically asked about is -e
The set -e option instructs bash to immediately exit if any command [1] has a non-zero exit status. You wouldn't want to set this for your command-line shell, but in a script it's massively helpful. In all widely used general-purpose programming languages, an unhandled runtime error - whether that's a thrown exception in Java, or a segmentation fault in C, or a syntax error in Python - immediately halts execution of the program; subsequent lines are not executed.
if ./command | grep -q 'AZERTY already exists, please check the error'; then
echo "Error found, exiting"
exit 1
fi

Don't update watch output unless the command succeeds

Is there a way to get watch to update the screen when the command succeeds? I have a command that infrequently succeeds, and I want it to show the last successful output.
Is there a way without a helper program?
watch does not allow to conditionally show the output of a command, but you can start your command from script and show its output depending on the termination status. Use something like this:
#!/bin/bash
cmd > /tmp/cmd_out
if [ $? -eq 0 ]; then
cat /tmp/cmd_out
fi
Of course, your command should return proper exit status (not just 0 in any case) or this method will not work.

Concurrency with shell scripts in failure-prone environments

Good morning all,
I am trying to implement concurrency in a very specific environment, and keep getting stuck. Maybe you can help me.
this is the situation:
-I have N nodes that can read/write in a shared folder.
-I want to execute an application in one of them. this can be anything, like a shell script, an installed code, or whatever.
-To do so, I have to send the same command to all of them. The first one should start the execution, and the rest should see that somebody else is running the desired application and exit.
-The execution of the application can be killed at any time. This is important because does not allow relying on any cleaning step after the execution.
-if the application gets killed, the user may want to execute it again. He would then send the very same command.
My current approach is to create a shell script that wraps the command to be executed. This could also be implemented in C. Not python or other languages, to avoid library dependencies.
#!/bin/sh
# (folder structure simplified for legibility)
mutex(){
lockdir=".lock"
firstTask=1 #false
if mkdir "$lockdir" &> /dev/null
then
controlFile="controlFile"
#if this is the first node, start coordinator
if [ ! -f $controlFile ]; then
firstTask=0 #true
#tell the rest of nodes that I am in control
echo "some info" > $controlFile
fi
# remove control File when script finishes
trap 'rm $controlFile' EXIT
fi
return $firstTask
}
#The basic idea is that a task executes the desire command, stated as arguments to this script. The rest do nothing
if ! mutex ;
then
exit 0
fi
#I am the first node and the only one reaching this, so I execute whatever
$#
If there are no failures, this wrapper works great. The problem is that, if the script is killed before the execution, the trap is not executed and the control file is not removed. Then, when we execute the wrapper again to restart the task, it won't work as every node will think that somebody else is running the application.
A possible solution would be to remove the control script just before the "$#" call, but that it would lead to some race condition.
Any suggestion or idea?
Thanks for your help.
edit: edited with correct solution as future reference
Your trap syntax looks wrong: According to POSIX, it should be:
trap [action condition ...]
e.g.:
trap 'rm $controlFile' HUP INT TERM
trap 'rm $controlFile' 1 2 15
Note that $controlFile will not be expanded until the trap is executed if you use single quotes.

How to handle error/exception in shell script?

Below is my script that I am executing in the bash. And it works fine.
fileexist=0
for i in $( ls /data/read-only/clv/daily/Finished-HADOOP_EXPORT_&processDate#.done); do
mv /data/read-only/clv/daily/Finished-HADOOP_EXPORT_&processDate#.done /data/read-only/clv/daily/archieve-wip/
fileexist=1
done
Problem Statement:-
In my above shell script which has to be run daily using cron job, I don't have any error/exception handling mechanism. Suppose if anything gets wrong then I don't know what's has happened?
As after the above script is executed, there are some other scripts that will be dependent on the data provided by above script, so I always get's complaint from the other people who are depending on my script data that something wrong has happened.
So is there any way I can get notified if anything wrong has happened in my script? Suppose if the cluster is having some maintenance and at that time I am running my script, so definitely it will be failing for sure, so can I be notified if my above scripts failed, so that I will be sure something wrong has happened.
Hope my question is clear enough.
Any thoughts will be appreciated.
You can check for the exit status of each command, as freetx answered, but this is manual error checking rather than exception handling. The standard way to get the equivalent of exception handling in sh is to start the script with set -e. That tells sh to exit with a non-zero status as soon as any executed command fails (i.e. exits with a non-zero exit status).
If it is intended for some command in such a script to (possibly) fail, you can use the construct COMMAND || true, which will force a zero exit status for that expression. For example:
#!/bin/sh
# if any of the following fails, the script fails
set -e
mkdir -p destdir/1/2
mv foo destdir/1/2
touch /done || true # allowed to fail
Another way to ensure that you are notified when things go wrong in a script invoked by cron is to adhere to the Unix convention of printing nothing unless an error ocurred. Successful runs will then pass without notice, and unsuccessful runs will cause the cron daemon to notify you of the error via email. Note that local mail delivery must be correctly configured on your system for this to work.
Its customary for every unix command line utility to return 0 upon success and non-zero on failure. Therefore you can use the $? pattern to display the last return value and handle things accordingly.
For instance:
> ls
> file1 file2
> echo $?
> 0
> ls file.no.exist
> echo $?
> 1
Therefore, you can use this as rudimentary error detection to see if something goes wrong. So the normal approach would be
some_command
if [ $? -gt 0 ]
then
handle_error here
fi
well if other scripts are on the same machine, then you could do a pgrep in other scripts for this script if found to sleep for a while and try other scripts later rechecking process is gone.
If script is on another machine or even local the other method is to produce a temp file on remote machine accessible via a running http browser that other scripts can check status i.e. running or complete
You could also either wrap script around another that looks for these errors and emails you if it finds it if not sends result as per normal to who ever
go=0;
function check_running() {
running=`pgrep -f your_script.sh|wc -l `
if [ $running -gt 1 ]; then
echo "already running $0 -- instances found $running ";
go=1;
}
check_running;
if [ $go -ge 1 ];then
execute your other script
else
sleep 120;
check_running;
fi

Resources