which loop in bash script - bash

I am quite new in bash, but I need to create a simple script which will do below steps:
Wait 1 minute
A) bash script will use CM to generate result file
B) check row 8 in result file (to know if Administrator is running any jobs or not)
if NO jobs:
C) bash script will use CM to start cube refresh
D) wait 1 minute
D1) Remove result file
E) generate result file
E1) Read row 8
no jobs:
F) remove result file G) EXIT
yes:
I) Go to D)
YES:
E) Wait 1 minute
F) Remove result file
Go to A)
As bash doesn't have goto (or should not be use), I tried few loops, but I not sure which I should choose.
I know how to:
- start cube(step C)
- generate result file (step A & E):
- check line 8:
sed '8!d' /abc_uat/cmlogs/adm_jobs_u1.log
condition for loops will be probably similar to this: !='Owner = Administrator'
but how to avoid goto ?
I tried with while do loop, but I am not sure what should I add in case of false condition, I added else, but not sure of it:
sleep 60
Generate result file with admin jobs (which admin runs inside of 3rd party tool)
while [ sed '8!d' admin_jobs_result_file.log !="Owner = Administrator" ];
do
--NO Admin jobs
START CUBE REFRESH (it will start admin job)
sleep 60
REMOVE RESULT FILE (OLD)
GENERATE RESULT FILE
while [ sed '8!d' admin_jobs_result_file.log = "Owner = Administrator" ];
--Admin is still running cube refresh
do
sleep 60
REMOVE RESULT FILE (OLD)
GENERATE RESULT FILE
-- it should continue checking every 1 minute if admin is still running cube refresh job, so I hope it will go back to while condition
else
done
else
-- Admin is running something
sleep 60
REMOVE RESULT FILE (OLD)
GENERATE RESULT FILE
-it should check result file again but I think it will finish loop
done

You can replace goto with a loop. while loop, for example.
Syntax
while <condition>
do
action
done

Check out cron jobs. Delegate, if possible, "waiting for a minute" task to cron. Cron should worry about running your script on a timely fashion.
You may consider writing two scripts instead of one.
Do you really need to create a result file? Do you know piping ? (no offense, just mentioning it because you said you were fairly new to bash)

Hopefully this is self explanatory.
result_file=admin_jobs_result_file.log
function generate {
logmsg sleeping
sleep 60
rm -f "$result_file"
logmsg generating
# use CM to generate result file
}
function owner_is_administrator {
# if line 8 contains "Owner = Administrator", exit success
# else exit failure
sed -n '8 {/Owner = Administrator/ q 0; q 1}' "$result_file"
}
function logmsg { date "+%Y-%m-%d %T -- $*"; }
##############
generate
while owner_is_administrator; do
generate
done
# at this point, line 8 does NOT contain "Owner = Administrator"
logmsg start cube refresh
# use CM to start cube refresh
generate
while owner_is_administrator; do
generate
done
logmsg Done
Looks like AIX's sed can't exit with a specified status. Try this instead:
function owner_is_administrator {
# if line 8 contains "Owner = Administrator", exit success
# else exit failure
awk 'NR == 8 {if (/Owner = Administrator/) {exit 0} else {exit 1}}' "$result_file"
}

Related

Stop a bash script when awm command returns with failure

