【问题标题】:Add additional fields based on field count根据字段计数添加其他字段
【发布时间】:2021-06-17 10:21:24
【问题描述】:

我的文件中有以下格式的数据

"123","XYZ","M","N","P,Q"
"345",
"987","MNO","A,B,C"

我总是希望行中有 5 个条目,所以如果 2 中的字段计数,则需要添加 3 个额外的 ("")。

"123","XYZ","M","N","P,Q" 
"345","","","",""  
"987","MNO","A,B,C","",""  

我查看了页面上的解决方案

Add Extra Strings Based on count of fields- Sed/Awk

它有非常相似的要求,但是当我尝试时它失败了,因为我在字段中也有逗号 (,)。

谢谢。

【问题讨论】:

  • 第二行末尾的, 是错字吗?如果没有,其他字段也可以为空吗?

标签: awk sed


【解决方案1】:

在 GNU awk 中使用您显示的示例,请尝试以下代码。

awk -v s1="\"" -v FPAT='[^,]*|"[^"]+"' '
BEGIN{ OFS="," }
FNR==NR{
  nof=(NF>nof?NF:nof)
  next
}
NF<nof{
  val=""
  i=($0~/,$/?NF:NF+1)
  for(;i<=nof;i++){
    val=(val?val OFS:"")s1 s1
  }
  sub(/,$/,"")
  $0=$0 OFS val
}
1
'  Input_file  Input_file

说明:为上述添加详细说明。

awk -v s1="\"" -v FPAT='[^,]*|"[^"]+"' ' ##Starting awk program from here setting FPAT to csv file parsing here.
BEGIN{ OFS="," }                         ##Starting BEGIN section of this program setting OFS to comma here.
FNR==NR{                                 ##Checking condition FNR==NR here, which will be true for first time file reading.
  nof=(NF>nof?NF:nof)                    ##Create nof to get highest NF value here.
  next                                   ##next will skip all further statements from here.
}
NF<nof{                                  ##checking if NF is lesser than nof then do following.
  val=""                                 ##Nullify val here.
  i=($0~/,$/?NF:NF+1)                    ##Setting value of i as per condition here.
  for(;i<=nof;i++){                      ##Running loop till value of nof matches i here.
    val=(val?val OFS:"")s1 s1            ##Creating val which has value of "" in it.
  }
  sub(/,$/,"")                           ##Removing ending , here.
  $0=$0 OFS val                          ##Concatinate val here.
}
1                                        ##Printing current line here.
'  Input_file  Input_file                ##Mentioning Input_file names here.


编辑: 在此处添加此代码,其中保留一个名为 nof 的变量,我们可以在其中给出字段值的数量,该值应在所有缺失的行中添加最少,如果任何行的字段值超过最小字段值,则将使用该值在缺少的字段行中添加那么多字段。

awk -v s1="\"" -v nof="5" -v FPAT='[^,]*|"[^"]+"' '
BEGIN{ OFS="," }
FNR==NR{
  nof=(NF>nof?NF:nof)
  next
}
NF<nof{
  val=""
  i=($0~/,$/?NF:NF+1)
  for(;i<=nof;i++){
    val=(val?val OFS:"")s1 s1
  }
  sub(/,$/,"")
  $0=$0 OFS val
}
1
'  Input_file  Input_file

【讨论】:

  • 它有效.. 谢谢.. 输入文件有条目的任何原因?
  • @AnandAbhay,我正在写详细的解释。实际上代码正在读取文件 2 次,第一次它获得最大数量的字段值,第二次它检查哪一行的字段较少,然后添加它们。
  • @AnandAbhay,以上代码的详细解释现已添加。
  • 谢谢你的详细解释。
【解决方案2】:

这是一个awk 命令,它适用于任何版本的awk

awk -v n=5 -v ef=',""' -F '","' '
{
   sub(/,+$/, "")
   for (i=NF; i<n; ++i)
      $0 = $0 ef
} 1' file

"123","XYZ","M","N","P,Q"
"345","","","",""
"987","MNO","A,B,C","",""

【讨论】:

  • 谢谢埃德。这是一个非常好的技巧,可以让它变得更短。
  • 还有一个没有循环的替代方案,我们可以像(未经测试)allef=sprintf("%*s",n-NF,""); gsub(/ /,ef,allref); $0=$0 allef 那样做,但除非你有一千个缺失字段,否则很难关心:-)!
【解决方案3】:

这是一个使用FPAT 的 GNU awk,当 [你] 总是希望在行中有 5 个条目时:

