固定幅ファイルをCSVに変換し、末尾のスペースを削除します。

固定幅ファイルをCSVに変換し、末尾のスペースを削除します。

私の入力ファイルは次のとおりです

$ cat -e myfile.txt 
999a bcd efgh555$
 8 z         7  $
1  xx xx xx  48 $

列に末尾のスペースがないCSVが必要です。

999,a bcd efgh,555
 8,z,7
1,xx xx xx,48

これまでに必要な場所に昏睡状態を正常に追加しました。

$ gawk '$1=$1' FIELDWIDTHS="3 10 3" OFS=, myfile.txt
999,a bcd efgh,555
 8 ,z         ,7  
1  ,xx xx xx  ,48 

末尾のスペースをどのように削除できますか?

編集する:データにすでにカンマがある可能性があるため、次のことを行う必要があります。 (i)フィールドを二重引用符で囲みます。 (ii) 以下を使用します\"(または""以下に従います.RFC 4180)。たとえばa,aab"bbccc-> "a,aa","b\"bb","ccc"

  • gawk(同様にawk)を使用できます。
  • 他のソリューション(たとえば)も開いていますperl
  • gawk ... | sed ...処理する大容量ファイルが多いため、効率的なソリューション(例:not)が必要です。
  • フィールドの幅を知っているので、FIELDWIDTHS自動計算は不要です。

答え1

そしてperl

<your-file perl -C -lnse 'print map {s/\s+$//r} unpack "a3a10a3"' -- -,=,

unpack()gawkと同じ処理を行いますFIELDWIDTHS

$,ここで、awkに対応するものはwithにOFS設定され、パラメータがに割り当てられていることがわかります。または、awkのようにこれを省略して最初にステートメントを追加することもできます。,-,=,-s-var=valuevalue$var-sBEGIN{$, = ","}BEGIN{OFS = ","}-v OFS=,

-Cロケールが文字マップとしてUTF-8を使用している場合、入力はUTF-8エンコードとして扱われ、最近ではほとんど使用されていないさまざまなマルチバイト文字マップを持つロケールは無視されます。

見つかったとおりに切り取りたい空白文字がすべてASCII文字である場合は、末尾のASCII空白(およびNUL)を削除するのではなく、A指定子を使用して単純化できます。aunpack()

<your-file perl -C -lnse 'print unpack "A3A10A3"' -- -,=,

それは文字数を考えてみてください。

バイト数は削除してください-C

子牛クラスターの数unpack "a3a10a3"はで置き換えることができます/^(\X{3})(\X{10})(\X{3})/

表示幅の場合、各文字の幅(幅0、単一幅、および二重幅文字を含む、TAB1、CR ...などの制御文字はサポートされていません)を考慮して、次のことがzshできます。 :