I have the following command
ads2 cls create
This command might return two outputs, a reasonable one that looks like:
kernel with pid 7148 (port 9011) killed
kernel with pid 9360 (port 9011) killed
probing service daemon # http://fdt-c-vm-0093.fdtech.intern:9010
starting kernel FDT-C-VM-0093 # http://fdt-c-yy-0093.ssbt.intern:9011 name=FDT-C-VM-0093 max_consec_timeouts=10 clustermode=Standard hostname=FDT-C-VM-0093 framerate=20000 schedmode=Standard rtaddr=fdt-c-vm-0093.fdtech.ssbt tickrole=Local tickmaster=local max_total_timeouts=1000
kernel FDT-C-VM-0093 running
probing service daemon # http://172.16.xx.xx:9010
starting kernel FDT-C-AGX-0004 # http://172.16.xx.xx:9011 name=FDT-C-AGX-0004 max_consec_timeouts=10 clustermode=Standard hostname=FDT-C-AGX-0004 framerate=20000 schedmode=Standard rtaddr=172.16.xx.xx tickrole=Local tickmaster=local max_total_timeouts=1000
kernel Fxx-x-xxx-xxx4 running
>>> start cluster establish ...
>>> cluster established ...
nodes {
node {
name = "FDT-C-VM-xxxx";
address = "http://fxx-x-xx-0093.xxx.intern:xxxx/";
state = "3";
}
node {
name = "xxx-x-xxx-xxx";
address = "http://1xx.16.xx.xx:9011/";
state = "3";
}
}
and an unreasonable one that would be:
kernel with pid 8588 (port 9011) killed
failed to probe service daemon # http://xxx-c-agx-0002.xxxx.intern:90xx
In both ways, I'm passing this output to awk in order to check the state of the nodes in case a reasonable output is returned, otherwise it should exits the whole script (line 28).
ads2 cls create | awk -F [\"] ' BEGIN{code=1} # Set the field delimiter to a double quote
/^>>> cluster established .../ {
strt=1 # If the line starts with ">>> cluster established ...", set a variable strt to 1
}
strt!=1 {
next # If strt is not equal to 1, skip to the next line
}
$1 ~ "name" {
cnt++; # If the first field contains name, increment a cnt variable
nam[cnt]=$2 # Use the cnt variable as the index of an array called nam with the second field the value
}
$1 ~ "state" {
stat[cnt]=$2; # When the first field contains "state", set up another array called stat
print "Node "nam[cnt]" has state "$2 # Print the node name as well as the state
}
END {
if (stat[1]=="3" && stat[2]=="3") {
print "\033[32m" "Success" "\033[37m" # At the end of processing, the array is used to determine whether there is a success of failure.
}
28 else {
29 print "\033[31m" "Failed. Check Nodes in devices.dev file" "\033[37m"
30 exit code
}
}'
some other commands...
Note that this code block is a part of a bash script.
All I'm trying to do is just to stop the whole script (rest following commands) from continuing to execute when it goes inside line 29 in which the exit 1 code should actually do the job. However its not working. In other words. It prints actually the statement Failed. Check Nodes in devices.dev file. However, it continues executing the next commands while i expect the script to stop as the exit command in line 30 should have also been executed.
I suspect your subject Stop a bash script from inside an awk command is what's getting you downvotes as trying to control what the shell that called awk does from inside the awk script is something you can't and shouldn't try to do as that would be a bad case of Inversion Of Control like calling a function in C to do something and that function deciding to exit the whole program instead of just returning a failure status so the calling code can decide what to do upon that failure (e.g. perform recovery actions and then call that function again).
You seem to be confusing exiting your awk script with exiting your shell script. If you want to exit your shell script when the awk script exits with a failure status then you need to write the shell code to tell the shell to do so, e.g.:
whatever | awk 'script' || exit 1
or to get fancy about it:
whatever | awk 'script' || { ret="$?"; printf 'awk exited with status %d\n' "$ret" >&2; exit "$ret"; }
For example:
$ cat tst.sh
#!/usr/bin/env bash
date | awk '{exit 1}' || { ret="$?"; printf 'awk exited with status %d\n' "$ret" >&2; exit 1; }
echo "we should not get here"
$ ./tst.sh
awk exited with status 1

Retry a command only once : when a command fails (in bash)

for ( i=3; i<5; i++)
do
execute some command 1
if command 2 is successful then do not run the command 1 (the for loop should continue)
if command 2 is not successful then run command 1 only once (like retry command 1 only once, after this the for loop should continue)
done
This is to note that command 2 is dependent on command 1 and command 2 can only be executed after command 1
for example:
for ( i=3; i<5; i++)
do
echo "i" >> mytext.txt ---> command 1
if "check the content of mytext.txt file to see if the value of i is actually added" ---> command 2
if it is not added then execute echo "i" >> mytext.txt (command 1) again and only once.
if i value is added to the file .. then exit and continue the loop
done
Since the "command 1" is quite big and not just an example echo statement here.I do not want to add "command 1" twice .. once outside and once inside the if condition. I want this logic in an optimized way with no redundancy of code.
Per a comment it sounds like the OP may need to invoke command 1 up to 2 times for a given $i value, but only wants to type command 1 once in the script.
Siddhartha's suggestion to use a function is probably good enough but depending on the actual command 1 (OP mentions that it's 'quite big') I'm going to play devil's advocate and assume there could be additional issues with passing some args to the function (eg, a need to escape some characters ... ??).
The general idea is to have an internal loop that can be executed at most 2 times, with logic in the loop that will allow for an 'early' exit (eg, after just one pass through the loop).
Since we're using pseudo-code I'll use the same ...
for ( i=3; i<5; i++ )
do
pass=1 # reset internal loop counter
while ( pass -le 2 )
do
echo "i" >> mytext.txt # command 1
if ( pass -eq 1 ) # after first 'command 1' execution
&& ( value of 'i' is in mytext.txt ) # command 2
then
break # break out of inner loop; alternatively ...
# pass=10 # ensure pass >= 2 to force loop to exit on this pass
fi
pass=pass+1 # on 1st pass set pass=2 => allows another pass through loop
# on 2nd pass set pass=3 => will force loop to exit
done
done
you can declare functions like
function command
{
your_command -f params
}
for ( i=3; i<5; i++)
do
if command ; then
echo "success"
else
echo "retry"
command
fi
done

