【问题标题】:Modify specific field in the last line of a text file修改文本文件最后一行中的特定字段
【发布时间】:2017-08-05 01:40:19
【问题描述】:

我正在尝试确定是否有一个快速的单行 sed 或 awk 脚本可以执行以修改文本文件中的某个值,特别是文件最后一行中的值。

目前我的文件有一个尾行,其中包含数据行数。我想修改它,使其包含包括页眉和页脚在内的计数。任何帮助将不胜感激。

file1 代码:

H|ACCT|XEC|1|TEMP|20130215035845|
D|849002|48|1208004|1
D|849007|28|1208004|1
D|849007|38|1208004|1
T|3

修改后的输出应该是

H|ACCT|XEC|1|TEMP|20130215035845|
D|849002|48|1208004|1
D|849007|28|1208004|1
D|849007|38|1208004|1
T|5

【问题讨论】:

  • 是文件内容的“HDT”部分,还是只是为了问题而标记行?
  • 它们是文件内容的一部分
  • 能不能有其他的线不应该算在内?那不是页眉、数据或页脚?
  • 不,除了 D 线之外,只有 1 条 H 线和 1 条 T 线。 T 行始终显示 D 行的计数。

标签: awk sed


【解决方案1】:

修改以T开头的行:

$ awk '{sub(/^T.*/,"T|"NR)}1' file
H|ACCT|XEC|1|TEMP|20130215035845|
D|849002|48|1208004|1
D|849007|28|1208004|1
D|849007|38|1208004|1
T|5

按照最初的要求修改输入文件的最后一行:

$ awk '{printf "%s",p} {p=$0 ORS} END{sub(/\|.*/,"|"NR,p); print p}' file
H|ACCT|XEC|1|TEMP|20130215035845|
D|849002|48|1208004|1
D|849007|28|1208004|1
D|849007|38|1208004|1
T|5

由于在 cmets 中存在一些争论,为什么我对发布在 heregetline 解决方案投了反对票,并且因为很难在 cmets 中给出示例 - 这里有几个示例说明您为什么不应该使用 getline 解决方案(或任何类似的)这个问题(或任何类似的):

适用于一组输入:

$ cat file1
H|ACCT|XEC|1|TEMP|20130215035845|
D|849002|48|1208004|1
D|849007|28|1208004|1
D|849007|28|1208004|1
T|3

$ awk '{printf "%s",p} {p=$0 ORS} END{sub(/\|.*/,"|"NR,p); print p}' file1
H|ACCT|XEC|1|TEMP|20130215035845|
D|849002|48|1208004|1
D|849007|28|1208004|1
D|849007|28|1208004|1
T|5

$ awk '{l=$0; if(getline==1){print l; print} else {sub("\\|.*","|"NR);print}}' file1
H|ACCT|XEC|1|TEMP|20130215035845|
D|849002|48|1208004|1
D|849007|28|1208004|1
D|849007|28|1208004|1
T|5

另一个失败:

$ cat file2
H|ACCT|XEC|1|TEMP|20130215035845|
D|849002|48|1208004|1
D|849007|28|1208004|1
T|3

$ awk '{printf "%s",p} {p=$0 ORS} END{sub(/\|.*/,"|"NR,p); print p}' file2
H|ACCT|XEC|1|TEMP|20130215035845|
D|849002|48|1208004|1
D|849007|28|1208004|1
T|4

$ awk '{l=$0; if(getline==1){print l; print} else {sub("\\|.*","|"NR);print}}' file2
H|ACCT|XEC|1|TEMP|20130215035845|
D|849002|48|1208004|1
D|849007|28|1208004|1
T|3

尴尬(充其量)以增强最小的工作,例如将每一行打印到 stderr 以进行调试:

$ awk '{print |"cat>&2"} {printf "%s",p} {p=$0 ORS} END{sub(/\|.*/,"|"NR,p); print p}' file2

$ awk '{print |"cat>&2"; l=$0; if(getline==1){print |"cat>&2"; print l; print} else {print |"cat>&2"; sub("\\|.*","|"NR); print}}' file1

请注意修改 2 个版本在简单性上的区别。修改getline 版本是笨拙的、复杂的、不平凡的、不明显的、低效的、容易出现潜在错误、需要重复代码和/或大量重写等...

我们在上面看到的是尝试使用getline 来解决 awk 的自然文本处理模式可以轻松处理的问题的非常常见的后果。

getline 在适当使用时很有用,请参阅http://awk.info/?tip/getline 了解一些有效应用程序的示例。

