Issue in a Bash loop - bash

just a quick question with something I'm not understanding. Everything is working fine - I'm just trying to get my head around something.
I have a very simple Bash script that is restarting dnsmasq once per day:
LogFile="/var/log/logfile"
declare -a CMD_AR=()
declare -a ECHO_OUT=(stopping starting)
STOP=$(service dnsmasq \stop)
SLEEP=$(\sleep 15)
START=$(service dnsmasq \start)
CMD_AR+=($STOP)
CMD_AR+=($START)
z=0
while [[ $z -le 1 ]]; do
DT=`date +%c`
echo "$DT - ${ECHO_OUT[$z]} dnsmasq..." >> ${LogFile}
eval ${CMD_AR[$z]} >> ${LogFile} 2>&1
unset DT
eval ${SLEEP}
z=$[$z+1]
done
So...this works, however, the DT variable never changes. So my log file reads:
Tue 31 Oct 2017 10:57:07 PM MDT - stopping dnmasq
Tue 31 Oct 2017 10:57:07 PM MDT - starting dnmasq
Shouldn't the time string be at least 15 (the value of sleep 15) seconds different? I'm failing to understand why the loop is not re-computing the DT variable - anyone?
Also, I'll take any suggestions on my code as well - I'm a total hack when it comes to scripting.

The issue is in this line:
SLEEP=$(\sleep 15)
Which sets SLEEP variable to a null string since the command sleep 15 generates no output.
Change it to:
SLEEP="sleep 15"
to solve your problem.
The line
START=$(service dnsmasq \start)
has the same problem.
Your script seems overly complicated - get it checked through shellcheck. In general, it is not a good idea to store commands in a variable and execute them through eval - see BashFAQ/050. Your code can be rewritten this way:
LogFile="/var/log/logfile"
z=0
while ((z <= 1)); do
if ((z == 0)); then
echo "$(date) - stopping dnsmasq..." >> "${LogFile}"
service dnsmasq stop >> "${LogFile}" 2>&1
else
echo "$(date) - starting dnsmasq..." >> "${LogFile}"
service dnsmasq start >> "${LogFile}" 2>&1
fi
sleep 15
((z++))
done
Improvements done:
Use (( .. )) for arithmetic operations and checks
Wrap variables in double quotes to prevent word splitting
Remove indirect execution with eval
Eliminate the array - we don't need it here
Make the code more readable
The loop seems unnecessary here. The code could probably be written even more concisely as:
LogFile="/var/log/logfile"
exec >"$LogFile" 2>&1
echo "$(date) - stopping dnsmasq..."
service dnsmasq stop
sleep 15
echo "$(date) - starting dnsmasq..."
service dnsmasq start

Related

Script executes but fails to increment

So I have this shell script that I think should run a given number of times, sleep then resume, and output the results to a log file
#!/bin/bash
log=/path/to/file/info.log
a=$(COMMAND1 | cut -d : -f 2)
b=$(COMMAND2 | grep VALUE| cut -c 7,8)
for i in {1..4}
do
echo "Test" $i >> $log
date >> $log
echo $a >> $log
echo "$((-113 + (($b * 2)))) VALUE" >> $log
sleep 60
done
When I run ps -ef | grep scriptname.sh it seems the script does run. Executes once then the PID is gone as if the run has completed.
I have tested the script and know that it is running and capturing the data I want. But I do not understand why its not incrementing and not sure why its ending earlier than expected.
info.log output sample
Test {1..4}
DATE IN UTC
EXPECTED VALUE OF a
EXPECTED VALUE OF b
Note that the output is literally "Test {1..4}" not "Test 1" "Test 2" Test 3" and so on, as I would expect.
I have run the script as ./scriptname.sh & and as /path/to/file/scriptname.sh &
I have read that there is a difference in running the script with sh and bash though I dont fully understand what effect that would have on the script. I am not a software person at all.
I have tried to run the script with nohup to keep it running in the background if I close the terminal. I also thought the & in the command was supposed to keep the script running in the background. Still it seems the script does not continue to run
I previously asked this question and it was closed, citing that it was similar to a post about the difference between sh and bash...but thats not my main question.
also echo "$BASH_VERSION" returns nothing, a blank line. echo "$-" returns smi, and I have no idea what that means. but bash --version returns:
BusyBox v1.17.1 (2019-11-26 10:41:00 PST) built-in shell (ash)
Enter 'help' for a list of built-in commands.
So my questions are:
If running the script with sh - is that done with ./scriptname.sh & and running the script with bash is /path/to/file/scriptname.sh &...and if so what effect does that have on how the script code is processed? that is - is using sh or bash. I do not fully understand the difference between the two
why does the script not continue to run when I close the terminal? This is my big concern. I would like to run this script hourly for a set period of time. Every time I try something and come back I get one instance in the log.
Neither brace expansion nor seq are part of the POSIX specification. Use a while loop.
log=/path/to/file/info.log
a=$(COMMAND1 | cut -d : -f 2)
b=$(COMMAND2 | grep VALUE| cut -c 7,8)
i=1
while [ "$i" -le 4 ]; do
printf 'Test %s\n' "$i"
date
printf '%s\n' "$a"
printf '%s\n' "$((-113 + (($b * 2)))) VALUE"
sleep 60
i=$((i+1))
done >> "$log"
(I suspect that you want to move the assignments to a and b inside the loop as well; right now, you are simply writing identical files to the log at each iteration.)

