コンマ区切りファイルでのみ引用符の間のコンマを削除します


23

入力ファイルがコンマ(,)で区切られています。二重引用符で囲まれたいくつかのフィールドには、カンマが含まれています。ここにサンプル行があります

123,"ABC, DEV 23",345,534.202,NAME

二重引用符と二重引用符内で発生するすべてのコンマを削除する必要があります。したがって、上記の行は以下に示すように解析されるはずです

123,ABC DEV 23,345,534.202,NAME

を使用して次のことを試みましたsedが、期待した結果が得られませんでした。

sed -e 's/\(".*\),\(".*\)/\1 \2/g'

sedawkまたはその他のUNIXユーティリティを使用した簡単なトリックはありますか?


あなたが何をしようとしているのか分かりませんが、ユーティリティ「csvtool」は、sedやawkなどの汎用ツールよりもcsvの解析にはるかに適しています。Linuxのほぼすべてのディストリビューションにあります。
figtrap

回答:


32

引用符のバランスが取れている場合、他のすべての引用符の間のコンマを削除する必要があります。これは次のように表現できますawk

awk -F'"' -v OFS='' '{ for (i=2; i<=NF; i+=2) gsub(",", "", $i) } 1' infile

出力:

123,ABC DEV 23,345,534.202,NAME

説明

これ-F"により、awkは二重引用符で行を分離します。つまり、1つおきのフィールドが引用符間テキストになります。for-loopはgsub、グローバル置換の略で、他のすべてのフィールドで実行され、コンマ(",")を何も("")に置き換えます。1最後には、デフォルトのコード・ブロックを呼び出します{ print $0 }


1
gsubこのライナーがどのように機能するかを簡単に説明してください。お願いします。
mtk

ありがとうございました!このスクリプトは本当にうまく機能しますが、スクリプトの最後にある孤独な1を説明してもらえますか?- } 1' -
CocoaEv

@CocoaEv:を実行し{ print $0 }ます。私もそれを説明に追加しました。
トール14年

