问题 我正在尝试解决/增强提供数字序列的 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::IntSpan
libset-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