Chained && and || operators in bash scripting - linux

I want to execute a series of commands in a bash script.
If any fail, the script should echo a line describing which line failed, and then exit the function.
Currently, my script echos the output adequately, but doesn't exit the script (because the echo command returns 0).
I have
COMMAND_A || echo "command A failed" | tee -a $SUMMARY_FILE
COMMAND_B || echo "command B failed" | tee -a $SUMMARY_FILE
COMMAND_C || echo "command C failed" | tee -a $SUMMARY_FILE
but these have no dependence on the previous line - I think enclosing with bash -e would also not help, since then either COMMAND_A failing would exit without the echo, or else the echo would succeed and nothing would exit.
Since the echo will succeed, I can't just add && to each line.
I could perhaps use
{ COMMAND_A && \
{ COMMAND_B && \
{ COMMAND_C || echo "C failed" | tee -a $SUM } \
|| echo "B failed" | tee -a $SUM } \
|| echo "A failed" | tee -a $SUM }
but this seems very clunky.
Is there a better syntax/style/method?
For a concrete example:
cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DCMAKE_BUILD_TYPE=Release .. || \
echo "$(date +%d/%m/%y-%H:%M:%S): cmake failed for $1" | tee -a $SUMFILE
make -j16 || \
echo "$(date +%d/%m/%y-%H:%M:%S): make failed for $1" | tee -a $SUMFILE
sudo make install && \
echo "$(date +%d/%m/%y-%H:%M:%S): Installed $1" | tee -a $SUMFILE || \
echo "$(date +%d/%m/%y-%H:%M:%S): make install failed for $1" | tee -a $SUMFILE
Edit
I can perhaps get some improvement with
COMMAND_A || { echo "A failed" && false; } && \
COMMAND_B || { echo "B failed" && false; } && \
COMMAND_C && echo "C succeeded" || { echo "C failed" && false; }
but this will still print that A, B and C failed if A fails (even though the commands are short-circuited, hence the improvement)

Since the series of commands is executed within a function, the return statement can be used.
If this were not the case, exit may be the appropriate choice.
Consequently, I can use the idea in the question's edit to write:
standardInstall() {
# Should be passed the directory (ie application/library) name as
# an argument.
if [ -z "$1" ]; then
echo "No argument was passed to standardInstall()"
echo "Exiting"
exit 2
fi
pushd $1/build/
cmake -DCMAKE_INSTALL_PREFIX=$KF5 -DCMAKE_BUILD_TYPE=Release .. || \
{ echo "$(date +%d/%m/%y-%H:%M:%S): cmake failed for $1" \
| tee -a $SUMFILE && return 1; } && \
make -j16 || \
{ echo "$(date +%d/%m/%y-%H:%M:%S): make failed for $1" \
| tee -a $SUMFILE && return 1; } && \
sudo make install && \
echo "$(date +%d/%m/%y-%H:%M:%S): Installed $1" | tee -a $SUMFILE || \
{ echo "$(date +%d/%m/%y-%H:%M:%S): make install failed for $1" \
| tee -a $SUMFILE && return 1; }
popd
}

Related

How to check if a file with a certain string in its filename exists in bash?

