For loop with Alphabet
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 tochars
, 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 toa 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-stylefor
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
- The
seq
command generates numeric sequences.$(
)
performs command substitution, so$(seq 97 122)
is replaced with the output ofseq 97 122
. These are the character codes fora
throughz
. -
The powerful
printf
command can turn character codes into letters (e.g.,printf '\141'
printsa
, followed by a newline), but the codes must be in octal, whileseq
outputs only in decimal. So I've usedprintf
twice: the innerprintf %o $i
converts decimal numbers (provided byseq
) to octal, and is substituted into the outerprintf
command. (Although it's also possible to use hexadecimal, it's no simpler and seems to be less portable.) -
printf
interprets\
followed by an octal number as the character with that code, and\n
as a newline. But the shell also uses\
as an escape character. A\
in front of$
will prevent$
from causing an expansion to occur (in this case, command substitution), but I don't want to prevent that, so I have escaped it with another\
; that's the reason for\\
. The second\
beforen
doesn't need to be escaped because, unlike\$
,\n
doesn't have special meaning to the shell in a double-quoted string. - For more information on how double quotes and the backslash are used in shell programming, see the section on quoting in the international standard. See also 3.1.2 Quoting in the Bash Reference Manual, especially 3.1.2.1 Escape Character and 3.1.2.3 Double Quotes. (Here's the whole section, in context.) Note that single quotes (
'
) are also an important part of shell quoting syntax, I just don't happen to have used them in that script.
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 ]
).
Related videos on Youtube
denski
Updated on September 18, 2022Comments
-
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 over 7 yearsThis works on Ubuntu.
-
denski over 7 yearsI 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 over 7 yearsThank you. Is there a way of circumventing dash's lack of concept of array's?
-
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 over 7 yearsIf you have some suggested reading that would be helpfully. Thanks.
-
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 over 7 yearsThis 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 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 over 7 years@denski Presumption :)
-
Ciro Santilli OurBigBook.com about 5 yearsRelated: stackoverflow.com/questions/169511/…