Bash: How to get a script to rerun itself as a background task? - linux

I'm writing a Bash script that is intended to be used as a daemon. If the user of my script does not pass a --sync option to the script, I want the script to rerun itself as a background task using that option. Here is my code (the last part was stolen from this SO post):
#!/usr/bin/env bash
args=("$#") # capture them here so we can use them if --sync's not passed
async=true
while [ $# -gt 0 ]
do
case "$1" in
--sync)
async=false
;;
# other options
esac
shift
done
# if --sync isn't passed, rerun the script as a background task
$async && exec nohup "${BASH_SOURCE[0]}" --sync "${args[#]}" 0<&- &> /dev/null &
For some reason, it doesn't seem to be working. When I do bash -x myscript (which helps debug the script), it seems that it just keeps on going even if $async is true, which I didn't think would happen since exec normally stops execution.
Likewise, if I run this command from my terminal:
exec nohup true 0<&- &> /dev/null &
it also fails to exit the shell, despite the use of exec. Why is this, and what can I do to work around it? (Bonus points: Is there any way to do this without creating a subshell?)
Thanks.

The & is being applied to the exec command itself, so exec foo & forks a new asynchronous subshell (or equivalent thereto, see below). That subshell immediately replaces itself with foo. If you want the parent (that is, your script) to terminate as well, you'll need to do so explicitly with an exit command.
The exec is probably not buying you anything here. Bash is clever enough to not actually start a subshell for a simple backgrounded command. So it should be sufficient to do:
if $async; then
nohup "${BASH_SOURCE[0]}" --sync "${args[#]}" 0<&- &> /dev/null &
exit 0
fi
I don't know of a way to do this without a subshell. But when you write shell scripts, you get subshells. Personally I'd just use a simple variable and test it with something like if [[ $async ]]; instead of executing true or false, but since those are also bash builtins, it's pretty well equivalent. In other shells they might run in subshells.
Now that I think of it, since you're reprocessing all the options in async execution anyway, you might as well just fork and exit from within the case statement, so you don't need the second check at all:
case "$1" in
--sync)
nohup "${BASH_SOURCE[0]}" --sync "${args[#]}" 0<&- &> /dev/null &
exit 0
;;

I disagree with rici's answer because the question clearly states background-ing is only wanted when --sync is NOT passed into the script. What was shown appears to be an infinite loop, and isn't checking all the parameters passed. I believe the original code was fine, except for the final "async && exec ...". The following replacement for that line should work:
if [ "$async" = true ]; then
nohup "${BASH_SOURCE[0]}" --sync "${args[#]}" 0<&- &> /dev/null &
exit 0
fi
followed by what your code is supposed to do when --sync is passed.

Related

nohup append the executed command at the top of the output file

Let's say that we invoke the nohup in the following way:
nohup foo.py -n 20 2>&1 &
This will write the output to the nohup.out.
How could we achieve to have the whole command nohup foo.py -n 20 2>&1 & sitting at the top of the nohup.out (or any other specified output file) after which the regular output of the executed command will be written to that file?
The reason for this is for purely debugging purpose as there will be thousands of commands like this executed and very often some of them will crash due to various reasons. It's like a basic report kept in a file with the executed command written at the top followed by the output of the executed command.
A straightforward alternative would be something like:
myNohup() {
(
set +m # disable job control
[[ -t 0 ]] && exec </dev/null # redirect stdin away from tty
[[ -t 1 ]] && exec >nohup.out # redirect stdout away from tty
[[ -t 2 ]] && exec 2>&1 # redirect stderr away from tty
set -x # enable trace logging of all commands run
"$#" # run our arguments as a command
) & disown -h "$!" # do not forward any HUP signal to the child process
}
To define a command we can test this with:
waitAndWrite() { sleep 5; echo "finished"; }
...and run:
myNohup waitAndWrite
...will return immediately and, after five seconds, leave the following in nohup.out:
+ waitAndWrite
+ sleep 5
+ echo finished
finished
If you only want to write the exact command run without the side effects of xtrace, replace the set -x with (assuming bash 5.0 or newer) printf '%s\n' "${*#Q}".
For older versions of bash, you might instead consider printf '%q ' "$#"; printf '\n'.
This does differ a little from what the question proposes:
Redirections and other shell directives are not logged by set -x. When you run nohup foo 2>&1 &, the 2>&1 is not passed as an argument to nohup; instead, it's something the shell does before nohup is started. Similarly, the & is not an argument but an instruction to the shell not to wait() for the subprocess to finish before going on to future commands.