$ awk '
BEGIN {
    FPAT="([^,]*)|(\"[^\"]+\")"
    OFS=","
}
{
NF=5                              # set NF to limit too long records
for(i=1;i<=NF;i++)                # iterate to NF and set empties to ""
    if($i=="")
        $i="\"\""
}1' file

输出:

"123","XYZ","M","N","P,Q"
"345","","","",""
"987","MNO","A,B,C","",""

【讨论】:

  • 由于无论如何您都需要循环,因此在这种情况下,首先执行NF=5 实际上并没有为您做任何事情,除了为执行添加更多的工作/时间。只需去掉 NF=5 并创建循环 for(i=1;i&lt;=5;i++) ,它会产生相同的输出但运行速度更快。
  • @EdMorton 好吧,我把它放在那里是为了减少超过 5 条的记录(因为我使用1 输出)当然可以用更长的{for(i=1;i&lt;=5;i++){if($i=="")$i="\"\"";printf "%s%s",$i,(i==5?ORS:OFS)}} 代替,但我想保持代码简短,使用 3M 的 OP 数据记录,原始 NF=5 版本的运行速度提高了约 10%。
  • 我没有考虑过可能有超过 5 个字段。我想知道在这种情况下所需的行为是否真的是删除数据,或者我们是否应该将空字段添加到存在的数字 - 我认为它会是后者,因为 @RavinderSingh13 正在做,但 idk。 NF=5 会在少于 5 个的情况下添加字段,但如果超过 5 个,它会执行未定义的行为,尽管您已经在使用 FPAT 并且它会在 gawk 中执行您想要的操作。
【解决方案4】:

使用perl,假设每个字段都被双引号:

$ perl -pe 's/,$//; s/$/q(,"") x (4 - s|","|$&|g)/e' ip.txt
"123","XYZ","M","N","P,Q"
"345","","","",""
"987","MNO","A,B,C","",""

# if the , at the end of line isn't present
$ perl -pe 's/$/q(,"") x (4 - s|","|$&|g)/e' ip.txt
"123","XYZ","M","N","P,Q"
"345","","","",""
"987","MNO","A,B,C","",""

s|","|$&amp;|g 将搜索 "," 并将其替换回来。返回值是替换的数量,然后用于确定必须附加多少字段。

e 标志允许您在替换部分使用 Perl 代码。

q 运算符有助于为单引号字符串使用不同的分隔符。



这是另一种解决方案,它创建一个数组,然后在必要时添加空字段。

perl -lne '@f = /"[^"]+"|[^,]+/g; print join ",", @f, qw("") x (4 - $#f)'

/"[^"]+"|[^,]+/g 将字段定义为双引号字符串(内部没有双引号,因此转义引号不适用于此解决方案)或非 , 字符(至少一个,因此行尾的 , 将被忽略)。

qw("") x (4 - $#f) 确定要附加的额外字段。 qw("") 创建一个数组,其值为 "" 的单个元素,然后使用 x 运算符相乘。

【讨论】:

  • 添加了一个与您上次使用 -a 进行自动拆分非常相似的答案,我想这是将您的 @f 创建为 @F 的另一种方法
【解决方案5】:

这可能对你有用(GNU sed):

sed ':a;s/"[^"]*"/&/5;t;s/$/,""/;ta' file

如果有 5 个字段,则退出。

否则,追加一个空字段并重复。

【讨论】:

    【解决方案6】:

    另一种使用-a 进行自动拆分和-F 设置分隔符的perl 方式:

    perl -lanF'/"*,*"/' -e 'print join ",", map "\"$_\"", @F[1..5]'
    
    • -F'/"*,*"/' - 这使用双引号的自动拆分分隔符,可选地以逗号和引号开头
    • -a 使用该分隔符自动拆分为 @F
    • -l 为打印添加换行符,-n 将在不打印的情况下以流模式处理输入,除非明确告知
    • map "\"$_\"", @F[1..5] 正好取 5 个字段,甚至是未定义的字段,并添加双引号
    • print join ",", map ... 获取上面映射的结果,用逗号连接成一个字符串,然后打印

    (注意:因为每一行都以字段分隔符开头,所以我忽略了空的$F[0] 元素)

    【讨论】:

    • 很好,尤其是避免计算字段数。但在 OP 示例的第二行末尾没有涵盖 ,(这可能是 OP 的错字)。
    猜你喜欢
    • 2023-03-18
    • 2021-11-01
    • 2012-05-11
    • 1970-01-01
    • 1970-01-01
    • 2023-04-06
    • 2023-03-30
    • 2022-10-16
    • 1970-01-01
    相关资源
    最近更新 更多