【问题标题】:Inserting characters into specified fields in large files在大文件的指定字段中插入字符
【发布时间】:2016-07-14 18:49:54
【问题描述】:

这是我的问题。我有数百个文件太大而无法使用 vi 编辑器进行编辑。我正在寻找可能的 awk 或 sed 命令来操作我的文件。有点菜鸟。我有一个简化的文件:

001|1|3|053412|16|1234|||
001|21|4|123618|15|88|||

文件已创建,但第四个字段的格式错误。 第四个字段应该是反映 HH:MM:SS 的 05:34:12。时间值是正确的,我只需要在适当的地方插入:

如何在第四个字段的第二个字符和第四个字符之后插入冒号?我不能通过字符数来做到这一点,因为第四个字段之前和之后的值是可变的。

【问题讨论】:

  • 字段是否总是由一个或多个竖线分隔?,字段的数量是否始终相同?
  • 是的,分隔符是管道。我有我的答案,谢谢大家!

标签: awk sed


【解决方案1】:

gawk 来救援!

$ awk -F\| -v OFS=\| '{$4=gensub(/(..)(..)(..)/,"\\1:\\2:\\3","g",$4)}1' file

001|1|3|05:34:12|16|1234|||
001|21|4|12:36:18|15|88|||

否则你可以对substr($4,1,2)":"...做同样的事情

【讨论】:

  • Karakfa...我向你鞠躬!!!你是我的英雄!!!非常感谢你..你是神!!!!!
【解决方案2】:

使用 GNU awk 进行 gensub() 和就地编辑

awk -i inplace 'BEGIN{FS=OFS="|"} {$4=gensub(/(..)(..)/,"\\1:\\2:",1,$4)} 1' *

与用于 ERE 和就地编辑的 GNU sed 类似:

sed -i -E 's/(([^|]*\|){3}..)(..)/\1:\3:/' *

例如:

$ awk 'BEGIN{FS=OFS="|"} {$4=gensub(/(..)(..)/,"\\1:\\2:",1,$4)} 1' file
001|1|3|05:34:12|16|1234|||
001|21|4|12:36:18|15|88|||

$ sed -E 's/(([^|]*\|){3}..)(..)/\1:\3:/' file
001|1|3|05:34:12|16|1234|||
001|21|4|12:36:18|15|88|||

【讨论】:

    【解决方案3】:

    使用 sed:

    $ sed -r 's/([^|]*|[^|]*|[^|]*|)([0-9]{2})([0-9]{2})([0-9]{2})/\1\2:\3:\4/' file
    001|1|3|05:34:12|16|1234|||
    001|21|4|12:36:18|15|88|||
    

    【讨论】:

    • 欣赏凯纳沃兹!!
    【解决方案4】:

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

    sed -r 's/^(([^|]*\|){3})(..)(..)/\1\3:\4:/' file
    

    使用反向引用将前三个字段和后面的两对分组。然后根据需要格式化第 4 个字段。

    【讨论】:

      【解决方案5】:

      试试这个 awk!

      awk -F"|" -v OFS="|" '{r=split($4,T,"");for(i=2;i<=r;i+=2){if(i!=r)T[i]=T[i]":"}tmp="";for(i=1;i<=r;i++){tmp=tmp T[i]}$4=tmp;}1' file
      
      001|1|3|05:34:12|16|1234|||
      001|21|4|12:36:18|15|88|||
      

      更长的解释:

      BEGIN{
      FS=OFS="|"; #Field separator and output field separator
      }
      {
          tmp="";
          r=split($4,time_field,""); # Chunk field into pieces
          for(i=2;i<=r;i+=2) # Loop two by two
          {
              if(i!=r)
              {
                  time_field[i]=time_field[i]":"; # Add ":"
              }
          }
      
          for(i=1;i<=r;i++) # Loop over again to rebuild
          {
              tmp=tmp time_field[i];
          }
          $4=tmp; #rebuid field
          print
      }
      
      How you could use it in bash: Save it as whatever.awk
      
      while IFS='' read -r file
      do
      awk -f whatever.awk "$file" > out_file
      done < list_of_files_to_edit.txt
      

      如果您想就地编辑文件,可以将选项 -i 添加到 Kenavoz sed 命令。 sed -ri ...

      【讨论】:

      • 你应该提到,它只适用于将字符串拆分为单个字符的 awk,给定一个空字段分隔符(GNU awk 会,但它在 POSIX 中是未定义的行为)。在编写 shell 循环时,请始终使用 IFS=-r,除非您有特定目的而不这样做并且完全了解副作用(请参阅 why-is-using-a-shell-loop-to-process-text-considered-bad-practice)。您有 awk 语法问题 - 请阅读 Arnold Robbins 撰写的 Effective Awk Programming, 4th Edition
      • @EdMorton 我看不到 sintax 问题,你能给我举个例子吗?
      • sub() 的第一个参数是一个正则表达式,而不是一个字符串,所以你应该使用sub(/:$/,"",tmp),而不是sub(":$","",tmp)。后面的分号只是把代码弄得乱七八糟。 print $0 应该只是 print。您的循环添加 :s 然后必须删除最后一个,而不是只添加您想要的内容以及为什么添加到数组元素,然后从它们构建一个字符串,而不是只在一个循环中执行。哦,除了 awk 的东西,你必须引用所有的 shell 变量 - 使用 "$file",而不是 $file
      • @EdMorton 现在看起来好多了,我认为
      • 是的,但我仍然不明白为什么你有 2 个循环而不是像 for (i=1;i&lt;=r;i++) tmp = tmp time_field[i] (i%2 || i==r ? "" : ":") 或类似的只有一个。只是不需要先修改数组然后填充 tmp。在大多数(但不是全部)行的末尾仍然有所有那些拖尾的分号。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多