2
このアプローチには問題があります:時々 、CSVは、次のような行をそのスパン数行、持っているprefix,"something,otherthing[newline]something , else[newline]3rdline,and,things",suffix (例:複数の行、およびネストされた「」マルチライン内の任意の場所をダブル引用符:全体の"...."一部が再結合する必要があると内部が,なければなりませんが...二重引用符... + 文字列\" 内にもエスケープさている場合は特に注意してください)
オリビエデュラック

1
この解決策は気に入ったが、コンマを保持したいが、それでも区切りたいと思うことが多いので、私はそれを微調整した。代わりに、私はカンマを切り替え PSVファイルにcsvファイルを変換し、パイプに引用符:awk -F'"' -v OFS='"' '{ for (I=1; i<=NF; i+=2) gsub(",", "|", $i) } 1' infile
ダントンノリエガ

7

ループで 1回だけsedを使用すると、良い応答があります

echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME'|
  sed ':a;s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 /;ta'
123,"ABC  DEV 23",345,534,"some more  comma-separated  words",202,NAME

説明:

  • :a; furterブランチのラベル​​です
  • s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/\1 / 3つの囲まれた部分を含むことができます
    • 最初の2番目:[^"]*,\?\|"[^",]*",\?二重引用符を含まない文字列と一致し、その後にコマまたは 2つの二重引用符で囲まれた文字列が続き、コマなしでコマが続く場合があります。
    • 最初のREパートは、前述のパート2を何度も繰り返した後、1つの二重引用符といくつかの文字が続きますが、二重引用符もコンマもありません。
    • コマが続く最初のRE部分。
    • 注、行の残りの部分に触れる必要はありません
  • ta:a前のs/コマンドが変更された場合にループします。

ネストされた引用符でも機能します。素晴らしいです、ありがとう!
トリカス

5

バランスのとれた引用符の間のいくつかのコンマも処理できる一般的なソリューションには、ネストされた置換が必要です。特定の入力のすべての行を処理し、引用符の他のすべてのペアのコンマのみを置換するperlでソリューションを実装します。

perl -pe 's/ "  (.+?  [^\\])  "               # find all non escaped 
                                              # quoting pairs
                                              # in a non-greedy way

           / ($ret = $1) =~ (s#,##g);         # remove all commas within quotes
             $ret                             # substitute the substitution :)
           /gex'

または要するに

perl -pe 's/"(.+?[^\\])"/($ret = $1) =~ (s#,##g); $ret/ge'

処理するテキストをコマンドにパイプするか、最後のコマンドライン引数として処理するテキストファイルを指定できます。


1
[^\\]は、引用符内の最後の文字と一致してそれを削除するという望ましくない効果があります(\以外の文字)。つまり、その文字を消費すべきではありません。(?<!\\)代わりに試してください。
tojrobinson

あなたの反対をありがとう、私はそれを修正しました。それにもかかわらず、ここでアサーションの背後に目を向ける必要はないと思います。
user1146332

1
キャプチャグループに非\を含めると、同等の結果が生成されます。+1
トジロビンソン

1
+1。sedでいくつかのことを試した後、sedのドキュメントを確認し、行の一致部分だけに置換を適用できないことを確認しました...だからあきらめて、perlを試しました。[^"]*最終的には非常に似たアプローチになりましたが、このバージョンでは、マッチを貪欲にしない(つまり、あるものから次のもの"へのすべてを一致させる)ために使用します 。それは引用がバックスラッシュでエスケープされるかもしれないという風変わりな考えを認めていません: "perl -pe 's/"([^"]+)"/($match = $1) =~ (s:,::g);$match;/ge;'
cas

コメントありがとうございます。[^"]*アプローチまたは明示的な貪欲でないアプローチのいずれかがCPU時間をあまり消費しない場合は興味深いでしょう。
user1146332

3

適切なCSVパーサーで言語を使用します。例えば:

ruby -r csv -ne '
  CSV.parse($_) do |row|
    newrow = CSV::Row.new [], []
    row.each {|field| newrow << field.delete(",")}
    puts newrow.to_csv
  end
' < input_file

最初はこのソリューションが好きでしたが、大きなファイルでは信じられないほど遅いことが判明しました
...-KIC

3

2番目の引用符が間違っています:

sed -e 's/\(".*\),\(.*"\)/\1 \2/g'

さらに、正規表現を使用すると、テキストの可能な限り長い部分と一致する傾向があります。つまり、文字列に複数の引用フィールドがある場合、これは機能しません。

sedで複数の引用フィールドを処理する方法

sed -e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' -e 's/\"//g'

これはこれを解決する方法でもありますが、引用されたフィールドごとに複数のコンマを含む可能性がある入力では、sedの最初の式は単一フィールドの最大コンマコンテンツと同じ回数、またはそれまで繰り返される必要があります出力をまったく変更しません。

複数の式でsedを実行すると、複数のsedプロセスが実行され、オープンパイプですべて実行される「tr」よりも効率的です。

ただし、入力が適切にフォーマットされていない場合、これは望ましくない結果になる可能性があります。すなわち、ネストされた引用符、終了していない引用符。

実行例を使用して:

echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME' \
| sed -e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' \
-e 's/\(\"[^",]\+\),\([^",]*\)/\1 \2/g' -e 's/\"//g'

出力:

123,ABC  DEV 23,345,534,some more  comma-separated  words,202,NAME

GNU sed:のように、条件付き分岐でより一般化し、EREでより読みやすくすることができますsed -r ':r; s/("[^",]+),([^",]*)/\1 \2/g; tr; s/"//g'
トール

2

perl Text::CSVでは、これを解析するために使用でき、簡単に実行できます。

#!/usr/bin/env perl
use strict;
use warnings;

use Text::CSV; 

my $csv = Text::CSV -> new();

while ( my $row = $csv -> getline ( \*STDIN ) ) {
    #remove commas in each field in the row
    $_ =~ s/,//g for @$row;
    #print it - use print and join, rather than csv output because quotes. 
    print join ( ",", @$row ),"\n";
}

で印刷することはできますがText::CSV、引用を保持する傾向があります。(が、私はお勧めしたい-というよりも、ストリッピングあなたの出力のために引用符を、あなただけ使用して解析することができText::CSV、最初の場所で)。


0

文字列内のすべての文字をループする関数を作成しました。
文字が引用符の場合、チェック(b_in_qt)はtrueとマークされます。
b_in_qtがtrueの間、すべてのコンマはスペースに置き換えられます。
次のコンマが見つかると、b_in_qtはfalseに設定されます。

FUNCTION f_replace_c (str_in  VARCHAR2) RETURN VARCHAR2 IS
str_out     varchar2(1000)  := null;
str_chr     varchar2(1)     := null;
b_in_qt     boolean         := false;

BEGIN
    FOR x IN 1..length(str_in) LOOP
      str_chr := substr(str_in,x,1);
      IF str_chr = '"' THEN
        if b_in_qt then
            b_in_qt := false;
        else
            b_in_qt := true;
        end if;
      END IF;
      IF b_in_qt THEN
        if str_chr = ',' then
            str_chr := ' ';
        end if;
      END IF;
    str_out := str_out || str_chr;
    END LOOP;
RETURN str_out;
END;

str_in := f_replace_c ("blue","cat,dog,horse","",yellow,"green")

RESULTS
  "blue","cat dog horse","",yellow,"green"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.