If else in bash script for shell command - bash

I have written a bash script that does not show any errors. However I would like to add conditional block list if success then show email success else show error message in email as shown in the code below.
scp -i id_rsa -r testuser#1.1.1.:/data1/scp ~/data/scp/files
success >> ~/data/scp/files/log.txt 2>&1
if success
then
| mail -s "Download
Successfull" abc#test.com <<< "Files Successfully Downloaded"
else
| mail -s "Error: Download Failed" abc#test.com <<< "Error File download
Failed!"
fi
Here is the working script without If else block
#!/module/for/bash
scp -i id_rsa -r test#1.1.1.1:/data1/scp ~/data/scp/files
echo success! >> ~/data/scp/files/log.txt 2>&1 | mail -s "Download
Successfull" abc#test.com <<< "Files Successfully
Downloaded" | mail -s "Error: Download Failed" abc#test.com <<<
"Error:file download Failed!"

The scp man page states: The scp utility exits 0 on success, and >0 if an error occurs.
So you can do something like:
if scp -i id_rsa -r testuser#1.1.1.:/data1/scp ~/data/scp/files
then
mail -s "Download Successful" abc#test.com <<<"Files Downloaded"
else
mail -s "Download Error" abc#test.com <<<"Download error"
fi
or
scp -i id_rsa -r testuser#1.1.1.:/data1/scp ~/data/scp/files
if [[ $? -eq 0 ]]
then
mail -s "Download Successful" abc#test.com <<<"Files Downloaded"
else
mail -s "Download Error" abc#test.com <<<"Download error"
fi
finally you may also want to look at something like storing the scp output. Use -q to have scp not print out progress meters and what not:
MYOUT=$(scp -q -i id_rsa -r testuser#1.1.1.:/data1/scp ~/data/scp/files 2>&1)
if [[ $? -eq 0 ]]
then
mail -s "Download Successful" abc#test.com <<<"$MYOUT"
else
mail -s "Download Error" abc#test.com <<<"$MYOUT"
fi

This link should clear the air. Hope it helped!

#Korthrun has already posted several ways to accomplish what I think you're trying to do; I'll take a look at what's going wrong in your current script. You seem to be confused about a couple of basic elements of shell scripting: pipes (|) and testing for command success/failure.
Pipes are used to pass the output of one command into the input of another (and possibly then chain the output of the second command into the input of a third command, etc). But when you use a pipe string like this:
echo success! >> ~/data/scp/files/log.txt 2>&1 |
mail -s "Download Successfull" abc#test.com <<< "Files Successfully Downloaded" |
mail -s "Error: Download Failed" abc#test.com <<< "Error:file download Failed!"
the pipes aren't actually doing anything. The first pipe tries to take the output of echo and feed it to the input of mail, but the >> in the echo command sends its output to a file instead, so no actual data is sent to the mail command. Which is probably good, because the <<< on the mail command tells it to ignore the regular input (from the pipe) and feed a string as input instead! Similarly, the second pipe tries to feed the output from the first mail command (there isn't any) to the last mail command, but again it's ignored due to another <<< input string. The correct way to do this is simply to remove the pipes, and run each command separately:
echo success! >> ~/data/scp/files/log.txt 2>&1
mail -s "Download Successfull" abc#test.com <<< "Files Successfully Downloaded"
mail -s "Error: Download Failed" abc#test.com <<< "Error:file download Failed!"
This is also causing a problem in the other version of your script, where you use:
if success
then
| mail -s "Download Successfull" abc#test.com <<< "Files Successfully Downloaded"
Here, there's no command before the pipe, so it doesn't make any sense at all (and you get a shell syntax error). Just remove the pipe.
Now, about success/failure testing: you seem to be using success as a command, but it isn't one. You can either use the command you want to check the success of directly as the if conditional:
if scp ...; then
echo "It worked!"
else
echo "It failed!"
fi
or use the shell variable $? which returns the exit status of the last command (success=0, failure=anything else):
scp ...
if [ $? -eq 0 ]; then
...
There's a subtlety here that's easy to miss: the thing after if is a command, but in the second form it appears to be a logical expression (testing whether $? is equal to 0). The secret is that [ is actually a command that evaluates logical expressions and then exits with success or failure depending on whether the expression was true or false. Do not mistake [ ] for some sort of parentheses or other grouping operator, that's not what's going on here!
BTW, the [[ ]] form that Korthrun used is very similar to [ ], but isn't supported by more basic shells. It does avoid some nasty syntax oddities with [ ], though, so if you're using bash it's a good way to go.
Also, note that $? gives the status of the last command executed, so it gets reset by every single command that executes. For example, this won't work:
scp ...
echo "scp's exit status was $?"
if [ $? -eq 0 ]; then # Don't do this!!!!
...because the if is then looking at the exit status of the echo command, not scp! If you need to do something like this, store the status in a variable:
scp ...
scpstatus=$?
echo "scp's exit status was $scpstatus"
if [ $scpstatus -eq 0 ]; then

Related

Continuing a BASH script after error

This script works well in finding what I need, but there are occassions where a 404 error just kills everything.
#!/bin/sh
set +e
exec 7<foo.txt
exec 8<bar.tmp
echo "Retrieving data"
while read line1 <&7 && read line2 <&8
do
echo "beginning... retrieving files from d list"
echo "this WILL take a while"
echo $line1
echo $line2
wget -e robots=off -t1 -r -p -Q20k --wait=30 --random-wait --limit-rate=200k -np -U "$line1" http://$line2/page.html
cp /home/user/testing/*.html /home/user/production
echo "done"
done
exec 7<&-
exec 8<&-
I want to continue the script because even though this site, known as $line2 has a 404, the others don't.
I have done the "set +e", and even ran the script with "|| true", all stopping after the error. Because of the 404, there are no files to copy - and then it fails to go onto the next site.
Any suggestions?
What I found works is this:
if [ ! -d "/home/user/production" ]; then
continue #continue the loop.
fi

Batch Script Error - unexpected end of file

I'm trying to run a task where I have a script that will add a user to a remote server via ssh.
Here is my code:
#echo off
setlocal enabledelayedexpansion
set username=%1
set password=%2
for /F "tokens=*" %%a in (Linuxhosts.txt) do (
ssh -i svcaccount_id_rsa svcaccount#%%a 'bash -s' < adduser.txt
)
Here are the contents of the adduser.txt file
#!/bin/bash
#========================================================================================================
# This script allows for account creation on a server |
# It also performs error handling to ensure that the user doesn't currently exist on the system. |
# Also provides feedback from the input to verify the entries are correct. |
#========================================================================================================
while true; do
echo -n "Enter username: "
read -r username
/bin/egrep -i "^${username}:" /etc/passwd
if [ $? -eq 0 ]; then
echo "User $username already exists. Please check the username and try again."
else
echo "User $username does not exist. Proceed with account creation."
break
fi
done
adduser "$username"
if [ $? -gt 0 ]; then
echo "Error encountered."
exit 1
fi
echo -n "Enter password: "
read -r -s password
echo "$username:$password" | chpasswd
echo "Password was succesfully set for $username."
if [ $? -gt 0 ]; then
echo "Error encountered. There was a problem with your entry. Please re-run the script and try again."
exit 1
fi
usermod -a -G wheel "$username"
echo "User was succesfully added to the group wheel."
if [ $? -gt 0 ]; then
echo "Error encountered."
exit 1
fi
echo "Successfully added $username to the system."
However, when I try to run the first set of code through a cmd prompt, I get the following error:
bash: line 41: syntax error: unexpected end of file
I'm not sure what I'm missing. I have tested it with another file called hello.txt and it ran fine so I'm wondering if maybe there's spacing issues somewhere that I can't see because it's a text file.
I'm pretty sure the immediate problem here is that the file adduser.txt is in DOS/Windows format, with its lines terminated with a carriage return character, followed by a linefeed. Unix (including bash) expects just a linefeed as the line terminator, and hence treats the carriage return as part of the line's text. In this case, that means that bash sees line 17, "done", as "done[carriage return]" which isn't a valid keyword and does not end the while loop, so it keeps looking for the "done" keyword... until it runs out of file.
(Credit to Squashman for suggesting this in a comment.)
You're likely to have this problem a lot transferring files from Windows to unix; unfortunately, the tools available to fix the problem vary quite a bit depending on what OS you're using, etc.
BTW, I see a couple of other problems here. For one thing, the read commands in the script are going to be trying to read from the same source that bash is reading commands from, which is the adduser.txt file. So when it does e.g. read -r username it's actually going to be reading some later line from the script file, not from the user running the batch script. This is going to be very hard to solve the way you're doing it; I think it'll be much better to actually copy the script file to the unix system, then run it separately.
Also, as Socowi pointed out in the comments, $? gets the exit status of the last command executed, so in sections like this:
echo "$username:$password" | chpasswd
echo "Password was succesfully set for $username."
if [ $? -gt 0 ]; then
...
the if condition is checks the exit status of the command echo "Password was succesfully set for $username.", not the chpasswd command. The same problem applies to the usermod command later. The better way to do this is to use the command you want to check for success directly as the if condition:
if echo "$username:$password" | chpasswd; then
echo "Password was succesfully set for $username."
else
echo "Error encountered. There was a problem with your entry. Please re-run the script and try again."
exit 1
fi
I'd use this same format in all of the places you're checking exit status: the egrep, useradd, chpasswd, and usermod commands.
[UPDATE] After a bit of mulling, I have a couple of possible solutions: First, to fix the carriage return problem, you could pipe the file through tr -d "\r" as a quick-n-dirty CR remover (although you may have to play around with the quoting and/or escaping to get that \r through both batch and bash's command processing). Second, pass the username and password as arguments to the script instead of having it read them. Thus, your batch script would use something like this:
ssh -i svcaccount_id_rsa svcaccount#%%a 'tr -d "\r" | bash -s "username" "password"' < adduser.txt
... then change adduser.txt to use username="$1"; password="$2" instead of the read commands (and also changing that while loop into an if, since there wouldn't be an option to try again with a different username).

Actual return code for SCP

I am writing a bash script that goes through a list of filenames and attempts to copy each file using scp from two servers into a local folder. The script then compares the local files to each other. Sometimes however, the file will not exist on one server or the other or both.
At first, I was using this code:
scp $user#$host:/etc/$file ./$host/conf/ 2>/tmp/Error 1>/dev/null
error=$(</tmp/Error) # error catching
if [[ -n "$error" ]]; then echo -e "$file not found on $host"; fi
But I found that some (corporate) servers output a (legalese) message (to stderr I guess) every time a user connects via scp or ssh. So I started looking into utilizing exit codes.
I could simply use
scp $user#$host:/etc/$file ./$host/conf/ 2>/tmp/Error 1>/dev/null
if [[ $? -ne 0 ]]; then echo -e "$file not found on $host"; fi
but since the exit code for "file does not exist" is supposed to be 6, I would rather have a more precise
scp $user#$host:/etc/$file ./$host/conf/ 2>/tmp/Error 1>/dev/null
if [[ $? -eq 6 ]]; then echo -e "$file not found on $host"; fi
The problem is that I seem to be getting an exit code of 1 no matter what went wrong. This question is similar to this one, but that answer does not help me in Bash.
Another solution I am considering is
scp $user#$host:/etc/$file ./$host/conf/ 2>/tmp/Error 1>/dev/null
error=$(</tmp/Error) # error catching
if [[ ${error: -25} = "No such file or directory" ]]; then echo -e "$file not found on $host"; fi
But I am concerned that different versions of scp could have different error messages for the same error.
Is there a way to get the actual exit code of scp in a Bash script?
Per the comments (#gniourf_gniourf, #shelter, #Wintermute) I decided to simply switch tools to rsync. Thankfully the syntax doesn't need to be changed at all.
23 was the error code I was getting when files didn't exist so here is the code I ended up with
rsync -q $user#$host:/etc/$file ./$host/conf/ 2>/tmp/Error 1>/dev/null
if [[ $? -eq 23 ]]; then echo -e "$file not found on $host"; continue; fi
I'm seeing 1 for "file not found" not found, you can do testing for these sorts of things against localhost, if you need to differentiate different errors capture stdout instead.
if $err=`scp $host:$file 2>&1`
then
echo "copied successfully
else
case "$err" in
*"file not found"* )
echo "$file Not Found on $host"
;;
*"Could not resolve hostname"* )
echo "Host not found: $host"
;;
"Permission denied "* )
echo "perm-denied! $host"
;;
* )
echo "other scp error $err"
;;
esac
this isn't going to work if you have a different locale with different messages.

Why does this bash "if" statement execute both statements

I am messing around with an rsync script to sync some stuff over to my phone. I am trying to write an if statement that decides which message I get in an email upon the commands completion. For some reason, I get both emails no matter how the command exits.
success_message=echo "Podcasts are synced." | mail -s "Your podcasts have been synced to your phone." $email_address
fail_message=echo "Your phone did not sync." | mail -s "For some reason, your podcasts did not sync today." $email_address
rsync --log-file=/home/jake/logs/rsync.log -avzu $local_directory $remote_directory
if [ $? -ne "0" ];
then
$fail_message
else
$success_message
fi
This line
success_message=echo "Podcasts are synced." | mail ...
attempts to execute a command named "Podcasts are synced." (without the quotes, but with everything in between them), and pipe its output to the "mail" command. The token "success_message=echo" causes an environment variable named "success_message" to be set in the environment of the "Podcasts are synced." command, with value "echo".
Critically, even though the thing on the left side of the pipe fails (because you don't have a program named /usr/bin/Podcasts are synced., no doubt), the mail command on the right is executed. And since there are two such lines, both commands run.
Here's how to do what you were trying to do:
send_success_message () {
echo "Podcasts are synced." |
mail -s "Your podcasts have been synced to your phone." "$1"
}
send_fail_message () {
echo "Your phone did not sync." |
mail -s "For some reason, your podcasts did not sync today." "$1"
}
if rsync --log-file=/home/jake/logs/rsync.log -avzu \
"$local_directory" "$remote_directory"
then send_success_message "$email_address"
else send_fail_message "$email_address"
fi
This line sends the message, because the first instruction ends at the pipe.
success_message=echo "Podcasts are synced." | mail -s "Your podcasts have been synced to your phone." $email_address
There's absolutely no benefit to try and put the commands into shell variables and then refer to the variables once each later. Just put the commands in the if statement:
rsync --log-file=/home/jake/logs/rsync.log -avzu $local_directory $remote_directory
if [ $? -ne "0" ];
then
echo "Podcasts are synced." | mail -s "Your podcasts have been synced to your phone." $email_address
else
echo "Your phone did not sync." | mail -s "For some reason, your podcasts did not sync today." $email_address
fi

Bash command substitution stdout+stderr redirect

Good day. I have a series of commands that I wanted to execute via a function so that I could get the exit code and perform console output accordingly. With that being said, I have two issues here:
1) I can't seem to direct stderr to /dev/null.
2) The first echo line is not displayed until the $1 is executed. It's not really noticeable until I run commands that take a while to process, such as searching the hard drive for a file. Additionally, it's obvious that this is the case, because the output looks like:
sh-3.2# ./runScript.sh
sh-3.2# com.apple.auditd: Already loaded
sh-3.2# Attempting... Enable Security Auditing ...Success
In other words, the stderr was displayed before "Attempting... $2"
Here is the function I am trying to use:
#!/bin/bash
function saveChange {
echo -ne "Attempting... $2"
exec $1
if [ "$?" -ne 0 ]; then
echo -ne " ...Failure\n\r"
else
echo -ne " ...Success\n\r"
fi
}
saveChange "$(launchctl load -w /System/Library/LaunchDaemons/com.apple.auditd.plist)" "Enable Security Auditing"
Any help or advice is appreciated.
this is how you redirect stderr to /dev/null
command 2> /dev/null
e.g.
ls -l 2> /dev/null
Your second part (i.e. ordering of echo) -- It may be because of this you have while invoking the script. $(launchctl load -w /System/Library/LaunchDaemons/com.apple.auditd.plist)
The first echo line is displayed later because it is being execute second. $(...) will execute the code. Try the following:
#!/bin/bash
function saveChange {
echo -ne "Attempting... $2"
err=$($1 2>&1)
if [ -z "$err" ]; then
echo -ne " ...Success\n\r"
else
echo -ne " ...Failured\n\r"
exit 1
fi
}
saveChange "launchctl load -w /System/Library/LaunchDaemons/com.apple.auditd.plist" "Enable Security Auditing"
EDIT: Noticed that launchctl does not actually set $? on failure so capturing the STDERR to detect the error instead.

Resources