カンマで区切られた数字の範囲を始めから終わりまで順に縮小します。

カンマで区切られた数字の範囲を始めから終わりまで順に縮小します。

問題一連の数値を提供するBASHスクリプトを解決/強化しようとしています。プロセッサバインディングのためにnumactlに入力する物理プロセッサ番号を抽出するために、トポロジ認識ツール(lstopo-no-graphics)を使用しています。

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

または、縮小された1行をコピーして貼り付けます。

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 1行のコードを使う設定::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

これは、コマンドラインでコンマで区切られた整数のリストである1つの引数を使用します。リストにスペース、タブ、またはその他のスペースがある場合は、引用符で囲むことができます。 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の入力は一度に1行ずつ処理されます。

#!/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

関連情報