App-Bin4TSV

 view release on metacpan or  search on metacpan

scripts/csel  view on Meta::CPAN

#!/usr/bin/perl -T
use 5.008 ; use strict ; use warnings ; # 5.011
use Getopt::Std ; getopts '~i:=!d:0:h:np:rt:' , \my %o ; 
#use List::MoreUtils qw/any none/ ; 
use List::Util qw [ max ] ;
use Encode qw/decode_utf8/ ; 
use Memoize ; 

use Data::Dumper ;
use Term::ANSIColor qw[ :constants] ; $Term::ANSIColor::AUTORESET = 1 ;

memoize ( 'sCols' ) ; # このプログラムに現れる sCols 関数を高速化する。 
$| = 1 if $o{'!'} ; 

my %cream ; # 列の番号の配列を格納する。
 # $cream{p} 出力するよう指定した列番号の無名配列
 # $cream{d} 削除するよう指定した列番号の無名配列
 # $cream{h} 先頭に移動するように指定した列番号の無名配列
 # $cream{t} 末尾に移動するように指定した列番号の無名配列
my $emp = $o{'0'} // '' ; ; # 存在しないセルを参照したときに表示する文字列
my %colNamePos ; # 列の名前から、1始まりの列番を参照する
my $isep = $o{i} // "\t" ; # 入力の区切り文字
my $split_limit = $isep eq '' ? 0 : -1 ; # split で使う。split//はLIMIT=0にしないと、結果に空文字列の値が最後に生じる
my $osep = $isep eq '' && $o{n} ? ' ' : $isep ; # 出力の区切り子


& init ; # オプションについての処理 
& main ;  # メインの処理  printColsMain
exit 0 ;

#  初期化処理の為の展開の処理

sub expand ( $ ) { # まず コンマで分割, # 次に .. を展開する。 # 最後に正の数は 1を引く。
  #print BRIGHT_BLUE Dumper ["cNP=",%colNamePos] ; 
  return () if ! defined $_[0] ;
  my @gr = split /,/ , $_[0]  ; # grain 穀物の粒のつもり。expandedも考えたが、スペル長過ぎ。
  my $l = scalar @gr ; 
  for ( reverse 0 .. $#gr ) { 
    if ( $gr [ $_ ] =~ m|(.+)\.\.(.+)| ) {  # *末尾* から数字を展開する。
      my ($c1,$c2) = ($1,$2) ; 
      if ( $o{'='} ) { $c1 = $colNamePos { $c1 } ; $c2 = $colNamePos { $c2 } } #; print "\$c1=$c1, \$c2=$c2\n" } ;
      splice @gr , $_ , 1 , ( $c1 <= $c2 ? $c1 .. $c2 : reverse $c2 .. $c1 ) ;  
    } else  { 
      $gr[ $_ ] = $colNamePos { $gr[ $_ ] } if $o{'='} ;
    }
  }
  #print CYAN Dumper [@gr], Dumper  ;
  #print CYAN join ", " , @gr ;
  #@gr = grep { $_ ne 0 } @gr ; # 0は取り除く。$0 が -ptdhの引数に0を与えるのは、ヘルプの抑制が主目的となる。  
  grep { $_ = $_ - ( $_ > 0 ? 1 : 0 ) } @gr ;  # 1始まりで与えられた列番号を0始まりにする。ただし、負の数は変えない。
  return @gr ; 
}

#  初期化処理。コマンド引数のオプションについての処理 ; 2個の関数
sub init { 
    if ( ! scalar grep { defined }  @o{qw/p d h t n/} ) { *STDOUT= *STDERR ; &HELP_MESSAGE ; exit  } 
}