widths=(3 10 3)
while IFS= read -ru3 line; do
  csv=()
  for width in $widths; do
    field=${(mr[width])line}
    line=${line#$field}
    csv+=("${(M)field##*[^[:space:]]}")
  done
  print -r -- ${(j[,])csv}
done 3< your-file

ライトパッドの場合、r[width] rテキストを指定された幅にカットする場合、mこれは文字数ではなく表示幅に基づいて行われ、${(M)field##*[^[:space:]]}モードに基づいて前半まで拡張されます。空白(同じ必要はありません)。$fieldM${field%%[[:space:]]#}set -o extendedglob

おそらくよりもはるかに遅くなりますperl

ファイルにASCIIテキストのみが含まれている場合(例のように)、すべて同じでなければなりません。その後、-Cforを削除するか、perlロケールをC/に設定するPOSIXsedパフォーマンスが向上する可能性があります。gawkperl

UTF-8ロケールで入力が100000回繰り返されました。ここでは、1.1秒perl(変形0.34 A、変形1.7 \X)、Paulの1.3秒gawk、zsh 31秒、GNU sed 's/./&,/13;s/./&,/3;s/[[:space:]]*,/,/g;s/[[:space:]]*$//'(標準)2.1秒、1.1はsed -E 's/^(.{3})(.{10})/\1,\2,/;s/\s+,/,/g;s/\s+$//'(非標準)です。

C言語環境では、それぞれ0.9(0.27、1.2)、0.7、31、1.3、0.5になります。


これらのフィールドには、,または"文字が含まれていないと仮定します。一部のCSV形式には、先頭または末尾のスペースを含む引用フィールドも必要です。

正しいCSV出力を生成するための最も簡単な方法は、Text::CSV次のモジュールを使用することですperl

<your-file perl -C -MText::CSV -lne '
  BEGIN{$csv = Text::CSV->new({binary => 1})}
  $csv->print(*STDOUT, [unpack "A3A10A3"])'

基本的に、

  • 区切り記号は次のとおりです。,
  • 引用符は"..."
  • """引用符で脱出
  • 参照が必要なフィールドのみ参照

しかし、これはnew()即座に調整することができます。perldoc Text::CSV詳細より。


1特にTABの場合、入力を前処理してexpandこれらのTABを他の項目の空白シーケンスに変換できますが、この概念は次のとおりです。適用が難しく、テキストが送信される表示装置に依存することが多い。

答え2

$ cat txx
9  a bcd     55 # <- 1 trailing space here
48 z         7  # <- 2 trailing spaces here
1  xx xx xx  489
aaabbb   bb bccchh

$ awk 'BEGIN { FIELDWIDTHS="3 10 3"; OFS=","; }
{ for (f = 1; f <= NF; ++f) sub (/[[:space:]]*$/, "", $f); print; }' txx
9,a bcd,55
48,z,7
1,xx xx xx,489
aaa,bbb   bb b,ccc

答え3

文字列操作と正規表現を使用して、項目の末尾にある1つ以上のスペースを一致させることができます。ただし、これはすべてのフィールドに対して反復を意味します。

これを行うには、まずFIELDWIDTHS次のように分割するレコードをトリガーする必要があります。$1=$1

awk 'BEGIN { FIELDWIDTHS="3 10 3" ; OFS=","}
     {$1=$1 ;
     for (i=1;i<NF;i++) {$i=gensub(/ +$/,"","g",$i)}}
     1' infile

ネペレ

9  abc d     55 
48 z         7  
1  x x x   xx489

出力

9,abc d,55 
48,z,7  
1,x x x   xx,489

答え4

  • 処理する大容量ファイルが多いため、効率的なソリューション(たとえば、gawk ... | sed ...ではない)が必要です。

動作するソリューションの場合、実際には次のことをお勧めします。考える管路。

実際、パイプライン内の2つ以上のコマンドには独自の設定コストとパイプラインを流れるデータの継続的なオーバーヘッドがありますが、実際にはマルチスレッドマルチコアCPUの場合、パイプラインは互いに同時に実行される可能性が非常に高いです。したがって、パフォーマンスの観点からは、(合理的に)できるだけタスクをパイプラインに分割する方が良いでしょう。

したがって、perlStephane Chazelasのソリューションほどきれいではありませんが、コンパクトです(そして合理的に高速です)。オリジナルより簡単な要件) 効率を高めるために、次の点も考慮することができます。

(あなたの元のリクエストに応じて)

<input-data gawk '{$1=$1; print}' FIELDWIDTHS="3 10 3" OFS=, | gsed 's/ *,/,/g' | gsed 's/ \+$//'

(二重引用符フィールドと埋め込み二重引用符エスケープの追加要件)

<input-data gawk '/"/{for(i=1;i<=NF;i++) gsub(/"/, "\"\"", $i)} {$1=$1; print}' FIELDWIDTHS="3 10 3" OFS='","' | gsed 's/ *","/","/g' | gsed 's/ *$/"/;s/^/"/'

ここでは、ジョブをパイプラインの各ブランチごとに1つずつ3つのワークブロックに分割し、おそらくワークブロックは別々のCPUスレッド/コアで実行されます。

私のクアッドコアシステムでは、最初のパイプライン(元の要件)は、/dev/null入力サンプル(300,000行など)の100,000倍を噛むのに常に0.21秒(リダイレクト)がかかり、10Mxサンプル(30Mライン)を消化します。同じ20秒かかります。比較のために、Stephaneはperl -C -lnse 'print unpack "A3A10A3"' -- -,=,同じシステムでそれぞれ0.31秒と31秒かかります。つまり、マルチバイトサポートの場合、すべてが同じ場合は約50%遅くなりますが、非perlバージョン-C(マルチバイト文字をサポートしていない)は常に0.23秒と23秒かかります。これは上記の最初のパイプラインよりも10%遅いです。繰り返しますが、// -gawk唯一のソリューションは100%から200%遅いです。sedruby最速で(つまり、マルチバイト文字はサポートされていません)設定。

更新された要件により現実的です。上記の2番目のパイプラインはそれぞれ0.26秒と25秒かかりますが、Text perl:: CSV(Text :: CSV_XSに加速されます)ソリューションはそれぞれ0.76秒と1分17秒かかります。

C上記の例では、GNUツール(BSDツールは異なる場合があります)を使用すると、追加の速度を得るためにロケールを設定する必要さえないことがわかります。代わりに、私は剪定が必要であるという事実を利用しました。スペースただし、他の種類の空白文字は使用されていないため、単純に使用されますs/ */。明らかにCロケールが正しく設定されていない場合、データは可能なマルチバイト文字をサポートします。文字セットs/[[:space:]]*/(または同様の文字セット)を使用するs/\s*/ときに優れたパフォーマンスを維持するには、関連するすべてのスペース類似文字をに明示的に指定することをお勧めします。s/ */C[]

おそらく、シングルツールソリューションをさらに圧縮して追加の速度を得ることができます(特にperl確実に最適化できるText :: CSVソリューション)、基本的にまたはプログラムでマルチコアサポートを提供できない場合、パイプラインははい考慮する価値がある「速くて便利」パフォーマンス上の利点、少なくともいつ:

  1. 複数のタスクでアクションブロックを識別でき、各アクションブロックに対してより専門的なツールを選択できます。
  2. 使用可能なCPUコア/スレッドの数を超えることなく、できるだけ多くのパイプラインアームに分割できます。

gawkポイント1の例として、私はパイプラインの最初の部分にあなたのものを使用しました。その理由は、sedタスクの特定の部分をはるかに高速に実行するためであり、gawkパイプラインの2番目の部分ではパフォーマンスが30M行ほど低下するためです。サンプルデータの処理には20〜35秒かかります。さらに、更新要件のパイプラインはこれをgawk2つのサブタスクに分割しませんでした。なぜなら、それをそのまま維持することは、固定幅のフィールドを得るために私が考えることができる最も簡単で効率的な方法です。しかし、入力データに存在できる二重引用符をエスケープします。

ポイント2の例として、sed2つのsコマンドを実行する最後のコマンドに注意してください。私のコンピュータで利用可能なCPUコアの数と同じであることを避けるために、もはや分割されていません。さらに分割すると、パフォーマンスは同じであり、さらなる改善はありません。

ただし、データを入力するために必要な要件が多いほど、パイプラインがより複雑になり、厄介になり、エラーが発生しやすくなり、単一のツールソリューションよりも複雑になる可能性があります。


1.各パイプラインのアームが別々のCPUコアによって実行されることを確認するコマンドがありますが、最新のカーネルではコアの再割り当てが自動的に発生するため、これは通常必要ありません。

関連情報