Bash script: `exit 0` fails to exit

So I have this Bash script:
#!/bin/bash
PID=`ps -u ...`
if [ "$PID" = "" ]; then
echo $(date) Server off: not backing up
exit
else
echo "say Server backup in 10 seconds..." >> fifo
sleep 10
STARTTIME="$(date +%s)"
echo nosave >> fifo
echo savenow >> fifo
tail -n 3 -f server.log | while read line
do
if echo $line | grep -q 'save complete'; then
echo $(date) Backing up...
OF="./backups/backup $(date +%Y-%m-%d\ %H:%M:%S).tar.gz"
tar -czhf "$OF" data
echo autosave >> fifo
echo "$(date) Backup complete, resuming..."
echo "done"
exit 0
echo "done2"
fi
TIMEDIFF="$(($(date +%s)-STARTTIME))"
if ((TIMEDIFF > 70)); then
echo "Save took too long, canceling backup."
exit 1
fi
done
fi
Basically, the server takes input from a fifo and outputs to server.log. The fifo is used to send stop/start commands to the server for autosaves. At the end, once it receives the message from the server that the server has completed a save, it tar's the data directory and starts saves again.
It's at the exit 0 line that I'm having trouble. Everything executes fine, but I get this output:
srv:scripts $ ./backup.sh
Sun Nov 24 22:42:09 EST 2013 Backing up...
Sun Nov 24 22:42:10 EST 2013 Backup complete, resuming...
done
But it hangs there. Notice how "done" echoes but "done2" fails. Something is causing it to hang on exit 0.
ADDENDUM: Just to avoid confusion for people looking at this in the future, it hangs at the exit line and never returns to the command prompt. Not sure if I was clear enough in my original description.
Any thoughts? This is the entire script, there's nothing else going on and I'm calling it direct from bash.
Here's a smaller, self contained example that exhibits the same behavior:
echo foo > file
tail -f file | while read; do exit; done
The problem is that since each part of the pipeline runs in a subshell, exit only exits the while read loop, not the entire script.
It will then hang until tail finds a new line, tries to write it, and discovers that the pipe is broken.
To fix it, you can replace
tail -n 3 -f server.log | while read line
do
...
done
with
while read line
do
...
done < <(tail -n 3 -f server.log)
By redirecting from a process substitution instead, the flow doesn't have to wait for tail to finish like it would in a pipeline, and it won't run in a subshell so that exit will actually exits the entire script.
But it hangs there. Notice how "done" echoes but "done2" fails.
done2 won't be printed at all since exit 0 has already ended your script with return code 0.
I don't know the details of bash subshells inside loops, but normally the appropriate way to exit a loop is to use the "break" command. In some cases that's not enough (you really need to exit the program), but refactoring that program may be the easiest (safest, most portable) way to solve that. It may also improve readability, because people don't expect programs to exit in the middle of a loop.

Piping to a command in Ash Shell

