5

I know that to loop through the alphabet, one can do

for c in {a..z};  do   something;  done

My question is, how can I loop through the first n letters (e.g. to build a string) where n is a variable/parameter given in the command line.

I searched SO, and only found answers doing this for numbers, e.g. using C-style for loop or seq (see e.g. How do I iterate over a range of numbers defined by variables in Bash?). And I don't have seq in my environment.

Thanks.

Community
  • 1
  • 1
thor
  • 21,418
  • 31
  • 87
  • 173
  • Possible duplicate of [Looping through alphabets in Bash](https://stackoverflow.com/q/7300070/608639). – jww Dec 25 '19 at 12:05

5 Answers5

8

The straightforward way is sticking them in an array and looping over that by index:

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

Alternatively, if you just want them dash-separated:

printf "%s-" "${chars[@]:0:n}"
mklement0
  • 382,024
  • 64
  • 607
  • 775
that other guy
  • 116,971
  • 11
  • 170
  • 194
3

that other guy's answer is probably the way to go, but here's an alternative that doesn't require an array variable:

n=3 # sample value

i=0 # var. for counting iterations
for c in {a..z};  do 
  echo $c # do something with "$c"
  (( ++i == n )) && break # exit loop, once desired count has been reached
done

@rici points out in a comment that you could make do without aux. variable $i by using the conditional (( n-- )) || break to exit the loop, but note that this modifies $n.


Here's another array-free, but less efficient approach that uses substring extraction (parameter expansion):

n=3 # sample value

# Create a space-separated list of letters a-z.
# Note that chars={a..z} does NOT work.
chars=$(echo {a..z})

# Extract the substring containing the specified number
# of letters using parameter expansion with an arithmetic expression,
# and loop over them.
# Note:
#  - The variable reference must be _unquoted_ for this to work.
#  - Since the list is space-separated, each entry spans 2 
#    chars., hence `2*n` (you could subtract 1 after, but it'll work either way).
for c in ${chars:0:2*n};  do 
  echo $c # do something with "$c"
done

Finally, you can combine the array and list approaches for concision, although the pure array approach is more efficient:

n=3 # sample value

chars=( {a..z} ) # create array of letters

# `${chars[@]:0:n}` returns the first n array elements as a space-separated list
# Again, the variable reference must be _unquoted_.
for c in ${chars[@]:0:n}; do
  echo $c # do something with "$c"
done
Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Or `((n--))||break` if you were golfing – rici Mar 27 '15 at 01:26
  • @ric:i Indeed, but, generally, I prefer clarity of intent over golfing, but I realize that legibility is in the eye of the beholder. Did my approach strike you (no pun intended) as "golfy" too? – mklement0 Mar 27 '15 at 01:37
  • When I'm aiming for clarity I usually go for `if...then`, but tastes and styles differ. I'd count `n` down in any case, unless I needed `i` for something, but ditto. – rici Mar 27 '15 at 01:57
  • @rici: I hear you. Personally, I love the concision of `&&` and `||`, but I'm aware that it's less of a household idiom than `if ... then ... else`. If we assume that `$n` was passed as an argument, my preference is not to modify it, hence the use of aux. variable `$i`. – mklement0 Mar 27 '15 at 02:04
2

You can loop through the character code of the letters of the alphabet and convert back and forth:

# suppose $INPUT is your input
INPUT='x'
# get the character code and increment it by one
INPUT_CHARCODE=`printf %x "'$INPUT"`
let INPUT_CHARCODE++

# start from character code 61 = 'a'
I=61  
while [ $I -ne $INPUT_CHARCODE ]; do
    # convert the index to a letter
    CURRENT_CHAR=`printf "\x$I"`
    echo "current character is: $CURRENT_CHAR"
    let I++
done
Yaar Hever
  • 81
  • 1
  • 6
  • Thanks for introducing me to the `printf %x "'"` idiom (and its inverse, `printf '\x%s' `), which is even mandated by POSIX - ["If the leading character is a single-quote or double-quote, the value shall be the numeric value in the underlying codeset of the character following the single-quote or double-quote"](http://man.cx/printf), but to solve the OP's problem there really is no need to convert back and forth between characters and their ASCII values. – mklement0 Mar 27 '15 at 02:15
  • Also, I suggest not using all-uppercase variable names, so as to avoid conflicts with _environment_ variables. – mklement0 Mar 27 '15 at 02:19
  • 1
    You're welcome and I agree. The array solution is much neater. – Yaar Hever Mar 27 '15 at 11:02
1

Are you only iterating over the alphabet to create a subset? If that's the case, just make it simple:

 $ alpha=abcdefghijklmnopqrstuvqxyz
 $ n=4
 $ echo ${alpha:0:$n}
 abcd

Edit. Based on your comment below, do you have sed?

% sed -e 's/./&-/g' <<< ${alpha:0:$n}
a-b-c-d-
eduffy
  • 39,140
  • 13
  • 95
  • 92
  • I needed to loop through it. For example, `$ alpha=abcdefghijklmnopqrstuvqxyz; n=4; for c in ${alpha:0:$n}; do echo ${c}- ; done` gives me `abcd-`. But I needed `a-b-c-d-`. Thanks. – thor Mar 26 '15 at 20:01
  • Yes, indeed `sed` works for my case above:) I need to assemble a more complex string than inserting delim's though. I suspect a real loop is needed. – thor Mar 26 '15 at 20:12
  • 1
    In bash, there isn't a need to include the `$` on `n` within the substring index expression `${alpha:0:n}` works fine (similar to `((...))` syntax). – David C. Rankin Mar 26 '15 at 20:43
0

This question and the answers helped me with my problem, partially.
I needed to loupe over a part of the alphabet based on a letter in bash.

Although the expansion is strictly textual

I found a solution: and made it even more simple:

START=A
STOP=D
for letter in $(eval echo {$START..$STOP}); do
    echo $letter
done

Which results in:

A
B
C
D

Hope it's helpful for someone looking for the same problem i had to solve, and ends up here as well

(also answered here)

And the complete answer to the original question is:

START=A
n=4

OFFSET=$( expr $(printf "%x" \'$START) + $n)
STOP=$(printf "\x$OFFSET") 

for letter in $(eval echo {$START..$STOP}); do
    echo $letter
done

Which results in the same:

A
B
C
D
Alphons
  • 303
  • 2
  • 6