私の入力ファイルは次のとおりです
$ 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=value
value
$var
-s
BEGIN{$, = ","}
BEGIN{OFS = ","}
-v OFS=,
-C
ロケールが文字マップとしてUTF-8を使用している場合、入力はUTF-8エンコードとして扱われ、最近ではほとんど使用されていないさまざまなマルチバイト文字マップを持つロケールは無視されます。
見つかったとおりに切り取りたい空白文字がすべてASCII文字である場合は、末尾のASCII空白(およびNUL)を削除するのではなく、A
指定子を使用して単純化できます。a
unpack()
<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:]]}
モードに基づいて前半まで拡張されます。空白(同じ必要はありません)。$field
M
${field%%[[:space:]]#}
set -o extendedglob
おそらくよりもはるかに遅くなりますperl
。
ファイルにASCIIテキストのみが含まれている場合(例のように)、すべて同じでなければなりません。その後、-C
forを削除するか、perl
ロケールをC
/に設定するPOSIX
とsed
パフォーマンスが向上する可能性があります。gawk
perl
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の場合、パイプラインは互いに同時に実行される可能性が非常に高いです。したがって、パフォーマンスの観点からは、(合理的に)できるだけタスクをパイプラインに分割する方が良いでしょう。
したがって、perl
Stephane 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%遅いです。sed
ruby
最速で(つまり、マルチバイト文字はサポートされていません)設定。
更新された要件により現実的です。上記の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ソリューション)、基本的にまたはプログラムでマルチコアサポートを提供できない場合、パイプラインははい考慮する価値がある「速くて便利」パフォーマンス上の利点、少なくともいつ:
- 複数のタスクでアクションブロックを識別でき、各アクションブロックに対してより専門的なツールを選択できます。
- 使用可能なCPUコア/スレッドの数を超えることなく、できるだけ多くのパイプラインアームに分割できます。
gawk
ポイント1の例として、私はパイプラインの最初の部分にあなたのものを使用しました。その理由は、sed
タスクの特定の部分をはるかに高速に実行するためであり、gawk
パイプラインの2番目の部分ではパフォーマンスが30M行ほど低下するためです。サンプルデータの処理には20〜35秒かかります。さらに、更新要件のパイプラインはこれをgawk
2つのサブタスクに分割しませんでした。なぜなら、それをそのまま維持することは、固定幅のフィールドを得るために私が考えることができる最も簡単で効率的な方法です。しかし、入力データに存在できる二重引用符をエスケープします。
ポイント2の例として、sed
2つのs
コマンドを実行する最後のコマンドに注意してください。私のコンピュータで利用可能なCPUコアの数と同じであることを避けるために、もはや分割されていません。さらに分割すると、パフォーマンスは同じであり、さらなる改善はありません。
ただし、データを入力するために必要な要件が多いほど、パイプラインがより複雑になり、厄介になり、エラーが発生しやすくなり、単一のツールソリューションよりも複雑になる可能性があります。
1.各パイプラインのアームが別々のCPUコアによって実行されることを確認するコマンドがありますが、最新のカーネルではコアの再割り当てが自動的に発生するため、これは通常必要ありません。