コマンドラインからcsvファイルを解析するのに問題があります。

コマンドラインからcsvファイルを解析するのに問題があります。

私は一日の中で最も時間をかけて作業したCSVファイルを持っていますが、awkの正規表現を使って正しく解析することはできません。

awk は期待どおり正規表現を処理しません。

入力は次のとおりです。

  • GNU Awk 4.1.4、API:1.1(GNU MPFR 3.1.5-p2、GNU MP 6.1.2)
  • 正規表現:/(\[(.*?)\])|[^,]+/g
  • サンプルテキストhostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,[role1, role2, role3],[recipe1, recipe2, recipe3],2019-01-10 06:06:31
  • 元のテキスト(この質問に明示的にリストされていないステップから二重引用符を削除する前): hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,"[""role1"", ""role2"", ""role3""]","[""recipe1"", ""recipe2"", ""recipe3""]",2019-01-10 06:06:31

私がこれを実行するとき正規表現ウェブサイト、これは正しい一致を示しています。 Regexrのスクリーンショット

cat -> sed -> awk(上記のサンプルテキストはsedにあります)からパイプし、次のコマンドを実行しました([]に含まれる最初のフィールドの内容全体を含む最初の9つのフィールドのみが必要ですが、何も望まない)。以降):

awk '/(\[(.*?)\])|[^,]+/g{print $1,$2,$3,$4,$5,$6,$7,$8,$9}'

私が期待する結果は次のとおりです。 hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,[role1, role2, role3]

メモ:これの重要な部分は、ロール(角かっこ)のあるフィールドを単一のフィールドとして扱うか、少なくとも出力にすべてのロールを含めますが、レシピは含まないことです。

私が実際に得るのは、入力の完全な行です。

変数を使用して、awkで次のフィールド割り当てを見つけました。

  • 1ドル=hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,[role1,
  • $2 =role2,
  • 3ドル=role3],[recipe1,
  • 4ドル=recipe2,
  • $5=recipe3],2019-01-10
  • 6ドル=06:06:31

許可された答えを試してみました。このスタックオーバーフローの質問私は直接""の代わりに[]を区切り文字として使用するように調整しましたが、それでも役割フィールドは単一のフィールドとして扱われません。

答え1

複雑なCSVファイル、特にフィールドに引用符区切り文字(この場合はカンマ)を含む可能性があるファイルを処理する場合は、正しいCSVパーサーを使用すると多くの問題を減らすことができます。csvtool

$ echo 'hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,"[""role1"", ""role2"", ""role3""]","[""recipe1"", ""recipe2"", ""recipe3""]",2019-01-10 06:06:31' | 
    csvtool col 1-9 -
hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,"[""role1"", ""role2"", ""role3""]"

または (引用符を削除)

$ echo 'hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,"[""role1"", ""role2"", ""role3""]","[""recipe1"", ""recipe2"", ""recipe3""]",2019-01-10 06:06:31' | 
    csvtool col 1-9 - | tr -d '"'
hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,[role1, role2, role3]

たとえば、スタンドアロンCSVパーサーを取得できない場合は、csvtoolPerlとPythonの両方のCSVモジュールがあります。

perl -MText::CSV -lpe '
  BEGIN{$p = Text::CSV->new()} 
  $_ = join ",", map { $_ = s/"//gr } ($p->fields())[0..8] if $p->parse($_)
'

答え2

デフォルトでは、awkフィールドは空白として定義され、これが表示される出力を取得する理由を説明します。カンマを使用してフィールドを区切るには、以下を使用する必要があります-F

awk -F, '{...}' 

コンマで区切られた出力を印刷するには、変数を設定するawk必要があります。OFS

awk -F, -vOFS=, '{...}' 

[role1, role2, role3]ここで本当に難しいのは、これを単一のフィールドとして扱おうとするのに3つのフィールドがあるということです。そこにカンマがあり[role1role2、 で分けられますrole3]。常に3つのフィールドがあることを知っていれば簡単です。

$ awk -F, -vOFS=, '{print $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11}' file
hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,[role1, role2, role3]

しかし、今追加した生データに基づいて正しいCSVパーサー常に良いアプローチですが、awk元の入力データから実行するだけです。

$ awk -F']' -vOFS=, '{gsub(/"/,"");print $1"]"}' file
hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,[role1, role2, role3]

]秘密は、これをフィールド区切り文字として使用し、awk最初のフィールドのみを印刷するように指定することです。これにより、最初の項目まですべての内容が印刷されます]。その後、もう一度追加します](フィールドの作成時に削除されたため)。gsubすべての引用符を削除します。

関連情報