Bash:我可以在不使用"eval"的情况下计算非恒定范围内的数字的格式字符串吗?



我对必须使用eval感到不高兴,但由于{a..b}语法的限制,我尝试的其他一切都失败了。这是我的,是的我知道我可以把这两个循环结合起来,但eval已经够难看的了。

cores=""
suffixes=""
np=$(nproc --all)
eval 'for i in {1..'$np'} ; do cores="$cores $i"; done'
for i in $cores ; do
suffixes="$suffixes "$(printf %02i $i)
done

首先,一个激进的现代解决方案:

#!/bin/bash
#      ^^^^- "printf -v" and C-style for loops both require bash, not /bin/sh
np=$(nproc --all)
cores=( )
for ((i=0; i<np; i++)); do
printf -v suffix '%02i' "$i"
cores[$i]=$suffix
done

这会生成一个单独的索引数组:它的键是核心数字,值是后缀字符串。因此,您可以对"${!cores[@]}"进行迭代,以获得核心编号的列表;通过"${cores[@]}"获取后缀字符串列表,或者使用"${cores[$i]}"查找核心$i的后缀。


接下来,一个更接近原始代码的解决方案,为现代bash:构建

#!/bin/bash
#      ^^^^- "printf -v" and C-style for loops both require bash, not /bin/sh
np=$(nproc --all)
cores=""; suffixes=""
for ((i=0; i<np; i++)); do
printf -v suffix '%02i' "$i"
cores+=" $i"
suffixes+=" $suffix"
done

您也可以只在数组中构建核心数字,并在一个步骤中计算后缀数字:

# read cores from string into an array to allow safe evaluation even with unknown IFS
IFS=' ' read -r -a cores_arr <<<"$cores"
# ...and expand the full array, repeating the format string for every element
printf -v suffixes '%02i ' "${cores_arr[@]}"

值得注意的是:

  • 在扩展数组(即for i in $cores)上迭代通常是不好的做法——如果您的值保证仅为数字,则可能是安全的,但要注意副作用:

    • 字符串中的Glob表达式被展开:如果你的数据中有一个*,你会发现自己在当前目录中的文件上迭代
    • 字符串分割不允许像数组那样对元素边界进行细粒度控制。可以使用array=( "item one" "item two" )来存储两个项,两个项的名称中都有空格;如果你尝试设置string=' "item one" "item two" ',你会得到"item作为一个单词,one"作为第二个单词,等等

    因此,在数组元素上迭代——即使这意味着从字符串读取到数组中——也是非常可取的。

  • 在任意数量的项目上循环最好使用C样式的for循环。

  • 在上述内容中,除了nproc之外,没有其他外部命令。这意味着我们不依赖非POSIX工具,如seq
  • 使用printf -v suffixprintf执行的字符串格式化操作的结果直接写入名为suffix的变量。(除此之外:ksh93没有printf -v,但可以识别在$()中使用printf并避免子shell惩罚)。请参阅printf上的bash黑客页面
  • 因此:除了运行所述外部命令并获取其输出所需的子shell之外,没有其他子shell。(在使用mkfifo()生成FIFO以捕获其输出后,每个子shell都需要fork()关闭shell的另一个副本;读取该输出;wait()退出子shell,等等;因此,它们最好保持在紧密循环之外)

相比之下,如果您需要与POSIX-sh兼容,那么我们仍然有$(( )),但没有(( ))(除了数学上下文之外,没有+=操作,并且根本没有C样式的for循环)。这给我们留下了:

#!/bin/sh
build_suffix() {
np=$1; i=0
while [ "$i" -lt "$np" ]; do
printf '%02i ' "$i"
i=$((i+1))
done
}
suffixes=$(build_suffix "$(nproc --all)")

这给了我们一个答案,正好有两个子shell,无论我们循环多少次,通过将整个循环放在一个子shell中。

suffixes=''
np="$(nproc --all)"
for ((i=0; i <$np; ++i)); do suffixes="$suffixes $(printf %02i $i)"; done

感谢大家的投入。它给了我一些思考的东西。我已经决定替换for循环并调用printf(1):

np=$(nproc --all)
suffixes=$(eval "echo {01..$np}")

它很简洁,引用也很明显。我仍然对eval不太满意,但它没有使用不可信的数据,而且在我所做的整体方案中,它只贡献了非常(非常!)少的运行时间。

最新更新