与字母循环


12

这在OSX上完美运行

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

但是,当我在Ubuntu上运行它时,出现以下错误。

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

我似乎无法解决问题。有什么建议么?


2
这适用于Ubuntu。
飞行员

我无法使其作为脚本在16.04 bash 4.3上运行。但是,如果我将它复制到终端中,它确实可以工作。
denski

Answers:


25

大概您正在以以下方式运行脚本:

sh ForLoopAlphabetTest.sh

在Ubuntu中,sh链接到dash;由于dash没有数组的概念,因此您得到的语法错误(

该脚本可在上完美运行bash,因此如果将其作为bash参数运行就可以了:

bash ForLoopAlphabetTest.sh

现在,您bash在脚本上有了shebang,因此可以使脚本可执行(chmod u+x ForLoopAlphabetTest.sh),并以以下方式运行它:

/path/to/ForLoopAlphabetTest.sh

或从脚本目录中:

./ForLoopAlphabetTest.sh

另请注意,您的脚本包含大括号扩展名{a..z}和C样式for构造:;for (( ... ))也不支持dash;因此,如果您的目标是可移植性,则应sh仅查看POSIX 语法。


谢谢。有没有办法避免破折号缺乏数组的概念?
denski

3
@denski如果您想编写可/bin/sh在任何类Unix操作系统上运行的可移植脚本,那么您将无法使用数组。Bash(和其他一些shell)已经添加了它们,因为它们非常方便并且不能始终被更易移植的代码轻松替换。但是,特别是对于您的脚本,无需使用任何特定于bash的功能,就可以轻松完成此操作。您对该怎么做感兴趣?
伊利亚·卡根

如果您有一些建议的阅读材料,将对您有所帮助。谢谢。
denski

1
@denski我已经发布了答案,其中包括一些链接和示例。在我之前的评论中,我提到过您使用数组和C风格的循环,但没有提到您使用大括号扩展。我的答案涵盖了没有这三者的情况。请注意,答案(即,heemayl的答案,而不是我的答案)是解决您的问题的主要方法;我的重点是如果您不能依赖于bash特定的功能,如何重写脚本。
Eliah Kagan

@heemayl作为记录,我想补充一点,您以为我正在使用以下脚本运行脚本是正确的sh
denski

10

您的脚本使用了Bash Shell的三个功能,而这并不是所有Bourne风格的Shell都提供的。就像heemayl所说的一样,您可以用bash代替sh。您hashbang顶部(行#!/bin/bash)指定bash,如果你,但唯一有效执行脚本,如heemayl解释。如果您将脚本的名称传递给shsh则不会自动调用bash,而只会运行脚本。这是因为一旦脚本实际运行,hashbang行就无效了

如果您需要编写不依赖Bash功能的完全可移植的脚本,则另一种选择是更改脚本,使其在没有脚本的情况下也可以工作。您使用的Bash功能包括:

  • 一个数组。首先出现,因此这是您尝试使用Dash shell运行脚本时产生错误的原因。( {a..z} )分配给的带括号的表达式chars创建一个数组,并且${chars[i]}出现在循环中的对其进行索引。
  • 支撑扩展。在Bash中,以及在许多其他Shell中,{a..z}被扩展为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。但是,这不是Bourne样式的shell的通用(或标准化)功能,Dash不支持它。
  • C样式的交替for循环语法。尽管基于算术扩展,但它本身并不特定于Bash(尽管有些非常老的,不符合POSIX的外壳也没有),但是C风格的for循环一种Bash主义,因此无法广泛移植其他炮弹。

Bash广泛可用,尤其是在像Ubuntu这样的GNU / Linux系统上,并且(如您所见)在macOS和许多其他系统上也可用。考虑到您正在使用Bash特定功能的程度,您可能只想使用它们,并且只需确保在运行脚本时使用Bash(或其他支持您使用的功能的Shell)即可。

但是,您可以根据需要用可移植的结构替换它们。数组和C型for循环易于替换;生成字母范围而无需大括号扩展(并且无需在脚本中对它们进行硬编码)是一个棘手的部分。


首先,这是一个可打印所有小写拉丁字母的脚本:

#!/bin/sh

for i in $(seq 97 122); do
    printf "\\$(printf %o $i)\n"
done
  • seq命令生成数字序列。$( )执行命令替换,因此$(seq 97 122)被替换为输出seq 97 122。这些是字符代码az
  • 功能强大的printf命令可以将字符代码转换为字母(例如,printf '\141'印刷品a,后跟换行符),但是代码必须为八进制,而seq输出只能为十进制。因此,我使用了printf两次:内部printf %o $i将十进制数(由提供seq)转换为八进制,并替换为外部printf命令。(尽管也可以使用十六进制,但这并不简单,并且似乎不太方便。)
  • printf\八进制数字解释为带有该代码的字符和\n换行符。但是外壳程序\用作转义符。一个\在前面$将防止$引起膨胀发生(在这种情况下,命令替换),但我不想阻止,所以我与另一个逃脱它\; 这就是的原因\\。不需要转义第二个\n因为与不同\$\n对于双引号字符串中的shell没有特殊含义。
  • 有关在Shell编程中如何使用双引号和反斜杠的更多信息,请参见国际标准中的引用部分。另请参见《Bash参考手册》中的3.1.2报价,尤其是3.1.2.1 换码符3.1.2.3双引号。(这是上下文中的整个部分。)请注意,单引号()也是shell引用语法的重要组成部分,我只是碰巧没有在脚本中使用过它们。'

它可以移植到大多数类似Unix的系统上,而不取决于您使用哪种Bourne风格的shell。但是,seq默认情况下未安装一些类似Unix的系统(它们倾向于使用jot,大多数GNU / Linux系统默认未安装)。如果需要,可以使用带有expr或算术替换的循环来进一步提高可移植性:

#!/bin/sh

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

一个使用while-loop用[命令,继续当唯一的循环$i是在范围内。


您的脚本不是打印整个字母,而是定义一个变量n并打印第一$n个小写字母。这是您的脚本的一个版本,它不依赖于Bash的特定功能,并且可以在Dash上运行,但是需要seq

#!/bin/sh

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

调整n更改的值,如在脚本中一样,可以打印多少个字母。

这是一个不需要的版本seq

#!/bin/sh

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

那里比应该打印的最后一个字母的字符代码$stop一个,因此我在命令中使用(小于)而不是(小于或等于)。(它也会在制造和使用上起作用)。-lt-le[stop=$((i + n - 1))[ $i -le $stop ]


1
这是比您的教育要详细得多的答案。我是一个初学者,所以我编写脚本的方式是将互联网上找到的工作元素放在一起,直到它起作用为止。我毫不怀疑有1)更好的方法和2)更简单的方法来执行我用脚本创建的事情,而上述方法在帮助方面大有帮助。
denski

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.