# 列数ごとに、どの列を見せるかの列番の無名配列を返す。
sub sCols ( $ ) {

  sub none ( &@ ) { my $f = shift; foreach ( @_ ) { return 0 if $f->(); } return 1 ; }
  sub drop ( $@ ) { my$o = shift @_ ; return grep { my$x=$_;none{$x==$_} @_ } @{$o} } ; # []

  my $W = $_[0] ; # 各行の、セル(列) の個数が与えられる。
  $cream {p} = [ 0 .. $W - 1 ] if ! defined $o{p} ; 
  my @p_ = @{ $cream{p} } ; 
  my @d_ = @{ $cream{d} } ; 
  my @h_ = @{ $cream{h} } ; 
  my @t_ = @{ $cream{t} } ; 
  $_ =  $W == 0 || abs ($_) >= $W && ! $o{r} ? -1 : $_ % $W for @p_ , @d_ , @h_ , @t_ ; # -r指定で単純にサイクリック
  @p_ = drop  \@p_ , @d_ , @h_ , @t_  ;
  @h_ = drop  \@h_ , @d_  ;  # -h,-tで指定された列でも、-dで指定されたものは消す。。
  @t_ = drop  \@t_ , @d_  ;  # 
  my @out = (@h_ , @p_ , @t_) ;

  #print join ". " , @{ $cream{p}} , "#" ; # map ( $_ // "u" , @out, @{$cream{p}}) , "\n" ;
  if ( $o{'~'} ){ 
    my @tmp = (-1) x ( 1 + max (@out , @{$cream{p}}   ) )  ; # = map { $out[$_] } 0 .. $#out ; 
    for ( 0 .. $#out ) { $tmp [ $out[$_] ] = $_ }
    @out = @tmp ; #print join ", " , map ( $_+1, @out) , "\n" ;
  }
 
  return [ @out ] ; 
}


# 各行の出力処理。 sCols関数を呼び出して、選択する。-n で列番号も付ける。
sub line ( ) { 
  chomp ; 
  $_ = decode_utf8 ( $_ ) if $isep eq '' ; # 入力がSTDINとは限らないので binmode を使わず decode_utf8
  my @F = split /$isep/ , $_ , $split_limit   ; #use Data::Dumper ; print scalar @F , Dumper [@F];  
  @F = map { $_ + 1 . ":$F[$_]" } 0 .. $#F  if $o{n} ;   # -n で列番号をコロンを付けて出力
  push @F , $emp ; # $F[-1] で参照する
  print join ( "$osep" , @F [ @{ sCols $#F } ] ) , "\n" ; 
}

# main 
sub main { 

  binmode STDOUT ,":utf8" if $isep eq ''  ; # 文字単位で処理する場合、utf8としての扱いをする。
  # 一行目かつ -= が指定された場合の処理
  if ( $o{'='} ) { 
      $_ = <> ; 
      chomp ;  
      my $c = 0 ;
      $colNamePos { $_ } = ++ $c  for split /$isep/ , $_ , $split_limit ; # <-- 分割文字列に気をつけたい
      $cream{ $_ } = [ expand $o{$_} ] for qw/p d h t/ ;    
      line ;  # <-- - 
  }
  else { 
    $cream{ $_ } = [ expand $o{$_} ] for qw/p d h t/ ;    
  }
  
  #print BRIGHT_GREEN Dumper [%colNamePos] ; 
  # 引き続く処理。-=が無い場合には、ここらか始まる。
  while ( <> ) {
    line 
  } ;

}






## ヘルプの扱い
sub VERSION_MESSAGE {}
sub HELP_MESSAGE {
    use FindBin qw[ $Script ] ; 
    $ARGV[1] //= '' ;
    open my $FH , '<' , $0 ;
    while(<$FH>){
        s/\$0/$Script/g ;
        print $_ if s/^=head1// .. s/^=cut// and $ARGV[1] =~ /^o(p(t(i(o(ns?)?)?)?)?)?$/i ? m/^\s+\-/ : 1;
    }
    close $FH ;
    exit 0 ;
}

=encoding utf8

=head1

  csel -- Easier interface than cut/AWK to handle TSV/CSV input,
  with the options such as -p(Printing)/-d(Deleting)/-h(moving to Head)/-t(moving to Tail).
 

    AWK言語やコマンドcutよりも列の抽出を簡単に書けるコマンドラインを提供する。

概要: 



( run in 1.504 second using v1.01-cache-2.11-cpan-39bf76dae61 )