web-dev-qa-db-ja.com

CSVファイル内の整数を囲む引用符を削除します

大きな(> 1 GB)csvファイルには、次のようなものがあります。

"34432", "name", "0", "very long description"

しかし、代わりに私は持ってみたい

34432, "name", 0, "very long description".

sedを見ていましたが、このタスクは私の範囲外です。

これを達成する方法はありますか?

4
Balázs Németh

Perlの使用:

Perl -ne 's/"(\d+)"/$1/g; print' file.csv > new_file.txt

すべての作業はs/"(\d+)"/$1/gによって行われます。ここで

  • s/patternA/patternB/は、patternApatternBに置き換えるために使用されます
  • perlは、二重引用符で囲まれた\d+の1つ以上の数字を探します。
  • \d+を囲む括弧は、数字をキャプチャし、Perlの特殊変数$1で置換パターンとして再利用するために使用されます。
6
Sylvain Pineau

この場合に機能するGNU sed正規表現は

sed -r 's/"([0-9]+)"/\1/g'    

純粋なsedの場合、グループ化括弧と+修飾子をエスケープする必要があります

sed 's/"\([0-9]\+\)"/\1/g'

Sedの一部のバージョンでは、その場で置換を実行できます。

sed -ri 's/"([0-9]+)"/\1/g' file.csv

文字範囲[[:digit:]]の代わりにPOSIXクラス[0-9]を使用することもできます

6
steeldriver

問題の説明はあまり明確ではありません。 1番目と3番目のフィールドのみの二重引用符を削除することを想定しています。もしそうなら、これらのいずれかが動作するはずです:

  1. sed

    sed -r 's/^"([^"]+)"(\s*,\s*[^,]+)\s*,\s*"([^"]+)"/\1\2, \3/' file.csv
    

    説明

    -rにより拡張正規表現が有効になり、括弧を使用してパターンをエスケープせずにキャプチャできます。したがって、行の先頭に引用符(^")が続き、その後に1つ以上の引用符以外の文字([^"]+)が続き、最後の引用符に0個以上のスペースが続きます。コンマ、次に0個以上のスペース(\s*,\s*)、次のコンマまでのコンマ以外の部分(これは2番目のフィールドを定義します)。最後に、0個以上のスペース、カンマを探し、それを最初のキャプチャパターン(\1)、次に2番目(\2)、コンマ、スペース、3番目に置き換えます。

  2. Perl

    Perl -pe 's/^"([^"]+)"(\s*,\s*[^,]+)\s*,\s*"([^"]+)"/$1$2, $3/; ' file.csv
    

    説明

    -pは、-eによって渡されたスクリプトを適用した後、すべての行を印刷することを意味します。スクリプト自体は、基本的に上記のsedと同じ正規表現です。ここでのみ、キャプチャされるパターンは$1です。

  3. awk

    awk -F, -v OFS="," '{gsub("\"","",$1)0gsub("\"","",$3);}1;' file.csv 
    

    説明

    -Fは、フィールドセパレーターを,に設定します。 OFSは出力フィールド区切り文字であり、,にも設定されているため、行が正しく印刷されます。 gsubは置換を行い、1番目(")および3番目のフィールド($1)で実行するため、すべての$3を何も置き換えません。これらのフィールドから引用符のみを削除します。 1;は、「行を印刷する」ための単なるawkの省略形です。

5
terdon

Pythonソリューション

以下の小さなスクリプトは、ファイルのコマンドライン引数を取り、そのファイルの各行を反復処理し、,を区切り文字として使用して各行をアイテムのリストに分割します。各エントリは引用符で囲まれず、数値文字列であるかどうかがチェックされます。文字列が数値の場合、引用符は付けられません。

#!/usr/bin/env python
import sys

with open(sys.argv[1]) as fp:
    for line in fp:
        new_vals = []
        vals = line.strip().split(',')
        for val in vals:
            val = val.strip().rstrip().replace('"','')
            if not val.isdigit(): 
               val = '"' + val  + '"'
            new_vals.append(val)
        print(",".join(new_vals))

テスト走行:

$ cat input.txt
"34432", "name", "0", "very long description" 
"1234", "othe name" , "42", "another description"
$ ./unquote_integers.py  input.txt                                       
34432,"name",0,"very long description"
1234,"othe name",42,"another description"

追加のメモ

コメントで尋ねられたのは、なぜアイテムが数値文字列であるかどうかを評価する前に、スクリプトが各アイテムを囲む二重引用符を削除する理由です。その主な理由は、二重引用符を含めると、"123"のような項目がFalseに評価されるため、つまり非数値になるためです。事実上、二重引用符内の内容を何らかの方法で評価する必要があります。現在、各値のリストスライスを取得することで、これにアプローチする別の方法があります。ただし、最初から.replace()を使用するよりも優れています。それはコードを短くしますが、少なくともこの場合、スクリプトの短さは無関係です-私たちの目標は、コードゴルフではなく、コードを機能させることです。

リストスライスを使用した代替ソリューションは次のとおりです。

#!/usr/bin/env python
import sys

with open(sys.argv[1]) as fp:
    for line in fp:
        new_vals = []
        vals = line.strip().split(',')
        for val in vals:
            val = val.strip().rstrip() #remove extra spaces
            val = val.replace('"','') if val[1:-1].isdigit() else val
            new_vals.append(val)
        print(",".join(new_vals))
1