4

I have a question with range variables in for loops. In for loops I know you can use {..} to define a range. But I want it to be customer definable. So the script asks for a range value, and I want it to be flexible. like this:

#!/bin/bash
while true; 
    do
    echo "Some explenation..."
    read -p "Possible values are: ALL or RANGE (to define a specific range):" answer
    case $answer in
            #ALL
            [aA][lL][lL] )
            echo "ALL is selected, 1 to 250"
            RANGE="{1..250}"
            break
            ;;
            #RANGE
            [rR][aA][nN][gG][eE] )
            echo "Range is selected, please define a custom range. Notation should be like: <start>..<stop>"
            echo "or: <single> <single>"
            echo "or a combination of both. Like: 1..23 28 29 101..145 180 212"
            read RANGE
            break
            ;;
            #OTHERS
            * )
            echo "Typo error!! Not correct, type again ALL, PRI, SEC or 
RANGE"
            ;;
    esac
    done
echo ""
echo "Range for this execution is set to:"
echo "$RANGE"
for NEXI in $RANGE
    do
    sleep 0.2
    echo "Nexus number: $NEXI"
    done
echo ...

But the Output when I enter {1..10} 52 54 {120..128} is:

Nexus number: {1..10}
Nexus number: 52
Nexus number: 54
Nexus number: {120..128}

while I expected it to be like:

Nexus number: 1
Nexus number: 2
Nexus number: 3
Nexus number: 4
Nexus number: 5
Nexus number: 6
Nexus number: 7
Nexus number: 8
Nexus number: 9
Nexus number: 10
Nexus number: 52
Nexus number: 54
Nexus number: 120
Nexus number: 121
... etc.

fully written out.

What can I change to achieve this?

Thanks.

2 Answers 2

3

The problem is that brace expansion happens before variable expansion. As explained in man bash:

The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and pathname expansion.

This means that when you have something like $var which contains {1..3}, and do echo "$var", the shell looks for possible brace expansions before expanding the variable to its value. Since the braces are in the variable, that means that at that point the shell sees no braces so no brace expansion is performed.

One workaround would be to eval the variable if it is a range. Add these lines before your for loop:

if [[ $RANGE =~ ^[0-9]+\.\.[0-9]+$ ]]; then
        RANGE=$(eval echo {$RANGE})
fi

Now, on a more general note, it is usually a good idea to avoid using CAPITAL variable names in shell scripts. Environment variables are usually capitalized and that can lead to mixups if you happen to use the same name as a defined env variable. So use lower case variable names to be on the safe side.

Also, this is a really annoying program to debug and equally annoying to use. Don't prompt your user for input at run time! Instead, change your script and make it take arguments. Typing values is hard, annoying, error prone and can't be automated.

4
  • I will give this a go, asap. Sep 28, 2017 at 6:22
  • [quote]Now, on a more general note, it is usually a good idea to avoid using CAPITAL variable names in shell scripts. Environmental variables are usually capitalized and that can lead to mixups if you happen to use the same name as a defined env variable. So use lower case variable names to be on the safe side.[/quote] Thanks I did not know that.... Although I do find it better readable if the variables are all capitolized, but I'll keep it in mind. Sep 28, 2017 at 8:00
  • This seems to work (more or less) it only work on 1 range. So if I do 1..14 it is working, but when I enter: 1..14 20 22 so 1 through 14 PLUS 20 and 22 it does not parse it. which is kind of mandatory.... Any idea's? Thanks so far.. Sep 29, 2017 at 12:23
  • @SHLelieveld yes: don't do this. Rewrite the script so that it takes options and/or arguments and deal with them that way. Or, if you insist on this approach (but don't, it is hell to use and hell to debug), make RANGE an array and then iterate over each element, checking whether each is a number or a range.
    – terdon
    Sep 29, 2017 at 12:37
-1

Set your shell to ksh and this code should work.

#!/usr/bin/ksh

6
  • 2
    First, no it doesn't work. Please test before posting an answer. Second, while offering alternatives can be useful, suggesting the OP change shells to solve their issue seems like overkill.
    – terdon
    Sep 27, 2017 at 12:06
  • @terdon, I did test it. But I tested only printing the RANGE part and it works on my ksh shell. I'm new here, sorry if it's wrong.
    – mathB
    Sep 27, 2017 at 12:12
  • Yes, it works if you do range="{1..3}; echo $range, but it won't work in the OP's script. Try it: change the shebang line to ksh and run it. I admit that I have no idea why it doesn't work in the script, since it does work if you try it manually. It seems to have something to do with the read, but nevertheless, it doesn't work.
    – terdon
    Sep 27, 2017 at 12:27
  • 1
    Oh and sorry about my tone in my previous comment. I had tried exactly what you suggested, changing the shebang line and it failed, so I assumed you hadn't tested at all. Now that I see there's something strange with the read command, I can understand why you'd think this could work. Anyway, welcome to the site!
    – terdon
    Sep 27, 2017 at 12:32
  • I will try this as well when back in the office. Thanks for the tip. Sep 28, 2017 at 6:22

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .