Bash:我可以在不使用“eval”的情况下评估非常数范围内数字的格式字符串吗?

时间:2017-01-17 21:32:17

标签: bash range interpolation

我不乐意使用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

3 个答案:

答案 0 :(得分:7)

首先,一个积极的现代解决方案:

#!/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-style for loop对任意数量的项目进行循环。

  • 在上文中,除了nproc之外没有外部命令。这意味着我们不依赖于诸如seq之类的非POSIX工具。
  • 使用printf -v suffixprintf执行的字符串格式化操作的结果直接写入名为suffix的变量。 (旁白:ksh93没有printf -v,但承认在$()内使用printf并避免了子shell惩罚)。见the bash-hackers page on printf
  • 因此:除了运行所述外部命令并捕获其输出所需的子设备之外,没有其他子设备。 (在使用fork()生成FIFO以捕获其输出之后,每个子shell都需要mkfifo()另一个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的答案。

答案 1 :(得分:1)

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

答案 2 :(得分:0)

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

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

它简洁明了,引用很明显。我还没有对eval感到兴奋,但是它没有使用不受信任的数据,它在我正在做的整体方案中贡献了非常(非常!)的小运行时间。