从命令行解析 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 解析器(例如 )csvtool,那么 Perl 和 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 个字段。那里有逗号,所以会被分成[role1,role2role3]。如果您知道那里总是有 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所有引号。

相关内容