Why does this bash "if" statement execute both statements - bash

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

Related

Bash loop, two conditions, first must be true and part of the loop

I'm migrating a bunch of openvz containers and can only do one at a time for reasons. This is very time consuming and if I'm not watching the destination node constantly I won't know if the migrations fail or are done.
So I'm trying to write a little shell script to do two things. First, make sure the container ID that is being migrated shows up in the list of containers. If it does not, exit the script and send me an email. Second, as long as the first condition is true, watch for the status of the container to change to running and once that is true send me an email.
I have the second part of this working using until, I'm not sure that is the best way to go about it though and I need the first part, making sure the container exists to work as well. Obviously both these tests need to run every loop in case the migration fails. I just can't wrap my head around how to do this.
Here is what I have so far:
#!/bin/bash
read -p "Container ID: " -e CID
until vzlist -a | grep $CID | grep running
do
sleep 600
done
echo "Migration of container $CID complete" | mail -s "Migration complete" red#cted.com
If I'm understanding how to interpret vxlist -a correctly, something like this should work:
#!/bin/bash
emailTarget="red#cted.com"
read -p "Container ID: " -e CID
while true; do # This loops until something `break`s it out of the loop
# Capture the container status, so we can run multiple checks with
# only one run of `vzlist`.
containerStatus=$(vzlist -a | grep "$CID")
if [[ -z "$containerStatus" ]]; then
# If the the result was the empty string, our container is not
# in the list, so apparently it's failed.
echo "Migration of container $CID failed" | mail -s "Migration failed" "$emailTarget"
break
elif [[ "$containerStatus" = *"running"* ]]; then
# It's in the list *and* has "running" status -- migration succeeded!
echo "Migration of container $CID complete" | mail -s "Migration complete" "$emailTarget"
break
fi
# If neither of those conditions was met, it's still trying;
# wait 10 minutes and check again.
sleep 600
done

If else in bash script for shell command

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

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).

Check if mail was sent succesfully in bash

I have this bash script that sends to my email the new IP address if it has changed running on a crontab.
SUBJ="My new IP is "
EMAIL="myemail#gmail.com"
ip1=""
ip2=""
read ip1 < ip.txt
ip2=$(wget -qO- ifconfig.me/ip)
if [ "$ip1" = "$ip2" ]
then
exit
else
echo "$ip2" > ip.txt
echo "$ip2" | mail -s "$SUBJ""$ip2" $EMAIL
exit
fi
The problem is that if for any reason the email could not been sent, the ip.text file would still change, and the next time that the script runs "$ip1" = "$ip2" would be true and never send the email.
How can I check if the mail was sent successfully?
I followed this tutorial:
If you just want to ensure that the mail program ran successfully, use
echo "$ip2" | mail -s "$SUBJ $ip2" && echo "$ip2" > ip.txt
If you actually care about the mail being successfully delivered after mail sends it to your local mail transfer agent, there's not much you can do.

Bash script for usb controller connection output?

I have a bash script that configures my USB controller to act like an Xbox 360 controller for use with Steam. Here is the current script:
#!/bin/bash
lsmod | grep xpad
sudo xboxdrv --evdev /dev/input/event14 --evdev-absmap ABS_X=x1,ABS_Y=y1,ABS_RX=x2,ABS_RZ=y2,ABS_HAT0X=dpad_x,ABS_HAT0Y=dpad_y --axismap -Y1=Y1,-Y2=Y2 --evdev-keymap BTN_TOP=x,BTN_TRIGGER=y,BTN_THUMB2=a,BTN_THUMB=b,BTN_BASE3=back,BTN_BASE4=start,BTN_TOP2=lb,BTN_PINKIE=rb,BTN_BASE=lt,BTN_BASE2=rt,BTN_BASE5=tl,BTN_BASE6=tr --mimic-xpad --silent &
exit 0
I would like to add some sort of "if" "else" statement to say that if the script is successful then notify me that it worked.
But I don't know how I would do it.
e.g
if "script runs fine"
then notify-send -t 2000 USB controller connection successful
else "do nothing"
My questions are:
1.Would I need 2 seperate scripts? i.e one to run the first, the other to read the output of that script and produce the notification, or can I do it all in the same script?
2.How do I read that output?
Generally after it has created the new event in the terminal after the script runs, it will tell me the new event number (usually event15). If not it flags up an error.
Any help will be greatly appreciated,
Thanks in advance
script 1
#!/bin/bash
lsmod | grep xpad
sudo xboxdrv --evdev /dev/input/event14 --evdev-absmap ABS_X=x1,ABS_Y=y1,ABS_RX=x2,ABS_RZ=y2,ABS_HAT0X=dpad_x,ABS_HAT0Y=dpad_y --axismap -Y1=Y1,-Y2=Y2 --evdev-keymap BTN_TOP=x,BTN_TRIGGER=y,BTN_THUMB2=a,BTN_THUMB=b,BTN_BASE3=back,BTN_BASE4=start,BTN_TOP2=lb,BTN_PINKIE=rb,BTN_BASE=lt,BTN_BASE2=rt,BTN_BASE5=tl,BTN_BASE6=tr --mimic-xpad --silent &
script 2
#!/bin/bash
output=$(/path/to/script1)
if [ $? -eq 0 ]; then
#do something
fi
#!/bin/bash
lsmod | grep xpad
command1="sudo xboxdrv --evdev /dev/input/event14 --evdev-absmap ABS_X=x1,ABS_Y=y1,ABS_RX=x2,ABS_RZ=y2,ABS_HAT0X=dpad_x,ABS_HAT0Y=dpad_y --axismap -Y1=Y1,-Y2=Y2 --evdev-keymap BTN_TOP=x,BTN_TRIGGER=y,BTN_THUMB2=a,BTN_THUMB=b,BTN_BASE3=back,BTN_BASE4=start,BTN_TOP2=lb,BTN_PINKIE=rb,BTN_BASE=lt,BTN_BASE2=rt,BTN_BASE5=tl,BTN_BASE6=tr --mimic-xpad --silent"
command2="notify-send -t 2000 USB controller connection successful" #Your command here.
output=$(command1) && [ echo $output | grep -q "Replace this with your success output" ] && $command &
#!/bin/bash
lsmod | grep xpad
command1=$(sudo xboxdrv --evdev /dev/input/event14 --evdev-absmap ABS_X=x1,ABS_Y=y1,ABS_RX=x2,ABS_RZ=y2,ABS_HAT0X=dpad_x,ABS_HAT0Y=dpad_y --axismap$
command2=$(notify-send -t 2000 USB controller connection successful) #Your command here.
output=$(command1) && [ echo $output | grep -q "Press Ctrl-c to quit" ] && $command2 &
exit 0

Resources