Shell script + time dependency - linux

I wanted to write a shell script for the following:
I want to check if a service is running, if it is running then exit 1, or else after 5 min of it not running, exit -1.
something like:
while(for 5 minutes) {
if service running, exit 1
}
exit -1 //service is not running even after 5 minutes, so exit -1.
I am able to check the condition that if service is running or not, but not able to add the time constraint part.
This is what i attempted
if (( $(ps -ef | grep -v grep | grep tomcat7 | wc -l) > 0 ));
then
echo "running"
else
echo "NOT running"
fi

You should use the bash sleep command. An excerpt from the man page:-
You could provide sleep 5m in your script to wait for 5 minutes and do an action.
NAME
sleep - delay for a specified amount of time
DESCRIPTION
Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default), 'm' for minutes, 'h' for hours or 'd' for days. Unlike most implementations that require NUMBER be an
integer, here NUMBER may be an arbitrary floating point number. Given two or more arguments, pause for the amount of time specified by the sum of their values.
A proper way to your solution would be:-
#!/bin/bash
maxAttempts=0
maxCounter=2 # Number of attempts can be controlled by this variable
while [ "$maxAttempts" -lt "$maxCounter" ]; do
if ps -ef | grep -v grep | grep "tomcat7" > /dev/null
then
echo "tomcat7 service running, "
exit 1
else
maxAttempts=$((maxAttempts+1))
sleep 5m # The script waits for 5 minutes before exiting with error code '-1'
fi
done
exit -1
The if condition works when the ps -ef | grep -v grep | grep "tomcat7" returns a command success error code for which the condition passes. > /dev/null to suppress all standard outpur(stdout, stderr) to /dev/null so that we can work only with the exit codes of the command provided.

Related

Need to restart apache if apache connection exceeds X amount

I'm creating a bash script in which whenever my apache exceeds a fixed amount of numbers then it restart itself on next cronjob. I've created something but it is not running properly.
#!/bin/bash
RESTART="systemctl restart httpd"
COUNT="ps aux | grep httpd | wc -l"
SERVICE="httpd"
LOGFILE="/opt/httpd/autostart-apache2.log"
if COUNT > 45
then
echo "starting apache at $(date)" >> $LOGFILE
$RESTART >> $LOGFILE
else
echo "apache is running at $(date)"
fi
The error it is throwing is
line 10: COUNT: command not found
apache is running at Sat Sep 10 23:34:30 IST 2022
Can anyone help me on how to store the value of the output of ps aux | grep httpd | wc -l as a number and compare it with another number.
RESTART="systemctl restart httpd" is a variable assignment, although it might work for your use case, you should really use a function instead. See the Complex page for more info.
COUNT="ps aux | grep httpd | wc -l" is a variable assignment as well use Command Substitution, like what you did with $(date), on a side note grep has the -c option, there is no need for wc.
COUNT > 45 You're executing the COUNT word/string but since there is nothing in your script/path/function/executable with the same name (COUNT is a variable name in your script), thus you get the error: line 10: COUNT: command not found
> is a form of redirection if not used in Arithmetic Expression., it truncates a file or create it if it does not exists. Like in your code > 45 created a file named 45. Open the file for writing is the proper term word for it according to the bash manual.
Here is how I would write it.
#!/usr/bin/env bash
service="httpd"
count="$(ps aux | grep -c "$service")"
logfile="/opt/httpd/autostart-apache2.log"
restart(){ systemctl restart "$service" ; }
if (( count > 45 )); then
echo "starting apache at $(date)" >> "$logfile"
restart >> "$logfile" 2>&1
else
echo "apache is running at $(date)"
fi
(( count > 45 )) is a form of test in bash for comparing/testing numbers. The $ can be omitted inside it.

Script to check if vim is open or another script is running?

I'm making a background script that requires a user to input a certain string (a function) to continue. The script runs fine, but will interrupt anything else that is open in vim or any script that is running. Is there a way I can test in my script if the command line is waiting for input to avoid interrupting something?
I'm running the script enclosed in parenthesis to hide the job completion message, so I'm using (. nightFall &)
Here is the script so far:
#!/bin/bash
# nightFall
clear
text=""
echo "Night begins to fall... Now might be a good time to rest."
while [[ "$text" != "rest" ]]
do
read -p "" text
done
Thank you in advance!
If you launch nightFall from the shell you are monitoring, you can use "ps" with the parent PID to see how many processes are launched by the shell as well:
# bg.sh
for k in `seq 1 15`; do
N=$(ps -ef | grep -sw $PPID | grep -v $$ | wc -l)
(( N -= 2 ))
[ "$N" -eq 0 ] && echo "At prompt"
[ "$N" -ne 0 ] && echo "Child processes: $N"
sleep 1
done
Note that I subtract 2 from N: one for the shell process itself and one for the bg.sh script. The remainder is = how many other child processes does the shell have.
Launch the above script from a shell in background:
bash bg.sh &
Then start any command (for example "sleep 15") and it will detect if you are at the prompt or in a command.

How to timeout a tail pipeline properly on shell

