所以我尝试这样做
for x in {1..7000}
do
echo $x
done
输出是{1..7000}
我还了解到 C 风格的 for 循环在 mksh 中不起作用
我做了一些谷歌搜索,但主要是关于 ksh 的信息,而不是 mksh
那么在 mksh 中迭代范围的正确方法是什么?
笔记:我对创建迭代和类似 C 的行为的正确方法更感兴趣,而不是打印字符串。
答案1
所以,首先,按原样这里简要讨论常见的形式...
for arg in {brace..expanded..set}; do ...
...在我看来,这是 shell 编程错误的典范。它在循环中生成一个公式集,将每个结果存储在一个分隔数组中,然后传递给另一个循环将数组表示为要迭代的列表。
作为一般规则,括号扩展在编程环境中并不是特别有用,因为所有 shell 编程都以某种方式宏观的和大括号扩展已经是一个宏。虽然大括号扩展是(我也认为)A很有用交互式 shell 功能正是因为它是一个宏,否则可能会造成浪费。
支撑扩展也不是便携式的(阅读:POSIX-便携式)。据我所知,您通常可以依赖它在zsh
、bash
、yash
、的当前版本中工作,但在其他地方则不行。ksh93
即使在这些 shell 中,也可以使用 shell 选项禁用它,因此在 shell 函数上下文中不能绝对依赖它,尽管通常可以在$-
或 的输出中测试它的可用性set +o
。在它们之间,上述每个 shell 都需要不同类型的测试,因此如果您计划在函数中使用它并想要对其进行测试,请检查目标 shell 的手册以了解具体信息。
在其他 shell 中,您可以通过拆分命令替换的输出来获得几乎相同的行为 - 除了必须调用外部可执行文件来生成该集合之外$IFS
。在该seq
程序可用的情况下,这很容易被证明,例如......
unset IFS
### ^ensure $IFS default behavior
for arg in $(seq 7000)
do printf %d\\n "$arg"
done
...或者...
unset IFS
printf %d\\n $(seq 7000)
...但是,正如所写,除了缺点之外,两者都没有提供任何其他内容...
seq 7000
尽管如此,对于第一个示例,该方法与大括号扩展版本几乎相同。在这两种情况下,其他一些函数或进程都会生成一个完整的参数列表,for
然后控制构造会对其进行迭代。但在后一种情况下,(对于除 之外的外壳ksh93
)还有管道打开/拆卸、分叉 shell 进程及其子进程seq
以及最后在$IFS
.
$IFS
顺便说一句,根据所使用的外壳,分裂发生的速度非常不同。bash
例如,在简单的$IFS
分割性能方面通常很糟糕,而dash
- 取决于分割字符串的行长度 - 通常可以像其他 shell 进行大括号扩展一样快。
不过,无论您使用大括号扩展还是$IFS
拆分,最终结果都是您将运行一些循环来生成公式列表,然后运行另一个循环来迭代该列表。相反,您可能 - 并且可能应该 - 做的是对生成列表的公式的应用程序运行一个循环,尽快处理使用过的迭代器,并测试每次迭代的结果令人满意的结果。你会在 C 中做这样的事情......
for ( x = 0; x < 10; x++ ){ ...
这实际上也与您可以在bc
.它非常像方便的语法形式,可以在ksh93
,zsh
或bash
类似中使用:
for (( x=0; x<10; x++ )); do ...
然而,这也不是便携式的(再次阅读:POSIX)。相反,您可以便携地执行以下操作:
iterator=$(( start_value - interval ))
while [ "$(( iterator += interval ))" -le "$end_value" ]; do ...
...或者...
iterator=$(( start_value - interval ))
while [ "$(( ( iterator += interval ) <= end_value ))" -ne 0 ]; do ...
POSIX 指定类似于 shell 算术扩展和 C 语言算术运算的功能奇偶校验(即涉及整数的情况)。因此,您通常可以方便且可移植地迭代复杂的公式,只需一次扩展 - 甚至可以同时同步迭代多个变量。
另一方面,这样的构造可能会让您很难对抗另一种 C 语言与 shell 语言的不平等。 Shell 读取和写入几乎总是字面上的read()
s 和s - 每个系统调用本身 - 并且在 shell 语法中write()
没有可移植的fread()
,fwrite()
和/或类似物。setbuf()
因此,任何在每次迭代中执行任何此类操作的循环(即使仅使用内置函数执行此操作)都必然会导致性能下降。
不过,有时您可以在 shell 脚本中串行或并行生成和运行整个集合之间找到一个折衷方案。
set 0 1 2 3 4 5 6 7 8 9
for n do printf "$n%d\n" "$@"; done
这write()
在十次迭代中执行了十次,并打印了从 00 - 99 的换行划分序列。这是一个很小的例子 - 而且仍然很浪费,因为 30 字节write()
与 3 字节相比很难有显着改进,write()
但它是这种类型的证明您可能使用的方法。正如我之前提到的,shell 编程语言非常有用宏观的在那里面一切都是字符串。因此,如果您可以将目标集分解为更小、更易于管理的集合,然后在循环上下文中重复调用它们以最终生成目标集,您通常可以获得性能更高的结果。
这是一个更复杂的、类似宏的示例:
sh -c '
i=0 _i=-25
set "$1" "$1" "$1" "$1" "$1"
for n do eval "
printf %b%d $@ $@ $@ $@ $@"
done' -- \
'"\01$((1+(i<(i+=!(_i=(_i+=25)%275)))))" "$i$_i"'
write()
对于平均每个约 100 个字节的内容,它会迭代 5 次并打印:
10 125 150 175 1100 1125 1150 1175 1200 1225 1250
20 225 250 275 2100 2125 2150 2175 2200 2225 2250
30 325 350 375 3100 3125 3150 3175 3200 3225 3250
40 425 450 475 4100 4125 4150 4175 4200 4225 4250
50 525 550 575 5100 5125 5150 5175 5200 5225 5250
60 625 650 675 6100 6125 6150 6175 6200 6225 6250
70 725 750 775 7100 7125 7150 7175 7200 7225 7250
80 825 850 875 8100 8125 8150 8175 8200 8225 8250
90 925 950 975 9100 9125 9150 9175 9200 9225 9250
100 1025 1050 1075 10100 10125 10150 10175 10200 10225 10250
110 1125 1150 1175 11100 11125 11150 11175 11200 11225 11250
120 1225 1250 1275