have arbitrary executable inherit errexit, if script is bash

I have a folder of executable scripts, and some of them have Python shebangs, while others have Bash shebangs, etc. We have a cron job that runs this folder of scripts nightly, and the hope is that any error in any script will exit the job.
The scripts are run with something like: for FILE in $FILES; do ./$FILE; done
The scripts are provided by various people, and while the Python scripts always exit after an error, sometimes developers forget to add set -e in their Bash scripts.
I could have the for-loop use bash -e, but then I need to detect whether the current script is Bash/Python/etc.
I could set -e from the parent script, and then source scripts, but I still need to know which language each script is in, and I'd prefer them to run as subshells so script contributors don't have to worry about messing up the parent.
greping the shebangs is a short tweak, but knowing the flexibility of Bash, I'd be surprised if there weren't a way to "export" an option that affected all child scripts, in the same way you can export a variable. And, there have been many cases in general where I've forgotten "set -e", so it could be nice to know more options for fool-proofing things.
I see some options for inheriting -e for subshells involved in command substitution, but not in general.
Disclaimer: Never, ever do this! It's a huge disservice to everyone involved. You will introduce failures both in scripts with meticulous error handling, and in scripts without it.
Anyways, no one likes being told "don't do that" on StackOverflow, so my suggestion would be to identify scripts and invoke them with their shebang string plus -e:
for f in ./*
do
# Determine if the script is a shell script
if [[ $(file -i "$f") == *text/x-shellscript* ]]
then
# Read the first line
read -r shebang < "$f"
# The script shouldn't have been identified as a shell script without
# a shebang, but check anyways
if [[ $shebang != "#!"* ]]
then
echo "No idea what $f is" >&2
continue
fi
# Strip off the #! and run it with -e and the file
shebang=${shebang#??}
$shebang -e "$f"
else
# It's some other kind of executable, just run it directly
"$f"
fi
done
Here's a script with correct error handling that now stops working:
#!/bin/bash
my-service start
ret=$?
if [ $ret -eq 127 ]
then
# Use legacy invocation instead
start-my-service
ret=$?
fi
exit "$ret"
Here's a script without error handling that now stops working:
#!/bin/sh
err=$(grep "ERROR" file.log)
if [ -z "$err" ]
then
echo "Run was successful"
exit 0
else
echo "Run failed: $err"
exit 1
fi

Re-installing Linux O.S. and then running bunch of commands in a .sh script , how to stop the script if something fails?

If i copy and paste all the commands into the terminal..
some do not even go through.
so the solution is perhaps to turn the file into an executable file
and then execute it.
but what if some commands fail.
the script keeps on executing the other commands.
obviously there is no solution to this right ?
The easiest way to do this is to use the -e option in your shell. For example:
#!/bin/sh -e
command1
command2
In this script, if command1 fails, then the script as a whole will fail at that point without running any further commands.
You can check the error code from commands you run
#!/bin/bash
function test {
"$#"
status=$?
if [ $status -ne 0 ]; then
echo "error with $1"
exit 255
fi
return $status
}
test ls
test ps -ef
test not_a_command
taken from here for more information Checking Bash exit status of several commands efficiently
#Terminal, you were almost there.
If you just stick && on the end of each command, then execution will stop with the first failure (ie. the first command that returns a non-zero exit code).
Example:
#!/bin/sh
true &&
echo 'got here' &&
echo 'got here too' &&
false &&
echo 'also got here'
produces the output
got here
got here too
(Actually, I thought it would also require line-continuation markers too: && \, but a quick test showed otherwise.)
Note: All of the above assumes that your shell is bash; I can't speak for other shells.

How to run bash script when a program open in liunx

Is there a way to execute bash script when I click a program like NetBeans or DropBox on Ubuntu
and execute a bash script when exit it
My idea create bash script on cronjob #reboot check every second if the program exist in the current processes
#!/bin/bash
NameOfprogram="NetBeans"
while [[ true ]]; do
countOfprocess=$(ps -ef |grep $NameOfprogram | wc -l)
if [[ $countOfprocess -gt 1 ]]; then
#execute bash
fi
sleep 1
done
But I think this idea not the best ,Is there a better way to achieve it?
A better approach is to wrap the executable in a script. That means you put a script with the name of the program in your path (probably $HOME/bin) and Linux will use that instead of the real executable.
Now you can execute the real program using:
/usr/bin/NetBeans "$#"
So to execute the real executable, you just put the absolute path in front of the name. The odd "$#" too pass on any arguments someone might have given the script.
Put a loop around this:
while [[ true ]]; do
/usr/bin/NetBeans "$#"
done
But there is a problem: You can't exit this program anymore. As soon as you try, it restarts. So if you just want a restart when it crashes:
while [[ true ]]; do
/usr/bin/NetBeans "$#" && exit 0
done
As long as the program exits because of an error, it will be restarted. If you quit it, the script will stop.

How do I know if a bash script is running with nohup?

I have a script to process records in some files, it usually takes 1-2 hours. When it's running, it prints a progress of number of records processed.
Now, what I want to do is: when it's running with nohup, I don't want it to print the progress; it should print progress only when it run manually.
My question is how do I know if a bash script is running with nohup?
Suppose the command is nohup myscript.sh &. In the script, how do I get the nohup from command line? I tried to use $0, but it gives myscript.sh.
Checking for file redirections is not robust, since nohup can be (and often is) used in scripts where stdin, stdout and/or stderr are already explicitly redirected.
Aside from these redirections, the only thing nohup does is ignore the SIGHUP signal (thanks to Blrfl for the link.)
So, really what we're asking for is a way to detect if SIGHUP is being ignored. In linux, the signal ignore mask is exposed in /proc/$PID/status, in the least-significant bit of the SigIgn hex string.
Provided we know the pid of the bash script we want to check, we can use egrep. Here I see if the current shell is ignoring SIGHUP (i.e. is "nohuppy"):
$ egrep -q "SigIgn:\s.{15}[13579bdf]" /proc/$$/status && echo nohuppy || echo normal
normal
$ nohup bash -c 'egrep -q "SigIgn:\s.{15}[13579bdf]" /proc/$$/status && echo nohuppy || echo normal'; cat nohup.out
nohup: ignoring input and appending output to `nohup.out'
nohuppy
You could check if STDOUT is associated with a terminal:
[ -t 1 ]
You can either check if the parent pid is 1:
if [ $PPID -eq 1 ] ; then
echo "Parent pid=1 (runing via nohup)"
else
echo "Parent pid<>1 (NOT running via nohup)"
fi
or if your script ignores the SIGHUP signal (see https://stackoverflow.com/a/35638712/1011025):
if egrep -q "SigIgn:\s.{15}[13579bdf]" /proc/$$/status ; then
echo "Ignores SIGHUP (runing via nohup)"
else
echo "Doesn't ignore SIGHUP (NOT running via nohup)"
fi
One way, but not really portable would be to do a readlink on /proc/$$/fd/1 and test if it ends with nohup.out.
Assuming you are on the pts0 terminal (not really relevant, just to be able to show the result):
#!/bin/bash
if [[ $(readlink /proc/$$/fd/1) =~ nohup.out$ ]]; then
echo "Running under hup" >> /dev/pts/0
fi
But the traditional approach to such problems is to test if the output is a terminal:
[ -t 1 ]
Thank you guys. Check STDOUT is a good idea. I just find another way to do it. That is to test tty.
test tty -s check its return code. If it's 0 , then it's running on a terminal; if it's 1 then it's running with nohup.

Resources