I want to print out an error message if a file with a "RDR_Config_Summary" in its filename does not exist in the directory. Some example filenames could be er2_FCLS_RDR_Config_Summary.txt or er1_CDMA_RDR_Config_Summary.txt. I am getting a syntax error for the following code:
cd $inputDir/$reviewDir
[[ ! grep "RDR_Config_Summary" ]] && echo -e "\nError: RDR Config Summary file was not found\n" && exit 1
cd $inputDir/$reviewDir
ls | grep "RDR_Config_Summary" > /dev/null 2>&1
[ $? -ne 0 ] && echo -e "\nError: RDR Config Summary file was not found\n" && exit 1
[[ is a command itself; you don't need it to run grep. However, you have nothing to run grep on. Use pathname expansion instead.
cd "$inputDir/$reviewDir"
shopt -s failglob
if ! { : *RDR_ConfigSummary*; } 2> /dev/null; then
echo -e "\nError: RDR Config Summary file was not found\n";
exit 1
fi
shopt -u failglob

avoid executing other lines if my curl call fails for whatever reason

I have below lines in my script and it works fine as of now:
URL="$(hostname -f | grep -q "\.dev\." && echo "$URL_1" || echo "$URL_2")"
FILE="$(hostname -f | grep -q "\.dev\." && echo "file.init.proc" || echo "file.init.test")"
curl --fail -o "$TEMPFILE" "$URL" && if ! grep -q "$TEST_IPD" "$TEMPFILE"; then echo "ipaddress missing in the file" || return 2; else mv -- "$TEMPFILE" "$CONFIG_DIR/$FILE"; rm -f -- "$TEMPFILE"; fi
"line 4- something here"
"line 5- something here"
But earlier there was some problem in my URL and FILE line and because of which my curl line failed and for some reason still line 4 and line 5 got executed and I don't want those lines to be executed.
Let's say for some reason if I am not able to extract URL or FILE variable then if my curl line fails then I don't want line 4 and line 5 to be executed at all. Basically if my curl line fails for whatever reason I don't want line 4 or line 5 to be executed at all.
Let's clean this up a bit.
if hostname -f | grep -qF '.dev.'; then
URL=$URL_1
FILE=file.init.proc
else
URL=$URL_2
FILE=file.init.test
fi
if curl --fail -o "$TEMPFILE" "$URL"; then
if ! grep -q "$TEST_IPD" "$TEMPFILE"; then
echo "ipaddress missing in the file" >&2
return 2
else
mv -- "$TEMPFILE" "$CONFIG_DIR/$FILE" && rm -f -- "$TEMPFILE"
fi
else
"line 4- something here"
"line 5- something here"
fi
--fail just causes curl to exit if something goes wrong; it has no effect on the shell that executed curl. Try to avoid using && and || for anything other than short commands, and never use ... && ... || ... in place of a proper if statement.
There are several ways to stop or continue after a failed command.
Here are some examples:
Include the command as if condition maybe the best way
$ if echo foo; then echo bar; fi
foo
bar
Inspect the exit code to know the result of a given command (the standard is to return a 0 exit code in case of success and a variable code from 1 to 255 in case of error) but as #Charles Duffy said
this is an antipattern because it introduces complexity that wouldn't
exist if you didn't require the exit status to be recorded at all.
More info here
$ echo foo
$ echo $?
0
So you can do something like:
$ echo foo
foo
$ if [[ $? == 0 ]]; then echo bar; fi
bar
Using exit code and arithmetic mode
Testing for success
$ if ! (($?)); then echo bar; fi
bar
Testing for failure
$ if (($?)); then echo bar; fi
Using && ||
$ echo foo && echo bar || echo baz
foo
bar
Speaking about your question, simplify and do something like this:
if curl -o "$TEMPFILE" "$URL"; then
echo SUCCESS
echo "Here's the logic of your 4 and 5 line or whatever you want."
else
echo FAIL
echo "Maybe you should exit with an error code, like this "
exit 1
fi
exit 0

How can I check that program was installed successfully on linux?

I have .bin file, that contains a lot of scripts, that install program on Ubuntu and after installing via terminal
sudo chmod u+x myprogram.bin
./myprogram
I have it in /usr/bin/myprogram.
I need to write the program, which checks if some program was installed correctly to the system. How can I do that ?
Any one of the followings should work
$ command -v foo >/dev/null 2>&1 && echo "installed" || echo "not installed"
$ type foo >/dev/null 2>&1 && echo "installed" || echo "not installed"
$ hash foo >/dev/null 2>&1 && echo "installed" || echo "not installed"
In your case you may use
$ command -v /usr/bin/myprogram >/dev/null 2>&1 && echo "installed" || echo "not installed"
or else you can create function like below
#!/usr/bin/env bash
installed()
{
command -v "$1" >/dev/null 2>&1
}
# and then
if installed ls; then
echo 'ls exists'
else
echo 'ls not installed'
fi
Examples :
$ command -v ls >/dev/null 2>&1 && echo "installed" || echo "not installed"
installed
$ command -v foo >/dev/null 2>&1 && echo "installed" || echo "not installed"
not installed
If you already know the full path to the application/program then any one of the following should work
$ test -x /bin/ls && echo 'installed' || echo 'not installed'
installed
$ [ -x /bin/ls ] && echo 'installed' || echo 'not installed'
installed

Compiling this bash command

For my openVPN configuration, I'm trying to hit this code but it's not working.
Not a big fan of Bash command so I need to help to get this compiled. Any Ideas? (Currently using Kali linux and every time I hit this code it shows error message bash: syntax error near unexpected token &&)
echo "Enter clientName:" && set CLIENTNAME = $< && printf "\n<ca>\n" >> ./client.conf && cat ./ca.crt >> ./client.conf && printf "</ca>\n" >> ./client.conf && printf "\n<cert>" >> ./client.conf && grep -v '^ ' ./$CLIENTNAME.crt | grep -v 'Certificate' >> ./client.conf && printf "</cert>\n" >> ./client.conf && printf "\n<key>\n" >> ./client.conf && cat ./$CLIENTNAME.key >> ./client.conf && printf "</key>\n" >> ./client.conf
The problem is that this is not Bash code; it is Tcsh code:
$ echo $0
-bash
$ echo "Enter clientName:" && set CLIENTNAME = $< && printf "\n<ca>\n" >> ./client.conf && cat ./ca.crt >> ./client.conf && printf "</ca>\n" >> ./client.conf && printf "\n<cert>" >> ./client.conf && grep -v '^ ' ./$CLIENTNAME.crt | grep -v 'Certificate' >> ./client.conf && printf "</cert>\n" >> ./client.conf && printf "\n<key>\n" >> ./client.conf && cat ./$CLIENTNAME.key >> ./client.conf && printf "</key>\n" >> ./client.conf
-bash: syntax error near unexpected token `&&'
Change shell:
$ tcsh
% echo $0
tcsh
% echo "Enter clientName:" && set CLIENTNAME = $< && printf "\n<ca>\n" >> ./client.conf && cat ./ca.crt >> ./client.conf && printf "</ca>\n" >> ./client.conf && printf "\n<cert>" >> ./client.conf && grep -v '^ ' ./$CLIENTNAME.crt | grep -v 'Certificate' >> ./client.conf && printf "</cert>\n" >> ./client.conf && printf "\n<key>\n" >> ./client.conf && cat ./$CLIENTNAME.key >> ./client.conf && printf "</key>\n" >> ./client.conf
Enter clientName:
foo
The tutorial assumes you are using tcsh, not bash or any other POSIX-compatible shell. That said, you can make it work in bash by replacing
set CLIENTNAME = $<
with
IFS= read -r CLIENTNAME

Weird issue with "sed" command

On my script, this -for unknown some reason- gives me errors when it reaches sed command:
function nzb() {
( [ -z "$1" ] ) && echo "No argument is given!" && return 1
hash="$(echo -n "$1" | md5sum | cut -d" " -f1)"
mkdir "${hash}" && rar a -m0 -v200M "${hash}"/"${hash}".rar "$1" && rm -rf "$1" &&
par2 c -r10 -l "${hash}"/"${hash}".par2 "${hash}"/* && ~/newsmangler-master/mangler.py
-c ~/.newsmangler.conf "{hash}" && sed -i "1i$1\n${hash}\n" ~/hashs.txt
}
.
ERROR: "{hash}" does not exist or is not a file!
ERROR: no valid arguments provided on command line!
But when I -out of curiosity- removed sed's preceding commands, it worked perfectly like it suppose to:
function nzb() {
( [ -z "$1" ] ) && echo "No argument is given!" && return 1
hash="$(echo -n "$1" | md5sum | cut -d" " -f1)"
sed -i "1i$1\n${hash}\n" ~/hashs.txt
}
.
Any Ideas?
EDIT: it seems the problem is located in this area:
. . . && ~/newsmangler-master/mangler.py -c ~/.newsmangler.conf "{hash}" && . . .
Because even this is working:
function nzb() {
( [ -z "$1" ] ) && echo "No argument is given!" && return 1
hash="$(echo -n "$1" | md5sum | cut -d" " -f1)"
mkdir "${hash}" && rar a -m0 -v200M "${hash}"/"${hash}".rar "$1" && rm -rf "$1" &&
par2 c -r10 -l "${hash}"/"${hash}".par2 "${hash}"/* && sed -i "1i$1\n${hash}\n"
~/hashs.txt
}
Replace "{hash}" with "${hash}"

Resources