4

ほとんどの人が「なんだ?」と思うはずの質問がありますが、それでも私はそれを持っています。

ベンダーから大量のデータ ファイルを受け取りました。カンマで区切られておらず、値が引用符で囲まれていないことを除いて、CSV であると主張するカスタム フラット ファイル形式です。したがって、実際には CSV ではありません。

foo,bar,baz
alice,bob,chris

などなど、はるかに長くて面白くないことを除いて。問題は、一部のレコードに改行が埋め込まれていることです (!!!):

foo,bar
rab,baz
alice,bob,chris

これは、それぞれ 3 つのフィールドの 2 つのレコードであると想定されています。通常、私は「いいえ、これはばかげています」と言うだけですが、無意識によく見てみると、実際の行末シーケンスとは異なる種類の行末であることがわかりました。

foo,bar\n
rab,baz\r\n
alice,bob,chris\r\n

最初の行の \n に注意してください。これは、私が見つけた埋め込み改行のすべてのケースに当てはまると判断しました。したがって、基本的に行う必要がありますs/\n$//(この特定のコマンドを試しましたが、何もしませんでした)。

注: 実際にはフィールドの内容は気にしないので、改行を何も置き換えなくても問題ありません。ファイルの各行に同じ数のレコードが必要です (理想的には、同じ場所に)。

ファイルを処理するために作成したツールには、既存のソリューションがあります。

Guid g = Guid.NewGuid();

string data = File.ReadAllText(file, Encoding.GetEncoding("Latin1"));
data = data.Replace("\r\n", g.ToString()); //just so I have a unique placeholder
data = data.Replace("\n", "");
data = data.Replace(g.ToString(), "\r\n");

ただし、これは 1 ギガバイト程度を超えるファイルでは失敗します。(また、私はそれをプロファイリングしていませんが、犬も遅いと思います)。

私が自由に使えるツールは次のとおりです。

  • cygwin ツール (sed、grep など)
  • 。ネット

これを行う最善の方法は何ですか?

4

4 に答える 4

5

全体を大きな (潜在的に巨大な) 文字列としてメモリに読み込む代わりに、代わりにストリーム ベースのアプローチを検討してください。

入力ストリームを開き、一度に 1 行ずつ読み取り、必要に応じて置換を行います。出力ストリームを開き、変更された行をそこに書き込みます。何かのようなもの:

static void Main( string[] args )
{
    using( var inFs = File.OpenRead( @"C:\input.txt" ) )
    using( var reader = new StreamReader( inFs ) )
    using( var outFs = File.Create( @"C:\output.txt" ) )
    using( var writer = new StreamWriter( outFs ) )
    {
        int cur;
        char last = '0';
        while( ( cur = reader.Read() ) != -1 )
        {
            char next = (char)reader.Peek();
            char c = (char)cur;
            if( c != '\n' || last == '\r' )
                writer.Write( c );

            last = c;
        }
    }
}
于 2012-10-30T19:03:30.380 に答える
2

非常に単純なことを行うには、非常に多くのコードです。

代わりにこれを試してください。

tr -d '\n' <dirtyfile >cleanfile
于 2012-12-13T03:42:24.777 に答える
0

編集:いくつかのテストの後、 awk ソリューションは速度の点でより良い結果をもたらします。

UNIX/Linux/Cygwin の標準のファイル/入力フィルターは、バイナリ ファイルを処理するのに苦労します。フィルターを使用してこれを行うには、ファイルを HEX に変換し、編集してsed(またはawk、以下の 2 番目のソリューションを参照)、元のデータに戻す必要があります。これはそれを行う必要があります:

xxd -c1 -p file.txt | 
  sed -n -e '1{h}' -e '${x;G;p;d}' \
      -e '2,${x;G;/^0d\n0a$/{P;b};/\n0a$/{P;s/.*//;x;b};P}' |
  xxd -r -p

わかりました、これを理解するのは簡単ではありません。簡単な部分から始めましょう。

  • xxd -c1 -p file.txtfile.txt1 行あたり 1 バイトで、バイナリから HEX に変換します。
  • xxd -r -p変換を元に戻します。
  • sedは、 (16 進数の 0d) が前にない (16 進数の\n0a \r) を何も置換しません。

この部分の考え方はsed、前のバイトをホールド スペースに格納し、前のバイトと現在のバイトの両方を処理することです。

  • 1 行目では、その行 (バイト) をホールドスペースに格納します。
  • 最後の行で、両方のバイトを正しい順序で出力し ( x;G;p)、スクリプトを停止します ( d)。
  • 間にある行では、現在のバイトがホールド スペースにあり、2 バイト (前と現在) がパターン スペースにある場合 ( x;G)、次の 3 つのケースが考え られます。
    1. が の場合、次のサイクルのためにホールド スペースを維持するように\r\n出力し、このサイクルを停止します (コマンド)。\r\nb
    2. \nそれ以外の場合(で始まっていないことを意味する)で終わる場合\rは、ホールド スペースに空の文字列を格納し、このサイクルを停止します (bコマンド)
    3. それ以外の場合は、最初の文字を出力します。

で理解しやすいかもしれませんawk

xxd -c1 -p file.txt |
  awk 'NR > 1 && $0 == "0a" && p != "0d" {$0 = ""}
       NR > 1 {print p}
       {p = $0}
       END{print p}' |
  xxd -r -p

次の方法でテストできます。

printf "foo,bar\nrab,baz\r\nalice,bob,chris\r\n" |
  xxd -c1 -p | 
  sed -n -e '1{h}' -e '${x;G;p;d}' \
      -e '2,${x;G;/^0d\n0a$/{P;b};/\n0a$/{P;s/.*//;x;b};P}' |
  xxd -r -p

また

printf "foo,bar\nrab,baz\r\nalice,bob,chris\r\n" |
  xxd -c1 -p |
  awk 'NR > 1 && $0 == "0a" && p != "0d" {$0 = ""}
       NR > 1 {print p}
       {p = $0}
       END{print p}' |
  xxd -r -p
于 2012-10-30T22:11:47.410 に答える
0

これは、私StreamReaderがやりたいことをしているように見えるクラスです。これはおそらく非常にドメイン固有であるため、役立つ場合とそうでない場合があることに注意してください。

class BadEOLStreamReader : StreamReader {
    private int pushback = -1;

    public BadEOLStreamReader(string file, Encoding encoding) : base(file, encoding) {

    }

    public override int Peek() {
        if (pushback != -1) {
            var r = pushback;
            pushback = -1;
            return r;
        }

        return base.Peek();
    }

    public override int Read() {
        if (pushback != -1) {
            var r = pushback;
            pushback = -1;
            return r;
        }

        skip:
        var ret = base.Read();
        if (ret == 13) {
            var ret2 = base.Read();
            if (ret2 == 10) {
                //it's good, push back the 10
                pushback = ret2;
                return ret;
            }
            pushback = ret2;
            //skip it
            goto skip;
        } else if (ret == 10) {
            //skip it
            goto skip;
        } else {

            return ret;
        }
    }
}
于 2012-10-30T19:54:12.803 に答える