【问题标题】:Modify multiple columns value based on specific column values in LinuxLinux中根据特定列值修改多列值
【发布时间】:2021-07-28 04:03:57
【问题描述】:

我有一个包含以下数据的文件

"col1","col2","col3","col4","col5","col6"
"CACR","0","SO2","50","6","2.0"
"FF","15","CO2","20","4","3"
"CACR","25","NOx","30","10",        
"CACR","50","CO","40","5","0"

我想找到包含 CACR 的每一行,然后使用 Linux 终端将 col2、col4、col5 和 col6 的值除以 col6 的各个单元格(如果 col6 有 0 或空白单元格,则忽略除法计算)。因此,我的输出如下所示:

"col1","col2","col3","col4","col5","col6"
"CACR","0","SO2","25","3","1"
"CACR","25","NOX","30","10",           
"CACR","50","CO","40","5","0"

我正在尝试使用 grep 和 awk

grep  CACR  file.csv | awk -F "," '$6 != 0; $6 == "" {$2 = $2/$6; $4= $4/$6; $5 = $5/$6; $6 = $6/$6}1' 

但无法获得任何所需的输出。

【问题讨论】:

  • 如果您使用的是awk,则不需要grep。到目前为止,您尝试过什么?
  • 嗨@vgersh99 抱歉回复晚了,我用我一直在尝试的方法编辑了这个问题。我试过这个 grep CACR file.csv | awk -F "," '$6 != 0; $6 == "" {$2 = $2/$6; $4= $4/$6; 5 美元 = 5 美元/6 美元; $6 = $6/$6}1'
  • 输入数据的逗号后面真的有这么多空格吗?双引号会让生活变得困难——如果它们不存在,你可以做算术,但是使用双引号,字符串(字段)不会转换为数字,所以算术不起作用。您将需要从字段中删除引号(编写一个 Awk 函数来执行此操作),然后您可以进行算术运算。重新打印带有引号的字段也很麻烦;再次,编写一个 awk 函数来提供帮助。
  • 您好@JonathanLeffler 我在列之间没有空格。但我必须在输出中保留所有双引号和逗号。
  • 请更新问题中的数据以删除空格 - 它们具有误导性。对于其余部分,strip_quotes 函数和 add_quotes() 函数应该会有所帮助。我观察到您的 grep 解决方案丢失了列标题 - 仅使用 awk 意味着您可以保留列标题。

标签: linux unix centos7


【解决方案1】:

正如comment 中所述,主要问题是字段周围的双引号意味着当字段被解释为数字(例如,带有除法)时,该值为零。我认为您需要编写 Awk 函数来删除和恢复双引号。有了这些,它主要是 SMOP — 一个简单的编程问题。

这是我的版本。它可以写得更简洁(更少的换行符,更少的空格),但我更喜欢清晰而不是简洁。

script.awk

