1

Here's part of my script:

#!/bin/sh

n=1

echo "How many repetitions to run (0 = no limit)?"
read reps

if [ $reps = 0 ]; then
    while="true"
else
    while="[ $n -lt $((reps+1)) ]"
fi

echo "How much off-time in-between reps (in minutes)?"
read time

pwr_init

while $while; do
    echo "* Sending power pulse $n"
    pwr_normal
    t=$time
    echo "* Waiting for next power on"
    while [ $t -gt 0 ]; do
        echo "    $t min until next power on"; sleep 60
        t=$((t-1))
    done
    n=$((n+1))
done

Everything is working exactly as I need it to, except for the first while loop I have going on. Whenever the $while variable is called, I'd like shell to expand and check the variables at that moment that are defined in the first if statement.

I can only get shell to expand the variables at the moment of the first if statement, but then I get the behavior like true because the comparator is always true and never changes when $n gets incremented, because that $while variable has already expanded everything during the first if statement.

I've tried methods like ${!while} and various combinations of single and double quotes, but no luck. I usually get errors like $n is a bad number or bad substitution.

Right now the only thing I can think of is to add even more if statements within more functions to check and echo this line to the while statement every time. I have to believe there's a better way to do this, though, I'm just having a hard time figuring it out (and also finding the right keywords to search for online).

While debugging, I use set -x, and can see that the comparator string is being evaluated properly in the way the script is written here, but as I mentioned it never updates itself when $n is incremented.

Does anyone have a trick for this type of substitution that makes life just a little easier? Much appreciated any insight that can be given into this odd variable expansion conundrum!

NOTE1: pwr_init and pwr_normal are functions defined elsewhere in the full script.

NOTE2: Take note of the shebang, it must be POSIX compliant. I can't use any fancy bash specific techniques.

EDIT: Getting this to function like I need was less convoluted than I thought, and I'm putting what I added below. However, I'm leaving the question open as it would be great to know whether or not this type of variable expansion can be done.

New script with the check function wrapped around the if statement:

#!/bin/sh

n=1

echo "How many repetitions to run (0 = no limit)?"
read reps

count=$((reps+1))

check() {
    if [ $reps = 0 ]; then
        while="true"
    else
        while="[ $n -lt $count ]"
    fi
}

echo "How much off-time in-between reps (in minutes)?"
read time

pwr_init

while $while; do
    echo "* Sending power pulse $n"
    pwr_normal
    t=$time
    echo "* Waiting for next power on"
    while [ $t -gt 0 ]; do
        echo "    $t min until next power on"; sleep 60
        t=$((t-1))
    done
    n=$((n+1))
    check
done
0

Extra simple solution: to run an "without end", just set the limit to some insanely large number. A 32-bit integer can hold numbers up to 4294967296. Allowing for a sign bit, and rounding down, we could assume we can count up to at least 2000000000. Since you're sleeping for at least a minute between the counts, that works for up to, oh about 4000 years.

So just do:

if [ "$reps" = 0 ]; then
    reps=2000000000
fi

And hardwire the condition to be

while [ "$n" -le "$reps" ]; do ...

(If you're running a 64-bit system, you can count even higher. Not that it matters. If you're running a 16-bit system instead, then you're likely to be screwed in other ways too.)


Simple solution: Just make a function to check for if reps is zero or n is smaller than reps.

keep_going() {
    [ "$reps" = 0 ] && return 0
    [ "$n" -le "$reps" ] 
}

and then make the condition

while keep_going; do ...

Yes, you'll call a function each time. No, it doesn't matter, since you only do it once a minute at maximum. (And if you did care about being fast, you shouldn't use the shell to do it.)


The solution you're looking for: Use eval. With the double quotes in the assignment to while="[ $n -lt $count ]", both $n and $count will be expanded immediately. What you need is to use single quotes in the assignment: while='[ $n -lt $count ]' and then run the string through eval to have the variables there expanded.

while='[ $n -lt $count ]'
while eval "$while" ; do ...
  • Both of the 1st options would definitely work, and I hadn't thought about just running another function like you suggested in the 2nd simple solution. The final bit was exactly what I was looking for though, thank you! I saw some info out there about using eval, but I never could get it to work. What you put here does indeed expand like I expect, I must not have tried it exactly like that although I was using single quotes. Marked as the answer. – engsysadmin Jan 30 '18 at 21:55
1

You might try defining different functions:

if [ $reps -eq 0 ]; then
    while_test() { true; }
else
    while_test() { test $1 -lt $((reps+1)); }
fi

then

while while_test $n; do

You can use eval in a POSIX shell to work with the condition in a variable:

$ cond='[ $n -lt $reps ]'
$ reps=5
$ n=4
$ eval "$cond" && echo 'n < reps' || echo 'n >= reps'
n < reps
$ n=5
$ eval "$cond" && echo 'n < reps' || echo 'n >= reps'
n >= reps

Make sure the condition is defined in single quotes so the variables are not expanded prematurely.

  • Good approach! This is a little cleaner than what I ended up implementing and would ultimately solve the issue I was having. Unfortunately, it doesn't answer the question of how to have the nested variables expand every time the main variable is expanded. – engsysadmin Jan 26 '18 at 22:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.