【讨论】:

    【解决方案2】:

    严格来说,它不是单行的,它对“T”行的格式做出了假设,但是:

    (sed '${=;d;}' | sed '$s/^/T|/') < infile > outfile
    

    还有一个 awk 单行:

    awk '/^T/ {sub(/[0-9]*$/, NR)}; {print}' < infile > outfile
    

    【讨论】:

      【解决方案3】:

      更新 2

      • 此解决方案有效并且高效因为它只读取输入文件一次
      • 但是,对于更惯用的 awk 解决方案,也只读取一次文件,请参阅 @Ed Morton's answer
      • 此解决方案使用getline,这是一个awk 函数,它有很多陷阱(但也有合法的应用程序) - 请参阅http://awk.freeshell.org/AllAboutGetline
        • 恰当的例子:这个答案的原始版本从根本上被破坏了,因为它只适用于具有 odd 行数的输入文件;再次查看 Ed 的回答以获取说明。
      • 通常会使基于getline 的解决方案出现问题的另一个方面是可维护性 - 修改此解决方案以做更多的事情而不仅仅是更新行数会很麻烦。

      一个只读取输入文件的awk 解决方案一次

      awk '{l=$0; while(getline==1){print l;l=$0;} sub("\\|.*","|"NR); print}' file
      

      注释版:

      awk '
        {
          l=$0                     # save 1st line read
          # Start a loop that reads all remaining lines.
          # Print them EXCEPT for the LAST one.
          while (getline == 1) {   # loop until the last line is read
            print l                # print the saved line now known not to be the last
            l=$0                   # save this line for the next iteration
          }
          # Getting here means: the last line was read (and is stored in $0).
          sub("\\|.*","|"NR)       # replace the part after "|" with the line count
          print                    # output modified last line
        }
        ' file
      

      请注意,POSIX awk 和许多实现不支持就地修改输入文件,因此您必须将输出(至少暂时)保存到不同的文件。 p>

      但是,正如@Ed Morton 指出的那样,GNU awk4.1 或更高版本,确实允许使用 -i inplace 进行就地修改 - 请参阅 http://www.gnu.org/software/gawk/manual/gawk.html#Extension-Sample-Inplace

      【讨论】:

      • 如果无法读取另一行,getline 不一定会返回 0。见awk.info/?tip/getline。无论如何,为此使用 getline 是不必要且不合适的。此外,GNU awk 允许您通过 -i inplace 就地修改输入文件。
      • @EdMorton:感谢指针回复-i in place - 答案已更新。警告getline 适当指出-我也将它们添加到答案中;但是,在手头的 limited 情况下,我觉得它使用起来很安全,允许在 single 通行证中处理文件。来自假设反对票是你的:除非你能证明为什么我的解决方案不起作用,否则请撤消它。
      • @EdMorton:感谢您澄清和更新您的答案。我终于明白了我原来的答案出了什么问题(仅仅链接到常见问题解答是不够的)。我已经用 working getline 解决方案更新了我的答案,但我确实明白你的意思,并添加了更多背景信息和指向你答案的链接。
      • @EdMorton:是的,我确实看到您的第二个解决方案是我的更好版本(这就是为什么我的答案现在链接到它以及为什么我投票赞成它);最后,我想说这两种解决方案都不是特别容易理解,它让你希望awk 有一个$ 模式来匹配最后一行,就像sed 一样。感谢sub() 中的指针重新正则表达式文字。
      • 这实际上是一个非常好的观点 - 很多时候我们只想在输入的最后一行做一些事情,我们目前需要跳过箍来实现它(tac file | awk 'FNR==1...' | tacawk '...' file fileawk -v nr=$(wc -l &lt; file) 'NR==nr...' fileawk '..prev=$0..; END{handle prev}' fileLASTLINE 或类似的构造将非常有用!我可能会看看我是否可以让 gawk 人介绍一下,我们只是不希望 awk 成为另一个具有一百万个晦涩难懂的 perl,很少使用的结构把它弄得乱七八糟!
      【解决方案4】:

      awk 版本

      awk -F\| 'FNR==NR{f++;next} FNR==f {$NF=f} 1' OFS=\| file{,}
      H|ACCT|XEC|1|TEMP|20130215035845|
      D|849002|48|1208004|1
      D|849007|28|1208004|1
      D|849007|38|1208004|1
      T|5
      

      如果file{,}不起作用,请使用file file两次读取文件。 第一次计算行数,然后更新计数器以显示正确的行。


      只计算以H DT 开头的行

      awk -F\| 'FNR==NR{if (/^(H|D|T)/) f++;n=NR;next} FNR==n {$NF=f} 1' OFS=\| file{,}
      

      【讨论】:

      • 这是一个简短而紧凑的代码!但是有必要重新计算行数吗?是否可以只获取最后一行,然后将 2 添加到最后一个字段?
      • 没有简单的方法可以知道您在最后一行。您可以在END 块中执行 tings,但最后一行将被打印两次。
      猜你喜欢
      • 1970-01-01
      • 2012-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-23
      • 2023-04-09
      • 1970-01-01
      • 2016-02-22
      相关资源
      最近更新 更多