【问题标题】:Group the consecutive numbers in shell将shell中的连续数字分组
【发布时间】:2018-04-16 19:28:48
【问题描述】:
$ foo="1,2,3,6,7,8,11,13,14,15,16,17"

在shell中,如何将$foo中的数字分组为1-3,6-8,11,13-17

【问题讨论】:

  • 一般来说,单行字会妥协一些东西。也许是可读性,也许是正确性,也许是极端情况的处理——但几乎总是需要权衡。
  • 顺便说一句——你试过自己做这个吗?你有任何反映这种尝试的代码吗?你在哪里卡住了?
  • shell 不好,尝试转换stackoverflow.com/questions/15867557/… 失败:(
  • 嗯。那里给出的代码将发出大小为 2 的范围为 6-7 而不是 6,7。你更喜欢这些行为中的哪一种?
  • ...也就是说,如果您不想要这种逻辑,您可以注释掉在emit_range 中创建它的elif 条件。

标签: linux shell awk


【解决方案1】:

给定以下函数:

build_range() {
  local range_start= range_end=
  local -a result

  end_range() {
      : range_start="$range_start" range_end="$range_end"
      [[ $range_start ]] || return
      if (( range_end == range_start )); then
        # single number; just add it directly
        result+=( "$range_start" )
      elif (( range_end == (range_start + 1) )); then
        # emit 6,7 instead of 6-7
        result+=( "$range_start" "$range_end" )
      else
        # larger span than 2; emit as start-end
        result+=( "$range_start-$range_end" )
      fi
      range_start= range_end=
  }

  # use the first number to initialize both values
  range_start= range_end=
  result=( )
  for number; do
    : number="$number"
    if ! [[ $range_start ]]; then
      range_start=$number
      range_end=$number
      continue
    elif (( number == (range_end + 1) )); then
      (( range_end += 1 ))
      continue
    else
      end_range
      range_start=$number
      range_end=$number
    fi
  done
  end_range
  (IFS=,; printf '%s\n' "${result[*]}")
}

...调用如下:

# convert your string into an array
IFS=, read -r -a numbers <<<"$foo"

build_range "${numbers[@]}"

...我们得到输出:

1-3,6-8,11,13-17

【讨论】:

  • 为什么在作业前使用:
  • @BenjaminW.,这些是用于set -x 的调试语句,因此在跟踪代码时可以看到值。
  • 我不确定我是否理解。我看到你需要它,比如: ${param='value'},所以你不将扩展作为命令执行,而是用于调试?当我使用set -x 运行时,我可以看到没有: 的行的作用。
  • @BenjaminW.,没有set -x,它什么都做 - 这就是重点。在您需要在跟踪中查看变量值之前,这是一个 noop,而不是分配。 : 旨在向读者说明这一点(这是一个没有副作用的声明,可以被注释掉或删除而没有效果)。
  • 哦,我知道了。我使用 bashdb ;)
【解决方案2】:

awk 扩展样本的解决方案:

foo="1,2,3,6,7,8,11,13,14,15,16,17,19,20,33,34,35"

awk -F',' '{
                r = nxt = 0; 
                for (i=1; i<=NF; i++) 
                    if ($i+1 == $(i+1)){ if (!r) r = $i"-"; nxt = $(i+1) } 
                    else { printf "%s%s", (r)? r nxt : $i, (i == NF)? ORS : FS; r = 0 }
           }' <<<"$foo"

输出:

1-3,6-8,11,13-17,19-20,33-35

【讨论】:

    【解决方案3】:

    作为替代方案,您可以使用以下 awk 命令:

    cat series.awk
    function prnt(delim) {
       printf "%s%s", s, (p > s ? "-" p : "") delim
    }
    BEGIN {
       RS=","
    }
    NR==1 {
       s = $1
    }
    p < $1-1 {
       prnt(RS)
       s = $1
    }
    {
       p = $1
    }
    END {
       prnt(ORS)
    }
    

    现在运行它:

    $> foo="1,2,3,6,7,8,11,13,14,15,16,17"
    $> awk -f series.awk <<< "$foo"
    1-3,6-8,11,13-17
    
    $> foo="1,3,6,7,8,11,13,14,15,16,17"
    $> awk -f series.awk <<< "$foo"
    1,3,6-8,11,13-17
    
    $> foo="1,3,6,7,8,11,13,14,15,16,17,20"
    $> awk -f series.awk <<< "$foo"
    1,3,6-8,11,13-17,20
    

    这里是做同样事情的单行:

    awk 'function prnt(delim){printf "%s%s", s, (p > s ? "-" p : "") delim}
    BEGIN{RS=","} NR==1{s = $1} p < $1-1{prnt(RS); s = $1} {p = $1}END {prnt(ORS)}' <<< "$foo"
    

    在这个 awk 命令中,我们保留 2 个变量:

    1. p 用于存储上一行的编号
    2. s 用于存储需要打印的范围的开始

    工作原理:

    1. NR==1 我们将s 设置为第一行的编号
    2. p 小于 (current_number -1) 或$1-1 时,表明我们有一个序列中断,我们需要打印范围。
    3. 我们使用函数prnt 进行打印,它只接受一个作为结束分隔符的参数。当 prntp &lt; $1-1 { ...} 块调用时,我们将 RS 或逗号作为结束分隔符传递,当它从 END{...} 块调用时,我们将 ORS 或换行符作为分隔符传递。
    4. p &lt; $1-1 { ...} 内部,我们将s(起始范围)重置为$1
    5. 处理完每一行后,我们将$1 存储在变量p 中。
    6. prnt 使用 printf 进行格式化输出。它总是首先打印起始编号s。然后它会检查是否是p &gt; s 并打印连字符后跟p 如果是这样的话。

    【讨论】:

    • 如果您更喜欢一种衬里,请使用:awk 'function prnt(delim){printf "%s%s", s, (p &gt; s ? "-" p : "") delim} BEGIN{RS=","} NR==1{s = $1} p &lt; $1-1{prnt(RS); s = $1} {p = $1}END {prnt(ORS)}' &lt;&lt;&lt; "$foo"
    • 能否请您将此 oneliner 也添加到解决方案部分并解释一下?谢谢,这有帮助。
    • 非常感谢,也看看能不能回答下其他相关问题:stackoverflow.com/questions/46288292/…
    • 好的,我已经添加了解释,现在查看您的其他问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-20
    • 1970-01-01
    • 2022-01-03
    • 2015-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多