在Bash中,如何将数字列表转换为数字范围?

7

目前我有一个从命令中输出的数字排序结果:

18,19,62,161,162,163,165

我想将这些数字列表压缩成单个数字或数字范围的列表:

18-19,62,161-163,165

我考虑在Bash中对数组进行排序并读取下一个数字以查看它是否为+1... 我有一个PHP函数基本上做了同样的事情,但是我在将其转换为Bash时遇到了问题:

foreach ($missing as $key => $tag) {
    $next = $missing[$key+1];
    if (!isset($first)) {
        $first = $tag;
    }
    if($next != $tag + 1) {
        if($first == $tag) {
            echo '<tr><td>'.$tag.'</td></tr>';
        } else {
            echo '<tr><td>'.$first.'-'.$tag.'</td></tr>';
        }
        unset($first);
    }
}

我想可能有一个bash的一行命令可以做到这一点,但我的谷歌搜索结果很少...

更新:
感谢@Karoly Horvath的快速回答,我用它完成了我的项目。我对其他更简单的解决方案非常感兴趣。

1
“但我在将其转换为Bash时遇到了困难” - 你哪些部分有问题? - Karoly Horvath
你想为这个问题发起一个代码高尔夫比赛吗?:-) - gniourf_gniourf
@KarolyHorvath,我遇到的问题是将列表转换为数组,对数组进行迭代和输出,只是不太熟悉语法。 - jjclarkson
1
不要使用数组。你可以1)一个一个地读取它们;2)不要尝试读取下一个值,而是记住旧值。sed "s/,/\n/g" - Karoly Horvath
在Bash中,[是一个命令,因此需要在其后加上一个空格。此外,算术运算需要特殊的语法:if [ $prev -ne $((n+1)) ]或者if (( prev != n+1 )) - glenn jackman
2个回答

6

是的,shell会进行变量替换,如果prev未设置,则该行将变为:

if [ -ne $n+1] 

这里是一个可用的版本:

numbers="18,19,62,161,162,163,165"

echo $numbers, | sed "s/,/\n/g" | while read num; do
    if [[ -z $first ]]; then
        first=$num; last=$num; continue;
    fi
    if [[ num -ne $((last + 1)) ]]; then
        if [[ first -eq last ]]; then echo $first; else echo $first-$last; fi
        first=$num; last=$num
    else
        : $((last++))
    fi
done | paste -sd ","

18-19,62,161-163,165

2
我可以推荐使用 tr 而不是 sed 进行单字符转换吗?tr , '\n' (同样,您可以使用参数扩展并跳过子shell。) - kojiro
@kojiro:没错,那样做是可行的。(老实说,如果我写shell脚本,我从来不担心性能问题...如果这很重要,我会选择另一种语言。顺便说一下,我永远不会用shell脚本来实现这个,但是嘿,这就是OP所要求的 :) - Karoly Horvath
2
使用 IFS=, 而不是 sed 怎么样? - xiaoyi
2
@xiaoyi 是的,或者一开始就把数字放在一个数组中,然后使用 for - kojiro
很棒的脚本!对于以行分隔而不是逗号分隔的数字列表(如果您正在处理表格,则通常如此),请在列表文件底部保留额外的换行符,并将前两个命令替换为:cat numbers.txt | while .... - Nikhil VJ

0

只需在Bash中使用一个函数:

#!/bin/bash

list2range() {
  set -- ${@//,/ }       # convert string to parameters

  local first a b string IFS
  local -a array
  local endofrange=0

  while [[ $# -ge 1 ]]; do  
    a=$1; shift; b=$1

    if [[ $a+1 -eq $b ]]; then
      if [[ $endofrange -eq 0 ]]; then
        first=$a
        endofrange=1
      fi
    else
      if [[ $endofrange -eq 1 ]]; then
        array+=($first-$a)
      else
        array+=($a)
      fi
      endofrange=0
    fi
  done

  IFS=","; echo "${array[*]}"
}

list2range 18,19,62,161,162,163,165

输出:

18-19,62,161-163,165

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接