White Mask Guy
White Mask Guy

Reputation: 67

Stop a loop process in shell scripting after some time

I have a script that looks like this, this script is checking whether my pods is in Running state or Not by redefining x on every loop.

x=$(/opt/oc get pods --selector app=${bamboo.shortPlanName} -o jsonpath='{range .items[]}{.status.phase}{"\n"}{end}')

until [ "$x" == "Running" ];
do
   sleep 5
   x=$(/opt/oc get pods --selector app=${bamboo.shortPlanName} -o jsonpath='{range .items[]}{.status.phase}{"\n"}{end}')
   echo $x
done

But, I want to modify my current script to also support timeout, I mean after 60 second of looping, then it should be stop, or after 12 times of looping, then it should be stop. Any idea how to do that?

Upvotes: 3

Views: 2019

Answers (3)

pjh
pjh

Reputation: 8134

For timeout after 60 seconds try this Shellcheck-clean code:

#! /bin/bash -p

readonly kOC_SLEEP_SECS=5
readonly kOC_TIMEOUT_SECS=60

initial_secs=$SECONDS
while
    status=$(/opt/oc get pods --selector app=bamboo.shortPlanName       \
                -o jsonpath='{range .items[]}{.status.phase}{"\n"}{end}')
    printf '%s\n' "$status"
    [[ $status != Running ]]
do
    if (( (SECONDS - initial_secs) >= kOC_TIMEOUT_SECS )); then
        echo 'ERROR: Timed out' >&2
        exit 1
    fi
    sleep -- "$kOC_SLEEP_SECS"
done
  • I replaced app=${bamboo.shortPlanName} with app=bamboo.shortPlanName because the old code was causing Bash errors. You'll need to fix it properly.
  • See Why is printf better than echo? for an explanation of why I replaced echo with printf for printing the status.
  • The code treats a timeout as an error, and exits with bad status. You might want to do something different.
  • The actual timeout period will be somewhere between 60 and 65 (or maybe a little more) seconds. You'll need to do something different if you need a more accurate timeout.

For a timeout after 12 iterations try this Shellcheck-clean variation on the code above:

#! /bin/bash -p

readonly kOC_SLEEP_SECS=5
readonly kOC_MAX_ITERS=12

oc_iters=0
while
    status=$(/opt/oc get pods --selector app=bamboo.shortPlanName           \
                -o jsonpath='{range .items[]}{.status.phase}{"\n"}{end}')
    printf '%s\n' "$status"
    [[ $status != Running ]]
do
   if (( ++oc_iters >= kOC_MAX_ITERS )); then
       echo 'ERROR: Timed out' >&2
       exit 1
    fi
   sleep -- "$kOC_SLEEP_SECS"
done

Upvotes: 1

chepner
chepner

Reputation: 531605

bash provides a special variable SECONDS that can be used as a rough timer.

SECONDS=0
while (( SECONDS < 60)); do
    x=$(/opt/oc get pods --selector app=${bamboo.shortPlanName} -o jsonpath='{range .items[]}{.status.phase}{"\n"}{end}')
    if [[ $x == Running ]]; then
        break
    fi
    sleep 5
done

The expansion of SECONDS gives you not the assigned value, but the difference between the numbers of seconds since the assignment and the assigned value. The effect is like a variable whose value is incremented by 1 each second.

Upvotes: 6

Bayou
Bayou

Reputation: 3451

I think trap is the easiest way to have an accurate timeout.

Prototype:

#! /bin/bash

HALT=0
TIMEOUT=4

# Trap for SIGALRM
stop_loop() {
    HALT=1
}

# Set trap
trap stop_loop SIGALRM

# The timeout goes after $TIMEOUT seconds.
{ sleep $TIMEOUT && kill -SIGALRM $$ & }

# Main loop
until false || [[ $HALT -eq 1 ]]; do 
    sleep 1
    echo 'loop'
done

echo 'out of loop'
exit 0

In your case, this looks a something like:

HALT=0
TIMEOUT=4

stop_loop() {
    HALT=1
}

trap stop_loop SIGALRM

x=$(/opt/oc get pods --selector app=${bamboo.shortPlanName} \
    -o jsonpath='{range .items[]}{.status.phase}{"\n"}{end}')

{ sleep $TIMEOUT && kill -SIGALRM $$ & }

until [ "$x" == "Running" ] || [[ $HALT -eq 1 ]];
do
   sleep 5
   x=$(/opt/oc get pods --selector app=${bamboo.shortPlanName} \
        -o jsonpath='{range .items[]}{.status.phase}{"\n"}{end}')
   echo $x
done
exit 0

Upvotes: 0

Related Questions