将序列中的一系列逗号分隔的数字折叠到开始-结束

将序列中的一系列逗号分隔的数字折叠到开始-结束

问题 我正在尝试解决/增强提供数字序列的 BASH 脚本:我正在使用拓扑感知工具(lstopo-no-graphics)来提取物理处理器编号,以用于输入 numactl 进行处理器绑定。

L3 L#4 共享缓存物理核心的输出示例

lstopo-no-graphics --no-io|sed -n "/L3 L#3/,/L3/p"|grep -v "L3\|L2"|tr -s '[:space:]'|cut -d " " -f4|grep -o "[0-9]*"|sort -g|tr '\n' ','|sed '$s/,$//'

结果为数字系列字符串:

32,33,34,35,36,37,38,39,96,97,98,99,100,101,102,103

一切都很好,我使用这个系列作为 Ënumactl --physcpubin=32,33,34,35,36,37,38,39,96,97,98,99,100,101,102,103我希望能够将序列折叠到numactl --physcpubin=32-39,96-103‌ ,希望在连续时将多个逗号分隔的数字序列折叠为“an”系列,每个序列逗号分隔。

我对现有的 bash 脚本没有问题,只是寻找一个更干净的实现(如果有人有任何想法的话)?

答案1

将此另存为range.awk

{
    for(i=2;i<=NF+1;i++){     #Visit each number from the 2nd on
        if($i==$(i-1)+1){
            if(f=="")f=$(i-1) #Candidate to first number of a range
            continue
        }
        printf("%s%s%s%s", f, (f!="" ? "-" : ""), $(i-1), (i>NF ? RS : FS))
        f="" #Unset the candidate
    }
}

运行:awk -F, -f range.awk

或者复制粘贴折叠的单行:

awk -F, '{for(i=2;i<=NF+1;i++){if($i==$(i-1)+1){if(f=="")f=$(i-1);continue}printf("%s%s%s%s",f,f!=""?"-":"",$(i-1),i>NF?RS:FS);f=""}}'

我没有对字段分隔符进行硬编码,因此必须使用 指定它-F

输出示例:

$ awk -F, -f range.awk <<< 32,33,34,35,36,37,38,39,96,97,98,99,100,101,102,103
32-39,96-103
$ awk -F, -f range.awk <<< 0,1,2,5,8,9,11
0-2,5,8-9,11
$ awk -F, -f range.awk <<< 4
4

答案2

使用 perl 单行代码设置::IntSpan模块:

$ perl -MSet::IntSpan -l -e 'print Set::IntSpan->new(shift)' 32,33,34,35,36,37,38,39,96,97,98,99,100,101,102,103
32-39,96-103

这需要一个参数,即命令行上以逗号分隔的整数列表。如果列表中有空格、制表符或其他空白,您可以将其括在引号中。 Set::IntSpan极其忽略数字列表中任意位置的空格,它会忽略所有空格。

如果列表已经包含范围和整数的混合,它将无缝处理它们:

$ perl -MSet::IntSpan -l -e 'print Set::IntSpan->new(shift)' 32,33,34-38,39,96-100,101,102,103
32-39,96-103

Set::IntSpanlibset-intspan-perl在 Debian 和相关发行版(如 Ubuntu)以及Fedora 上的打包方式相同perl-Set-IntSpan。对于其他系统,如果找不到软件包,可以使用cpan.

要在脚本中使用它,您可以使用命令替换:

numactl --physcpubin=$(perl -MSet::IntSpan -l -e 'print Set::IntSpan->new(shift)' 32,33,34,35,36,37,38,39,96,97,98,99,100,101,102,103)

如果您只在脚本中使用一次,这很好,但否则会很乏味并降低可读性。因此,将其包装在 bash 脚本中的函数中(有一个小改进,可以选择在命令行上使用多个参数,如果您想要,例如用 cpu 集填充数组,则很有用):

collapse () {
 perl -MSet::IntSpan -le 'for (@ARGV) {print Set::IntSpan->new($_)}' "$@"
}

然后将其用作:

cpus=$(collapse 32,33,34-38,39,96-100,101,102,103)
numactl --physcpubin="$cpus"

或者

numactl --physcpubin=$(collapse 32,33,34,35,36,37,38,39,96,97,98,99,100,101,102,103)

这是一个更精美的独立脚本版本,可以直接从命令行、命令行上列出的文件或标准输入获取多个参数。或其任意组合。按提供的顺序处理多个参数,最后处理 STDIN。来自文件和 STDIN 的输入一次处理一行。

#!/usr/bin/perl

use strict;
use Set::IntSpan;

my @series = ();

# take args from files and from command line
foreach my $arg (@ARGV) {
  if ( -e $arg ) { # if the arg is a filename, slurp it in
    open(my $fh, "<", $arg) || die "couldn't open $arg: $!\n";
    while(<$fh>) { push @series, $_; }
  } else { # otherwise, treat the arg as a series
    push @series, $arg;
  }
};

# take args from stdin too, if stdin isn't a terminal
if (! -t STDIN) { while(<STDIN>) { push @series, $_; } };

foreach (@series) {
  print Set::IntSpan->new($_) . "\n";
};

另存为,例如collapse.pl,使可执行文件chmod +x collapse.pl并运行如下:

$ printf '1,2,3\n4,5,6' | ./collapse.pl 7,8,9 32-39,50,51,52,53
7-9
32-39,50-53
1-3
4-6

相关内容