将连续数字分组在shell中

时间:2017-09-19 14:50:43

标签: linux shell awk

$ foo="1,2,3,6,7,8,11,13,14,15,16,17"

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

3 个答案:

答案 0 :(得分:2)

给出以下功能:

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

答案 1 :(得分:1)

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

答案 2 :(得分:0)

作为替代方案,您可以使用此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用于存储需要打印的范围的起点
  3. 工作原理:

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