I wrote a bash script to send an email using telnet. I'm installing it on a TS-7260 running busyBox (which has an ash shell).
Something is different between Bash and Ash and I can't figure out why the following won't work. It's got to be something with the way I'm piping the echos to telnet. Here's the script:
#!/bin/ash
# Snag all the error messages from a given date, open a telnet connection to an outgoing mail server, stick the logs in an email, and send it.
# Tue Jul 2 14:06:12 EDT 2013
# TMB
# Tue Jul 9 17:12:29 EDT 2013
# Grepping the whole error file for WARNING and the piping it to a grep for the date took about four minutes to complete on the gateway. This will only get longer and the file will only get bigger as time goes by.
# Using tail to get the last 5000 lines, I get about three days of errors (2000 of them are from one day, though)
# Getting 5000 lines, then searching them by WARNING and then DATE took 15 seconds on the gateway.
yesterdayDate=$(./getYesterday)
warningLogs=$(tail -5000 /mnt/sd/blah.txt | grep WARNING | grep "$yesterdayDate")
sleep 30
{
sleep 5
echo "ehlo blah.com"
sleep 5
echo "auth plain blah"
sleep 5
echo "mail from: blah#blah.com"
sleep 5
echo "rcpt to: me#blah.com"
sleep 5
echo "data"
sleep 5
echo "Hi!"
sleep 1
echo "Here are all the warnings and faults from yesterday:"
sleep 1
echo "$yesterdayDate"
sleep 1
echo "NOTE: All times are UTC."
sleep 1
echo ""
sleep 1
echo "$warningLogs"
sleep 10
echo ""
sleep 1
echo "Good luck,"
sleep 1
echo "The Robot"
sleep 5
echo "."
sleep 20
echo "quit"
sleep 5
} | telnet blah.com port
exit
I've tried using normal parentheses too before the pipe. I've read the man page for ash and am still doing something stupid. I suspect it's some kind of child process business going on.
This works fine from bash, btw.
Thanks in advance!
Note -- I simplified the script to be just:
echo "quit" | telnet blah.com port
It does exactly what you'd expect in bash, but I see nothing happen in ash.
Replacing the echo with "sleep 10" shows sleep running as a process, but not telnet.
After some more experimentation, the problem was not with the shell at all, but with the implementation of Telnet on Busybox. On my version of BusyBox (1.00rc2), piping anything to Telnet didn't work.
echo blah | telnet -yrDumb
Should have at least made telnet complain about usage. It didn't.
I grabbed the most recent version of inetutils (1.9.1) and compiled its telnet for the TS-7260. It works like a dream (read: it works) now, and is consistent with the behavior I see using telnet and bash on my normal linux box.
Thanks for the help!

Current activity logger

Does anyone know of an app (for iOS or Mac) that asks for my current activity every hour or so? I see tons of apps that let you log your activities, but none that use popups/notifications to actively ask you.
I found a script that looks promising (source), but I'm having trouble implementing it so that it repeats every hour:
#!/bin/bash
echo What are you doing right now?
read -e what
echo `date` - $what >> timelog.txt
You could wrap the whole thing in a while loop and have it sleep for 1 hour after asking:
#!/bin/bash
while true
do
echo What are you doing right now?
read -e what
echo `date` - $what >> timelog.txt
sleep 3600
done
you could set that script up as a cron job. Here's a SE question on how to do it
https://apple.stackexchange.com/questions/9373/how-do-i-run-a-cron-job-on-a-mac
An improvement to get a graphical dialog with zenity (uses GTK+). For Mac OS cocoaDialog could be the thing to use.
#!/bin/bash
while true
do
input=$(zenity --text "What are you doing?" --entry --width 400)
retval=$?
case $retval in
0)
what=$input;;
1)
what="";;
esac
echo `date` - $what >> ~/Documents/timelog/timelog_`date +%F`.txt
sleep 3600
done

bash - killing a subprocess after a set timeout

I was hoping somebody would be able to help me with this
I need a loop for a shell script that will run what is inside the loop for 15 seconds. SO for example
if (true)
run command for 15 seconds
fi
kill PID
I am new to shell scripting, so i am lost with this.
Also I am using a debian instll if that makes any difference
Any help is appreciated
Are you looking for the timeout command?
The following bash script might work for you. The script will set the initial epoch time as a variable prior to beginning a loop. While the loop runs an additional variable will be set with the current epoch time. Both epoch times will be compared and as long as the difference is less than or equal to 15 your command will continue to run. Note that in the script below the current command running is 'echo "counting ${COUNTER}"'. You should change this portion of the script to match what you are trying to accomplish. Once the difference of the two epoch times is greater than 15 the script will exit. You will need to initate your kill command at this point. If an error does occur you should see "ERROR... YourScript.sh failed" in "YourLogFile" (set your log file to what you would like)
NOTE: Whatever you are attempting to run while inside this loop may run many many many times within the 15 second period. By utilizing the script below as a test you will see that the echo command runs more than 50 times per second.
#!/bin/bash
LOOP="true"
INITIAL_TIME=$(date "+%s")
while [[ ${LOOP} == true ]]; do
CURRENT_TIME=$(date "+%s")
COUNTER=$(expr ${CURRENT_TIME} - ${INITIAL_TIME})
if [[ ${COUNTER} -le "15" ]]; then
echo "counting ${COUNTER}"
# RUN YOUR COMMAND IN PLACE OF THE ABOVE echo COMMAND
elif [[ ${COUNTER} -gt "15" ]]; then
exit 0
#INITIATE YOUR KILL COMMAND IN PLACE OF OR BEFORE THE exit
else
echo "ERROR... YourScript.sh failed" >> /YourLogFile
fi
done

Resources