如何在bash中循环遍历前n个字母表的字母

5
我知道要循环遍历字母表,可以这样做:
for c in {a..z};  do   something;  done

我的问题是,如何循环遍历前n个字母(例如构建字符串),其中n是在命令行中给定的变量/参数。
我在SO上搜索,只找到了针对数字的答案,例如使用C风格的for循环或seq(请参见例如如何在Bash中迭代由变量定义的数字范围?)。而且我在我的环境中没有seq
谢谢。

5个回答

8

简单的方法是将它们放入数组中,并通过索引循环遍历:

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

如果你只希望它们以破折号分隔:

printf "%s-" "${chars[@]:0:n}"

3

那个人的回答可能是最好的方法,但这里有一个不需要数组变量的替代方案:

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在评论中指出,您可以使用条件语句(( n-- )) || break退出循环而不需要辅助变量$i,但请注意这会修改$n的值。


这里还有一种不使用数组但效率较低的方法,它使用子字符串提取(参数扩展)

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

最后,您可以将数组和列表方法组合起来,以便更加简洁,但纯数组方法更加高效:

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

如果你在高尔夫比赛中,可以使用((n--))||break - rici
@ric:i 的确如此,但通常我更喜欢意图的清晰而不是追求代码的简短。但我也意识到易读性因人而异。我的方法是否也给你留下了“追求简短”的印象?(无恶意双关语) - mklement0
当我追求清晰明了时,通常会选择使用 if...then,但口味和风格因人而异。在任何情况下,除非我需要 i 进行某些操作,否则我都会将 n 减少,同理。 - rici
@rici:我明白你的意思。就我个人而言,我喜欢&&||的简洁性,但我知道它不如if ... then ... else这样的家喻户晓。如果我们假设$n是作为参数传递的,那么我的偏好是不修改它,因此使用辅助变量$i - mklement0

2
你可以循环遍历字母表中的字符代码并进行转换:
# 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

感谢您向我介绍了 printf %x "'<char>" 这个惯用法(以及它的反义词,printf '\x%s' <charCode>),这甚至是 POSIX 强制规定的 - "如果前导字符是单引号或双引号,则值应为在单引号或双引号后面的字符的底层代码集中的数值",但为了解决 OP 的问题,实际上没有必要在字符和它们的 ASCII 值之间来回转换。 - mklement0
此外,我建议不要使用全大写的变量名,以避免与环境变量发生冲突。 - mklement0
1
不客气,我同意。数组解决方案更加简洁。 - Yaar Hever

1

你是不是只是在遍历字母表来创建一个子集?如果是这样,那就简单点:

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

编辑。根据您下面的评论,您有sed吗?

% sed -e 's/./&-/g' <<< ${alpha:0:$n}
a-b-c-d-

我需要循环遍历它。例如,$ alpha = abcdefghijklmnopqrstuvqxyz; n = 4; for c in $ {alpha:0:$ n}; do echo $ {c} -; done 给了我 abcd-。但我需要的是 a-b-c-d-。谢谢。 - thor
是的,确实sed适用于我上面的情况 :) 不过我需要组装一个比插入分隔符更复杂的字符串。我猜需要一个真正的循环。 - thor
1
在Bash中,不需要在子字符串索引表达式${alpha:0:n}中包含$符号,这样做是可以的(类似于((...))语法)。 - David C. Rankin

0
这个问题和答案在一定程度上帮助了我解决了我的问题。
我需要在bash中基于字母来放大字母表的一部分。(译注:loupe是放大镜的意思,此处应该是指截取)

虽然扩展严格文本化,

我找到了一个解决方案,并使它更加简单:

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

这将导致:

A
B
C
D

希望这对于寻找解决方案的人有所帮助,就像我一样最终来到了这里。
同样在这里得到了答案
原问题的完整答案是:
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

这将导致相同的结果:

A
B
C
D

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接