I am implementing monitor_log function which will tail the most recent line from running log and check required string with while loop, the timeout logic should be when the tail log running over 300 seconds, it must close the tail and while loop pipeline.
The big issue i found is for some server the running log NOT keep generating, which means tail -n 1 -f "running.log" will also NOT generate output for while loop to consume, hence the timeout checking logic if [[ $(($SECONDS - start_timer)) -gt 300 ]] will not hit properly.
e.g I set 300 seconds to timeout, but if running.log stopped generate new line before 300 seconds and no more new line in 30 minutes, tail will not generate new output in 30 minutes, hence timeout checking logic in while loop not hit in 30 minutes, so even after 300 seconds it keep tailing and not break out, and if no new line coming from running.log forever, the timeout checking logic will not hit forever.
function monitor_log() {
if [[ -f "running.log" ]]; then
# Timer start
start_timer=$SECONDS
# Tail the running log last line and keep check required string
tail -n 1 -f "running.log" | while read tail_line
do
if [[ $(($SECONDS - start_timer)) -gt 300 ]]; then
break;
fi
if [[ "$tail_line" == "required string" ]]; then
capture_flag=1
fi
if [[ $capture_flag -eq 1 ]]; then
break;
fi
done
fi
}
Could you help to figure out the proper way to timeout the tail and while loop when 300 seconds ? Thank you.
Two options worth considering for inactivity timeout. Usually, option #1 works better.
Option 1: Use timeout (read -t timeout).
It will cap the the 'read' time. See information from bash man. The timeout will cause the read to fail, breaking the whlie loop.
In the code above, replace
tail -n 1 -f "running.log" | while read tail_line
with
tail -n 1 -f "running.log" | while read -t 300 tail_line
Option 2: TMOUT envvar
It's possible to get same effect by setting TMOUT env var.
From bash man - 'read' command:
-t timeout
Cause read to time out and return failure if a complete line of input (or a specified number of characters) is not
read within timeout seconds. timeout may be a decimal number with a
fractional
portion following the decimal point. This option is only effective if read is reading input from a terminal,
pipe, or other special file; it has no effect when reading from
regular files. If
read times out, read saves any partial input read into the specified variable name. If timeout is 0, read returns
immediately, without trying to read any data. The exit status is 0 if
input is
available on the specified file descriptor, non-zero otherwise. The exit status is greater than 128 if the
timeout is exceeded.
Based on dash-o's answer I did test for option 1, the -t for read command works fine only when while read loop on main shell and tail in sub shell, in my question, the tail in main shell, and while read loop consume its output in subshell, in this condition, even setup -t for read command, script not stop when time used up. Refer to
Monitoring a file until a string is found, Bash tail -f with while-read and pipe hangs and How to [constantly] read the last line of a file?
The working code based on dash-o's solution below:
function monitor_log() {
if [[ -f "running.log" ]]; then
# Tail the running log last line and keep check required string
while read -t 300 tail_line
do
if [[ "$tail_line" == "required string" ]]; then
capture_flag=1
fi
if [[ $capture_flag -eq 1 ]]; then
break;
fi
done < <(tail -n 1 -f "running.log")
# Silently kill the remained tail process
tail_pid=$(ps -ef | grep 'tail' | cut -d' ' -f5)
kill -13 $tail_pid
fi
}
But as test, this function after timeout auto terminate will left tail process alive, we can observe PID by check ps -ef on console, need to kill tail_PID separately.
Also test another solution: not change tail and while read loop position, so tail still on main shell and while read loop keep in sub shell after | pipeline, the only change is adding GNU's timeout command before tail command, it works perfect and no tail process left after timeout auto terminate:
function monitor_log() {
if [[ -f "running.log" ]]; then
# Tail the running log last line and keep check required string
timeout 300 tail -n 1 -f "running.log" | while read tail_line
do
if [[ "$tail_line" == "required string" ]]; then
capture_flag=1
fi
if [[ $capture_flag -eq 1 ]]; then
break;
fi
done
fi
}

Counter loop piped to grep seems unexpectedly random

Experiment to make grep stop a while loop at 5 iterations, so that /tmp/foo should only be 5 lines long:
n=1
while [ $n -le 2000 ]
do
echo $n
n=$(( $n + 1 ))
done | tee /tmp/foo | grep -q ^5
Check count:
wc -l < /tmp/foo
Outputs:
34
Repeated runs of the above return different numbers most every time, but it's not very random -- running the above 5000 times in bash results in about 1500 9s for example, running it 5000 times in dash results in 157 106s.
These results seem more interesting than the initial experiment. What's happening in this code?
Pipes are asynchronous. While tee will exit the first time it tries to write to its end of the pipe after grep exits and closes its end, there is no way to know how many lines tee will write before grep actually does so. It's entirely up to the OS scheduler.

Start a command, count lines of output after 10 seconds, then either restart it or let it run

I have an interesting situation I am trying to script. I have a program that outputs 26,000 lines after 10 seconds when it starts successfully. Otherwise I have to kill it and start it again. I tried doing something like this:
test $(./long_program | wc -l) -eq 26000 && echo "Started successfully"
but that only works if the program finishes running. Is there a clever way to watch the output stream of a command and make decisions accordingly? I'm at a loss, not quite sure even how to start searching for this. Thanks!
What about
./long_program > mylogfile &
pid=$!
sleep 10
# then test on mylogfile length and kill $pid if needed
count=0
until [ $count -eq 26000 ]; do
killall ./longrun
#start in background
./longrun >output.$$ &
sleep 10
count=$(wc -l output.$$ |awk '{print $1}')
done
echo "done"
#disown so it continues after current login quits
disown -h

Resources