function strip_quotes(s)
{
    gsub(/"/, "", s)
    return s
}
function add_quotes(s)
{
    return sprintf("\"%s\"", s)
}
BEGIN        { FS = "," }
NR == 1      { print; next }
$0 !~ /CACR/ { next }
$6 == "" || $6 == "\"0\"" { print; next }
        {
            div = strip_quotes($6)
            printf("%s,%s,%s,%s,%s,%s\n",
                   $1,
                   add_quotes(strip_quotes($2) / div),
                   $3,
                   add_quotes(strip_quotes($4) / div),
                   add_quotes(strip_quotes($5) / div),
                   add_quotes(strip_quotes($6) / div))
        }

data

"col1","col2","col3","col4","col5","col6"
"CACR","0","SO2","50","6","2.0"
"FF","15","CO2","20","4","3"
"CACR","25","NOx","30","10",
"CACR","50","CO","40","5","0"

输出

$ awk -f script.awk data
"col1","col2","col3","col4","col5","col6"
"CACR","0","SO2","25","3","1"
"CACR","25","NOx","30","10",
"CACR","50","CO","40","5","0"
$

变体script3.awk

此代码也将输出字段分隔符OFS 设置为逗号,并在使用print 打印修改后的$0 之前重置$2$4$5$6 的值.

function strip_quotes(s)
{
    gsub(/"/, "", s)
    return s
}
function add_quotes(s)
{
    return sprintf("\"%s\"", s)
}
BEGIN        { FS = ","; OFS = "," }
NR == 1      { print; next }
$0 !~ /CACR/ { next }
$6 == "" || $6 == "\"0\"" { print; next }
        {
            div = strip_quotes($6)
            $2 = add_quotes(strip_quotes($2) / div)
            $4 = add_quotes(strip_quotes($4) / div)
            $5 = add_quotes(strip_quotes($5) / div)
            $6 = add_quotes(strip_quotes($6) / div)
            print
        }

数据验证

两个版本的脚本可能更严格,验证有 5 或 6 列(拒绝具有更多或更少列的行或抱怨它们)。对标题的检查可以坚持 6 列。检查div 是否是一个非零数字可能是明智的。检查$2$4$5$6 是否是一个数字可能是明智的。样本数据中的除数(第 6 列)很方便;如果数字不是那么简单,您可能需要做一些工作,例如7,结果可能有很多小数位。您需要决定如何格式化这些数字(默认值可能是好的,也可能不是)。检查每个字段中的数据是否与正则表达式 /^"[^"]*"$/ 匹配可能也是值得的(因此每个值都用双引号括起来)。

尾随空格

规则$6 == "" || $6 == "\"0\"" { print; next } 不能很好地处理尾随空格。可以修改为:

$6 ~ /^[[:space:]]*$/ || $6 == "\"0\"" { print; next }

识别尾随空格并将其视为零。添加以下内容是可能的,并且可能是明智的:

if (div == 0) { print; next }

在分配给div 之后。如果找到的值为零,则存在问题。也可以投诉——生成错误消息来诊断“格式错误的数据”。

验证和错误预防的价值取决于输入数据的不规则程度。如果您正在处理人为生成的数据,则必须处理人为改变规则并向程序提供不稳定或错误数据的倾向,并且您可能需要处理(诊断)意外输入。如果您正在处理机器生成的数据,它通常会更加统一,并且您可以减少验证工作。

大多数依赖于正则表达式的解决方案都必须在运行良好和打破不稳定输入之间取得平衡。输入越不稳定,设计防炸弹(防傻瓜)正则表达式就越难。俗话说,“如果你做了点东西idiot-proof,就会有人做一个更好的白痴”。

【讨论】:

  • 如果我尝试使用 script3.awk,我得到错误 awk: script.awk:18: (FILENAME=test.csv FNR=4) fatal: 除以零尝试
  • 哪个平台(操作系统,Awk 版本)?我在运行 Mojave 的 MacBook Pro 上进行了测试(哎哟——很痛;公司政策)。我同时使用了本机 Awk(BSD Awk 版本 20070501)和 GNU Awk(版本 4.1.3),没有出现任何问题,并且获得了预期的结果。
  • FNR==4 的不同之处在于结尾的逗号。但是,测试/操作$6 == "" || $6 == "\"0\"" { print; next } 应该处理这个问题。将|| $6 == 0 添加到条件中可能是明智的,但这与给定的数据文件无关。你在那条线上有什么?尾随空格? CR/LF 行尾?如果有一个尾随空白,我会得到一个除以零的错误。尾随空白是诅咒。我们可以使用$6 ~ /^[[:space:]]*$/ 代替$6 == "",因为您可能没有干净的数据。
  • 我正在使用 centos 7 Linux 3.10.0-1062.4.3.el7.x86_64 和 GNU Awk 4.0.2
  • 嗯:那一行也有尾随(或前导)空格?我想我们可能需要在正则表达式中处理这个问题(正如我所说,这取决于数据生成器的聪明程度):$6 ~ /^[[:space:]]*("0")?[[:space:]]*$/ — 查找由零个或多个空格字符组成的字段,然后是零或出现一次"0",后跟零个或多个空格字符(^$ 将匹配限制为整个字段)。正如我所说,正则表达式通常是通用性和可理解性之间的平衡——这开始变得非常普遍,但有点难以理解。
猜你喜欢
  • 2021-06-22
  • 2019-06-19
  • 2022-06-11
  • 1970-01-01
  • 1970-01-01
  • 2023-02-14
  • 2020-08-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多