bash command substitution on a script that executes a background task

I'd like to return the results of a script that also kicks off a background task. The command substitution operator waits for the background task, making the call slow. I created the following example to illustrate the problem:
function answer {
sleep 5 &
echo string
}
echo $(answer)
Is there a way to call a command without waiting on any background jobs it creates?
Thanks,
Mark
The problem is that sleep inherits stdout and keeps it open. You can simply redirect stdout:
answer() {
sleep 5 > /dev/null &
echo "string"
}
echo "$(answer)"
If you are intending for the program to continue merrily along in the mean time while the function works, you can just call the function to run in the background.
function answer {
sleep 5
echo Second
}
echo $(answer) &
echo First
The output of which will be
First
Second

Is it possible to run two loops at the same time?

So I have a project in my cyber security class to make a bash game. I like to make one of those medieval games where you make farms and mines to get resources. Well I like to make something like that. To do that I have to have two while loops running. Like this
while [ blah ]; do
blah
done
while [ blah ]; do
blah
done
Is it possible to run two while loops at the same time and if I am writing it wrong how do I write it?
If you put a & after each done, like done&, you will create new processes in the background that run the while loops. You will have to be careful to realize what this means though, since the bash script will continue executing commands after creating those new processes even if they are not finished. You might use the wait command to prevent this from happening, but I'm not too used to using that so I cannot vouch for it.
Yes, but you will have to fork a new process for each while loop to be executing in. Technically, they won't both run at the same time (unless you consider multiple cores, but this isn't even garaunteed).
Below is a link to how to fork multiple processes using bash.
Forking / Multi-Threaded Processes | Bash
Since you mention this is a school project, I'll stop here lest I help you "not learn".
R
First things first, wrap the loop into a function and then fork it.
This is done when you want to split a process, for example, if I'm processing a CSV with 160,000+ lines, single process/"thread" will take hours. If you wrap the loop into a function and simply fork it, you will have x amount of processes running, then add wait/kill defunct process loop and you are done. here what you are looking at.
while loop with nested loop:
function jobA() {
while read STR;
do
touch $1_temp
key=$(IFS="|";set -- $STR; echo $1)
for each in ${blah[#]};
do
#echo "$each"
done
done <$1;
}
for i in ${blah[#]};
do
echo "$i"
$(jobRDtemp $i) &
child_pid=$!
parent_pid=$$
PIDS+=($child_pid)
echo "forked process $child_pid with parent $parent_pid"
done
for pid in ${PIDS[#]};
do
wait $pid
done
echo "all jobs done"
sleep 1
Now this is wrapped, here is example of a FORKED loop. this means you will have parallel processes run in the background, WAIT will wait for ALL to complete before proceeding. This is important for some type of scripts.
Also, DO NOT use nested FOR loops written C style like presented above, example:
for (( i = 1; i <= 5; i++ )) ### Outer for loop ###
This is VERY slow. use THIS type:
for each in ${blah[#]};
do
#echo "$each"
if [ "$key" = "$each" ]; then
# echo "less than $keyValNeed..."
echo $STR >> $1_temp
fi
done
You could also use nested for loops
for (( i = 1; i <= 5; i++ )) ### Outer for loop ###
do
for (( j = 1 ; j <= 5; j++ )) ### Inner for loop ###
do
echo -n "$i "
done
echo "" #### print the new line ###
done
EDIT: I thought you meant Nested Loop but reading again you said running both loops "at the same time". I will leave my answer here though.

Catch PHP Exits in CLI via sh

Alright, I am trying to figure this problem out. I have a class that loops indefinitely until I either restart it manually or it runs out of available ram. I've written the code to be compliant with both CLI and normal web based execution. The only difference is with web-based execution the script will last about 12 hours or so until it crashes due to memory issues. When I run it in CLI it runs far longer, (On average 4-5 days before a crash due to memory)
The script is an IRC bot that is heavily customized for what I need it to do. I don't know enough of C++, ruby, python or other languages to make something that is cross platform compliant. My dev machine is Windows and my production server is Ubuntu. Right now I have the script successfully forking off and detaching from the terminal window so I can close that with out ending the script.
But what I am trying to figure out is how to catch errors and restart the script automatically since it tends to fail at random times and not always when I am at the IRC channel to catch the failure. One last positive would be a way to catch if I requested a restart from the channel and have the bot restart as I am constantly adding in new code functions or just general bug fixes.
Here is my CLI start php script
#!/usr/bin/php
<?php
include_once ("./config/base_conf.php");
include_once ("./libs/irc_base.php");
if ($config ['database'] == true) {
include_once ("./config/db_conf.php");
}
$server = getopt ( 's', array ("server::" ) );
if (! $server) {
$SER = 'default_server';
} elseif ($server ['server'] == 'raelgun') {
$SER = 'server_a';
} else {
$SER = 'default_server';
}
declare ( ticks = 1 )
;
$pid = pcntl_fork ();
if ($pid == - 1) {
die ( "could not fork" );
} else if ($pid) {
exit (); // we are the parent
} else {
// we are the child
}
// detatch from the controlling terminal
if (posix_setsid () == - 1) {
die ( "could not detach from terminal" );
}
$posid = posix_getpid ();
$PID_FILE = "/var/run/bot_process_".$SER.".pid";
$fp = fopen ($PID_FILE , "w" ) or die("File Exists Process Running");
fwrite ( $fp, $posid );
fclose ( $fp );
// setup signal handlers
pcntl_signal ( SIGTERM, "sig_handler" );
pcntl_signal ( SIGHUP, "sig_handler" );
// loop forever performing tasks
$bot = new IRC_BOT ( $config, $SER );
function sig_handler($signo) {
switch ($signo) {
case SIGTERM :
$bot->machineKill();
unlink($PID_FILE);
exit ();
break;
case SIGHUP :
$bot->machineKill();
unlink($PID_FILE);
break;
default :
// handle all other signals
}
}
Depending on the server I connect to since it connects to a maximum of 2 servers I run the following in the terminal to get the script running
php bot_start_shell.php --server="servernamehere" > /dev/null
So what I am trying to do is get a shell file coded correctly to monitor that script, and if it exits due to error or requested restart to restart the script.
I've used this technique for a while, where a shell script runs a PHP script, monitors the exit value and restarts.
Here's a test script that uses exit() to return a value to the shell script - 95,96 & 100 are taken as other 'unplanned restarts', handled at the bottom of the script.
#!/usr/bin/php
<?php
// cli-script.php
// for testing of the BASH script
exit (rand(95, 100));
/* normally we would return one of
# 97 - planned pause/restart
# 98 - planned restart
# 99 - planned stop, exit.
# anything else is an unplanned restart
*/
I prefer to wait a few seconds before I restart the script, to avoid wasting CPU if the script being called instantly fails, and so would be immediately restarted.
#!/bin/bash
# runPHP-Worker.sh
# a shell script that keeps looping until an exit code is given
# if its does an exit(0), restart after a second - or if it's a declared error
# if we've restarted in a planned fashion, we don't bother with any pause
# and for one particular code, we can exit the script entirely.
# The numbers 97, 98, 99 must match what is returned from the PHP script
nice php -q -f ./cli-script.php -- $#
ERR=$?
## Possibilities
# 97 - planned pause/restart
# 98 - planned restart
# 99 - planned stop, exit.
# 0 - unplanned restart (as returned by "exit;")
# - Anything else is also unplanned paused/restart
if [ $ERR -eq 97 ]
then
# a planned pause, then restart
echo "97: PLANNED_PAUSE - wait 1";
sleep 1;
exec $0 $#;
fi
if [ $ERR -eq 98 ]
then
# a planned restart - instantly
echo "98: PLANNED_RESTART, no pause";
exec $0 $#;
fi
if [ $ERR -eq 99 ]
then
# planned complete exit
echo "99: PLANNED_SHUTDOWN";
exit 0;
fi
# unplanned exit, pause, and then restart
echo "unplanned restart: err:" $ERR;
echo "sleeping for 1 sec"
sleep 1
exec $0 $#
If you don't want to do different things for each value, it really just comes down to
#!/bin/bash
php -q -f ./cli-script.php -- $#
exec $0 $#;

Resources