have arbitrary executable inherit errexit, if script is bash - linux

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

Related

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

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.

Bash - Locate Script and Execute with Options

I did a good bit of searching and testing on my own, but I can't seem to find the best way to achieve this goal. I would like to have a bash one liner that will find a script on the machine, execute the script and have the ability to add switches or needed information in my case to execute the script successfully.
To get a little more specific, I am in Kali Linux and I run the locate command like so:
locate pattern_create
which returns:
/usr/share/metasploit-framework/tools/pattern_create.rb
So I thought about piping this into xargs to run the script like so:
locate pattern_create | xargs ruby
but of course I could not specify the options I need to that would run the script successfully, which would be:
ruby /usr/share/metasploit-framework/tools/pattern_create.rb 2700
I came up with a work around, but I feel that it's somewhat sloppy and this could be done easier, and that's where I hope I could get any input/feedback.
I found out I can run:
pattern_create=$(locate pattern_create) && ruby $pattern_create 2700
to get exactly what I need, but then I am dealing with environment variables which I would not want a bunch of when doing this often. I was hoping to figure this out with xargs or maybe and even cleaner way if possible. I know this can be done easily with find -exec, but that won't work in my case where I don't where the script is stored.
Any help would be awesome, I appreciate everyone's time. Thank you.
You can do:
ruby $(locate pattern_create)
But be aware that if there are multiple lines returned by locate, then this may not do what you wanted.
This is a dangerous thing to do as you do not know what locate will return and you could end up executing arbitrary scripts. I suggest that you use an intermediate script which will protect against the unexpected, such as finding no scripts or finding more than one.
#! /bin/sh
#
if [ $# -eq 0 ]
then
echo >&2 "Usage: $0 script arguments"
exit -1
fi
script=$(locate $1)
numfound=$(locate $1 | wc -l)
shift
if [ $numfound -eq 1 ]
then
# Only run the script if exactly one match is found
ruby $script $*
elif [ $numfound -eq 0 ]
then
echo "No matching scripts found" >&2
exit -1
else
echo "Too many scripts found - $script" >&2
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.

Equivalent of %~dp0 (retrieving source file name) in sh

I'm converting some Windows batch files to Unix scripts using sh. I have problems because some behavior is dependent on the %~dp0 macro available in batch files.
Is there any sh equivalent to this? Any way to obtain the directory where the executing script lives?
The problem (for you) with $0 is that it is set to whatever command line was use to invoke the script, not the location of the script itself. This can make it difficult to get the full path of the directory containing the script which is what you get from %~dp0 in a Windows batch file.
For example, consider the following script, dollar.sh:
#!/bin/bash
echo $0
If you'd run it you'll get the following output:
# ./dollar.sh
./dollar.sh
# /tmp/dollar.sh
/tmp/dollar.sh
So to get the fully qualified directory name of a script I do the following:
cd `dirname $0`
SCRIPTDIR=`pwd`
cd -
This works as follows:
cd to the directory of the script, using either the relative or absolute path from the command line.
Gets the absolute path of this directory and stores it in SCRIPTDIR.
Goes back to the previous working directory using "cd -".
Yes, you can! It's in the arguments. :)
look at
${0}
combining that with
{$var%Pattern}
Remove from $var the shortest part of $Pattern that matches the back end of $var.
what you want is just
${0%/*}
I recommend the Advanced Bash Scripting Guide
(that is also where the above information is from).
Especiall the part on Converting DOS Batch Files to Shell Scripts
might be useful for you. :)
If I have misunderstood you, you may have to combine that with the output of "pwd". Since it only contains the path the script was called with!
Try the following script:
#!/bin/bash
called_path=${0%/*}
stripped=${called_path#[^/]*}
real_path=`pwd`$stripped
echo "called path: $called_path"
echo "stripped: $stripped"
echo "pwd: `pwd`"
echo "real path: $real_path
This needs some work though.
I recommend using Dave Webb's approach unless that is impossible.
In bash under linux you can get the full path to the command with:
readlink /proc/$$/fd/255
and to get the directory:
dir=$(dirname $(readlink /proc/$$/fd/255))
It's ugly, but I have yet to find another way.
I was trying to find the path for a script that was being sourced from another script. And that was my problem, when sourcing the text just gets copied into the calling script, so $0 always returns information about the calling script.
I found a workaround, that only works in bash, $BASH_SOURCE always has the info about the script in which it is referred to. Even if the script is sourced it is correctly resolved to the original (sourced) script.
The correct answer is this one:
How do I determine the location of my script? I want to read some config files from the same place.
It is important to realize that in the general case, this problem has no solution. Any approach you might have heard of, and any approach that will be detailed below, has flaws and will only work in specific cases. First and foremost, try to avoid the problem entirely by not depending on the location of your script!
Before we dive into solutions, let's clear up some misunderstandings. It is important to understand that:
Your script does not actually have a location! Wherever the bytes end up coming from, there is no "one canonical path" for it. Never.
$0 is NOT the answer to your problem. If you think it is, you can either stop reading and write more bugs, or you can accept this and read on.
...
Try this:
${0%/*}
This should work for bash shell:
dir=$(dirname $(readlink -m $BASH_SOURCE))
Test script:
#!/bin/bash
echo $(dirname $(readlink -m $BASH_SOURCE))
Run test:
$ ./somedir/test.sh
/tmp/somedir
$ source ./somedir/test.sh
/tmp/somedir
$ bash ./somedir/test.sh
/tmp/somedir
$ . ./somedir/test.sh
/tmp/somedir
This is a script can get the shell file real path when executed or sourced.
Tested in bash, zsh, ksh, dash.
BTW: you shall clean the verbose code by yourself.
#!/usr/bin/env bash
echo "---------------- GET SELF PATH ----------------"
echo "NOW \$(pwd) >>> $(pwd)"
ORIGINAL_PWD_GETSELFPATHVAR=$(pwd)
echo "NOW \$0 >>> $0"
echo "NOW \$_ >>> $_"
echo "NOW \${0##*/} >>> ${0##*/}"
if test -n "$BASH"; then
echo "RUNNING IN BASH..."
SH_FILE_RUN_PATH_GETSELFPATHVAR=${BASH_SOURCE[0]}
elif test -n "$ZSH_NAME"; then
echo "RUNNING IN ZSH..."
SH_FILE_RUN_PATH_GETSELFPATHVAR=${(%):-%x}
elif test -n "$KSH_VERSION"; then
echo "RUNNING IN KSH..."
SH_FILE_RUN_PATH_GETSELFPATHVAR=${.sh.file}
else
echo "RUNNING IN DASH OR OTHERS ELSE..."
SH_FILE_RUN_PATH_GETSELFPATHVAR=$(lsof -p $$ -Fn0 | tr -d '\0' | grep "${0##*/}" | tail -1 | sed 's/^[^\/]*//g')
fi
echo "EXECUTING FILE PATH: $SH_FILE_RUN_PATH_GETSELFPATHVAR"
cd "$(dirname "$SH_FILE_RUN_PATH_GETSELFPATHVAR")" || return 1
SH_FILE_RUN_BASENAME_GETSELFPATHVAR=$(basename "$SH_FILE_RUN_PATH_GETSELFPATHVAR")
# Iterate down a (possible) chain of symlinks as lsof of macOS doesn't have -f option.
while [ -L "$SH_FILE_RUN_BASENAME_GETSELFPATHVAR" ]; do
SH_FILE_REAL_PATH_GETSELFPATHVAR=$(readlink "$SH_FILE_RUN_BASENAME_GETSELFPATHVAR")
cd "$(dirname "$SH_FILE_REAL_PATH_GETSELFPATHVAR")" || return 1
SH_FILE_RUN_BASENAME_GETSELFPATHVAR=$(basename "$SH_FILE_REAL_PATH_GETSELFPATHVAR")
done
# Compute the canonicalized name by finding the physical path
# for the directory we're in and appending the target file.
SH_SELF_PATH_DIR_RESULT=$(pwd -P)
SH_FILE_REAL_PATH_GETSELFPATHVAR=$SH_SELF_PATH_DIR_RESULT/$SH_FILE_RUN_BASENAME_GETSELFPATHVAR
echo "EXECUTING REAL PATH: $SH_FILE_REAL_PATH_GETSELFPATHVAR"
echo "EXECUTING FILE DIR: $SH_SELF_PATH_DIR_RESULT"
cd "$ORIGINAL_PWD_GETSELFPATHVAR" || return 1
unset ORIGINAL_PWD_GETSELFPATHVAR
unset SH_FILE_RUN_PATH_GETSELFPATHVAR
unset SH_FILE_RUN_BASENAME_GETSELFPATHVAR
unset SH_FILE_REAL_PATH_GETSELFPATHVAR
echo "---------------- GET SELF PATH ----------------"
# USE $SH_SELF_PATH_DIR_RESULT BEBLOW
I have tried $0 before, namely:
dirname $0
and it just returns "." even when the script is being sourced by another script:
. ../somedir/somescript.sh

Resources