For loop with Alphabet

9,479

Solution 1

Presumably, you are running the script as:

sh ForLoopAlphabetTest.sh

In Ubuntu, sh is symlinked to dash; as dash has no concept of arrays, you are getting the syntax error for (.

The script works perfectly on bash, so it would be fine if you were running it as bash's argument:

bash ForLoopAlphabetTest.sh

Now, you have the bash shebang on the script, so you could make the script executable (chmod u+x ForLoopAlphabetTest.sh), and run it as:

/path/to/ForLoopAlphabetTest.sh

or from the script's directory:

./ForLoopAlphabetTest.sh

Also note that, your script contains brace expansion {a..z}, and C-style for construct: for (( ... )) which are also not supported by dash; so if your goal is portability, you should look at POSIX sh syntaxes only.

Solution 2

Your script uses three features of the Bash shell that are not provided by all Bourne-style shells. As heemayl says, you can simply run that script with bash instead of sh. Your hashbang line at the top (#!/bin/bash) specifies bash but is only effective if you execute the script, as heemayl explained. If you pass the name of the script to sh, sh won't automatically call bash, but will simply run the script. This is because once your script is actually running, the hashbang line has no effect.

Your other alternative, if you need to write fully portable scripts that don't depend on Bash features, is to change your script so that it works without them. The Bash features you use are:

  • An array. This came first, so this is what produced the error when you tried to run your script with the Dash shell. The parenthesized expression ( {a..z} ), which you assign to chars, creates an array, and ${chars[i]}, which appears in your loop, indexes into it.
  • Brace expansion. In Bash, and also in many other shells, {a..z} is expanded to a b c d e f g h i j k l m n o p q r s t u v w x y z. However, this is not a universal (or standardized) feature of Bourne-style shells, and Dash doesn't support it.
  • The C-style alternate for-loop syntax. Although based on arithmetic expansion, which is not itself specific to Bash (though some very old, non-POSIX-compliant shells don't have it, either), the C-style for loop is a Bash-ism and is not widely portable to other shells.

Bash is widely available, especially on GNU/Linux systems like Ubuntu, and (as you have seen) is also available on macOS and many other systems. Considering how much you're using Bash-specific features, you might just want to use them, and simply make sure you're using Bash (or some other shell that supports the features you're using) when you run your scripts.

However, you can replace them with portable constructs if you like. The array and C-style for loop are easy to replace; generating the range of letters without brace expansion (and without hard-coding them in your script) is the one part that's a little tricky.


First, here's a script that prints all the lower-case Latin letters:

#!/bin/sh

for i in $(seq 97 122); do
    printf "\\$(printf %o $i)\n"
done

This is portable to most Unix-like systems and doesn't depend which Bourne-style shell you use. However, a few Unix-like systems don't have seq installed by default (they tend to use jot instead, which is not installed by default most GNU/Linux systems). You can use a loop with expr or arithmetic substitution to increase portability further, if you need to:

#!/bin/sh

i=97
while [ $i -le 122 ]; do
    printf "\\$(printf %o $i)\n"
    i=$((i + 1))
done

That uses a while-loop with the [ command to continue looping only when $i is in range.


Rather than printing the whole alphabet, your script defines a variable n and prints the first $n lower-case letters. Here's a version of your script that relies on no Bash-specific features and works on Dash, but requires seq:

#!/bin/sh

n=3 start=97
for i in $(seq $start $((start + n - 1))); do
    printf "\\$(printf %o $i)\n"
done

Adjusting the value of n changes how many letters are printed, as in your script.

Here's a version that doesn't require seq:

#!/bin/sh

n=3 i=97 stop=$((i + n))
while [ $i -lt $stop ]; do
    printf "\\$(printf %o $i)\n"
    i=$((i + 1))
done

There, $stop is one higher than the character code of the last letter that ought to be printed, so I use -lt (less than) rather than -le (less than or equal) with the [ command. (It would also have worked to make stop=$((i + n - 1)) and use [ $i -le $stop ]).

Share:
9,479

Related videos on Youtube

denski
Author by

denski

Updated on September 18, 2022

Comments

  • denski
    denski over 1 year

    This works perfectly on OSX

    #!/bin/bash
    chars=( {a..z} )
    n=3
    for ((i=0; i<n; i++))
    do
      echo "${chars[i]}"
    done
    

    But when I run it on Ubuntu, I get the following error.

    ForLoopAlphabetTest.sh: 2: ForLoopAlphabetTest.sh: Syntax error: "(" unexpected
    

    I can't seem to solve the issue. Any suggestions?

    • Pilot6
      Pilot6 over 7 years
      This works on Ubuntu.
    • denski
      denski over 7 years
      I cannot get it to work on 16.04 bash 4.3 as a script. But it does work if I copy past it into terminal.
  • denski
    denski over 7 years
    Thank you. Is there a way of circumventing dash's lack of concept of array's?
  • Eliah Kagan
    Eliah Kagan over 7 years
    @denski If you want to write portable scripts that can be run by /bin/sh on any Unix-like operating system, then you won't be able to use arrays. Bash (and some other shells) have added them because they're very convenient and can't always be easily replaced with more portable code. However, for your script in particular, you can do it with no trouble and without using any bash-specific features. Are you interested in how to do that?
  • denski
    denski over 7 years
    If you have some suggested reading that would be helpfully. Thanks.
  • Eliah Kagan
    Eliah Kagan over 7 years
    @denski I've posted an answer which includes some links and examples. In my earlier comment here, I had mentioned you used arrays and C-style for loops but didn't mention your use of brace expansion. My answer covers how to do without all three. Note that this answer (i.e., heemayl's, not mine) is the primary solution to your problem; mine focuses on how you might rewrite your script if you couldn't rely on bash-specific features.
  • denski
    denski over 7 years
    This is a phenomenally detailed answer, than you for the education. I'm very much a beginner, so my way of writing scripts is bringing working elements found on the internet together until it works. I've no doubts there are 1) better and 2) simpler ways to do the things I'm creating with scripts and the above goes a long way to helping with that.
  • denski
    denski over 7 years
    @heemayl For record, I wanted to add that you were correct in your assumption that I was running scripts with sh
  • heemayl
    heemayl over 7 years
    @denski Presumption :)
  • Ciro Santilli OurBigBook.com
    Ciro Santilli OurBigBook.com about 5 years