如何在 Bash 中将 `4-7` 替换为 `4,5,6,7`

How to replace `4-7` into `4,5,6,7` in Bash

目标

我有一个长字符串 s,它表示由逗号和破折号分隔的一系列数字(见下文)。当几个数字彼此跟随时,将写入两个极端数字并用破折号分隔。例如,数列4,5,6,7写成4-7。我的目标是扩展这个字符串,让所有数字都用逗号分隔(4-7 应该变成 4,5,6,7)。

我做了什么

这里是字符串的例子

s="4092-4093,4095-4097,4104,4107,4111,4125-4127"

我想先用 {4..7} 替换类型 4-7 的模式(使用 sed 反向引用)

a="$(echo $s | sed 's/\([0-9]*\)-\([0-9]*\)/{..}/g')"

然后评估字符串以扩展大括号

b="$(eval echo $a)"

但是,当我 运行 最后一个命令时,扩展完成 "in a factorial way" (导致 RAM 使用量激增)。

问题

如何将字符串中 4-7 类型的模式替换为 4,5,6,7

版本

我在 Mac OS X 10.11.3 并使用 Terminal 2.6.1 (361.1)

这是因为您的代码并没有按照您的预期行事。仅考虑 s="4092-4093,4095-4097"。在 运行 到 sed 之后,这将导致 a={4092..4093},{4095..4097}。在 运行 到 eval 之后,结果是:

b=4092,4095 4092,4096 4092,4097 4093,4095 4093,4096 4093,4097

我猜你期待这样的事情:

b=4092,4093,4095,4096,4096

如果您没有注意到两者之间的区别,那就是实际结果是两个大括号表达式的所有可能组合。您的实际案例有更多组合,导致 combinatorial explosion.

一个使用 GNU awk 的答案,它应该在大输入下表现更好:

#!/usr/bin/env gawk -f
{
    while ( match([=10=], /([0-9]+)-([0-9]+)/, arr) ) {
        s = arr[1]
        for (i=int(arr[1]) + 1; i<=int(arr[2]); i++) {
            s = s "," i
        }
        gsub(arr[1] "-" arr[2], s)
    }
    print
}

或者,在纯 bash 中(为了使用少量数据获得更好的性能):

s="4092-4093,4095-4097,4104,4107,4111,4125-4127"
re='([0-9]*)-([0-9]*)'
while [[ $s =~ $re ]]; do
  eval_str="printf -v replacement '%s,' {${BASH_REMATCH[1]}..${BASH_REMATCH[2]}}"
  eval "$eval_str"
  replacement=${replacement%,}
  s=${s//${BASH_REMATCH[0]}/$replacement}
done
s="4092-4093,4095-4097,4104,4107,4111,4125-4127"
a="$(echo $s | sed 's/\([0-9]*\)-\([0-9]*\)/{..}/g' | tr "," " ")"
b=""
for i in ${a[@]}
do
   add="$(eval echo $i)"
   b="${b} ${add}"
done    
echo $b

Perl 来拯救:

echo 4092-4093,4095-4097,4104,4107,4111,4125-4127 \
| perl -lane 's/-/../g;print join ",", eval'

在 Perl 中,范围是用 .. 运算符而不是破折号书写的。 运行 eval 将字符串扩展为实际列表。