【问题标题】:Is it possible to do simple arithmetic in sed addresses?是否可以在 sed 地址中进行简单的算术运算?
【发布时间】:2012-09-02 07:00:05
【问题描述】:

是否可以在 sed 地址中进行简单的算术运算? 从"addresses" manual section 来看,答案似乎不是。但也许有解决方法?

例如,如何打印文件的倒数第二行?这会很酷,比如:

sed -n '$-1 p' file

但这显然不起作用...所以我通常必须进行多次 sed 调用,首先用于识别行,然后使用 shell $((expr)) 进行算术运算,最后再次调用 sed。像这样:

sed -n "$(($(sed -n '$ =' file)-1)) p" file

是否有一种“更好”、更紧凑、更易读的方法来使用 sed 地址进行算术运算?


在一个严重的拖延时刻,我决定写一个小script that quickly changes the xterm colorscheme。这个想法是您拥有.Xresources 一个带有开始标记和结束标记的文件:

...
START_MARKER
...
END_MARKER
...

并且您想删除标记之间的所有内容,而不是标记本身。同样,做这样的事情会很棒:

sed '/START_MARKER/+1,/END_MARKER/-1 d' file

...但你不能!

【问题讨论】:

  • 你能解释一下你想要做什么吗?听起来sed 不是适合这项工作的工具。
  • 我添加了另一个示例,它可以方便地使用地址进行简单的算术运算,而这正是我试图解决的问题。
  • sed 对输入数据流进行操作,因此不知道它何时位于倒数第二行。它只能检测到最后一行

标签: linux shell sed


【解决方案1】:

你是对的,一个人不能直接在 sed1 中做数学,甚至地址。但是你可以使用一些诡计来做你想做的事:

倒数第二行:

$ seq 5 | sed -n -e '${ # On the last line
> g # Replace the buffer with the hold space
> p # and print it
> }
> h' # All lines, store the current line in the hold space.
4

STARTEND 之间:

$ cat test.in
1
START
2
3
END
4
$ cat test.in | sed '/^START$/,/^END$/{
> /^START$/d
> /^END$/d
> p
> }
> d'
2
3
$ cat test.in | sed -n -e '/^START$/,/^END$/!d' -e '/^START/d' -e '/^END$/d' -e p
2
3

我使用的是 BSD (mac) sed;在 GNU 系统上,您可以在行之间使用 ; 而不是换行符。或者将其粘贴在脚本中。

1:Sed 是图灵完备的,所以你可以做数学,但它充其量是笨拙的:http://rosettacode.org/wiki/A%2BB#sed

是的,我知道,UUOC;仅供参考

【讨论】:

  • 因此,结论似乎没有通用方法来处理地址中的算术,只有非常具体的解决方法。即使您对“倒数第二行问题”的解决方案也不能推广到“倒数第三行问题”......
【解决方案2】:

删除倒数第二行:

sed ':r;$!{N;br};s/\n[^\n]*\(\n[^\n]*\)$/\1/' file

删除标记内的所有内容:

sed ':r;$!{N;br};s/START_MARKER.*END_MARKER/START_MARKER\nEND_MARKER/' file

远非优雅,但有点工作。

正如 cmets 中提到的,sed 在线运行。但是,您可以使用N 命令将另一行读入模式空间。这两行现在都在模式空间中,并用\n 分隔。 sed 也有执行流控制的手段,即标签和条件/无条件分支。一切都记录在man sed 中,here 也是完整的示例参考。在上面的代码中r是一个标签; $!{..} 的意思是“除了最后一行之外的任何地方,都执行..N;br 读取另一行并再次无条件地分支到r。因此使用:r;$!{N;br},您可以将所有输入读入模式空间,然后对其进行操作作为单行,\n 分隔输入的行。

【讨论】:

  • 您能否对其中发生的事情做一个简单的解释:)?
  • 什么sed 版本像这样跨越换行符?没有我见过,也没有我刚刚明确尝试过的三个(mac/BSD、GNU、busybox)。
  • . 被明确定义为换行符
  • @Kevin 我不确定我理解你所说的换行符是什么意思。这些示例似乎在我的GNU sed version 4.2.1 中有效,并且它确实与. 匹配换行符。
  • 非常有趣的把戏。肯定会尝试记住 throw all in pattern space 命令 :r;$!{N;br}。正如您所承认的,它看起来并不适合这种情况。
【解决方案3】:

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

sed '$!N;$s/.*\n//;P;D' file

这很有效,应该很容易理解:

sed '/start/,/end/!d;//d' file

这些是您问题的解决方案,但算术最好使用 awk 或 perl。

【讨论】:

    【解决方案4】:

    你有一些很好的sed 建议,这里有一个基于 GNU awk 的建议:

    awk -v RS='START_MARKER|END_MARKER' 'RT == "END_MARKER"' infile
    
    • RS='START_MARKER|END_MARKER' 使用标记作为分隔符分割输入。
    • RT 设置为匹配的分隔符,当它匹配“END”时,默认块 {print $0} 被执行。

    例如,如果您想打印除最后三行之外的所有行,请将 FS 设置为 \n 并应用适当的循环:

    awk -v RS='START_MARKER|END_MARKER' -v FS='\n' 'RT == "END" { for(i=1; i<NF-3; i++) print $i }' infile
    

    【讨论】:

      【解决方案5】:

      您可以使用简单的方法显示文件的倒数第二行。

      TOTAL_LENGTH=$(cat file_name | wc -l)
      SECOND_LAST_LINE=`expr $TOTAL_LENGTH - 1`
      head -$SECOND_LAST_LINE | tail -1
      

      如果你想从文件中删除倒数第二行:

      sed -i "$SECOND_LAST_LINE"d file_name
      

      【讨论】:

      • ... 或cat file | tail -2 | head -1。问题是关于 sed 中的地址操作。还是谢谢!
      • 无用的猫。两个缺点。
      【解决方案6】:

      解决方案#2 中给出了在sed 中进行算术的更全面的处理。使用sed `sed' 自己的脚本的介绍在这里。

      由于过度“挥手”的不切实际的评论要求在解决方案 2 中引起的脑部疼痛实际上是过多的代码“挥手”,并列,这是解决方案 3:

      echo -e 'a\nb\nc\nd\ne' | sed -n '1!G;h;$p' | sed -n 3p
      

      它仍然使用管道(“但也许有解决方法?”),其中数字 3 必须从文件末尾“手动”替换为所需的行 ala $-3

      假设sed 脚本是'$-4 p; $-6p; $-8 p;'

      echo -e 'a\nb\nc\nd\ne\nf\ng\nh\ni'  | 
      sed  -n '1!G;h;$p'                   | 
      sed  -n '4 p; 6p; 8 p;'              | 
      sed  -n '1!G;h;$p'                   
      

      通过以下方式完成工作

      echo  '$-4 p;  $-6p;  $-8 p;'  |   sed s/$-//
      

      注意事项:
      sed 命令必须像 print 一样简单。
      “简单算术”只能是 '$-n' 的形式。
      算术不是“正常”计算的。

      “单个”“sed”命令字符串(如果将先前的管道视为此类,则为“行”)将嵌入并组合这两个命令,如下一个答案 #2 中所述。


      致命一击。

      鉴于这里第一个答案的敷衍解雇是#2:

      由于这只是第二次或第三次编写大量 sed 脚本,严重的语法微妙(s)规避破坏解决方案似乎就足够了:ala

      # file prep 
      echo -e '  a\n  b\n  c\n  d\n  e\n  f' >test
      

      下面的三振不是不正确的,但是在使用sedwith an SO problem over heresedexecute 玩并“搞砸”之后,如果从模式缓冲区运行以获取文件,则可以更简单地不进行 IO 重定向长度行数$ via:

      sed  -e  '1{h; s/.*/sed -n  "$="  test  /e' -e 'p;x}; ${p;x;}' test
      

      $= 枚举从一开始就保存在保存缓冲区中,并在最后再次打印。

      # get "sed -n  $= test" command output into sed script
      sed -n '1esed -n  "$=" test >sedr' test
      
      # see where this is headed? so far "sed -n ... test" is irrelevant
      # a pedantic "sed" only solution would keep it this way with
      # all the required "sed"'ng as part of an 'e' command or '$e'
      # where the 'sedr' file is itself "sed"'d ultimately to a final
      # command 'sed -n /<the calculated line number>/p' 
      
      # one could quibble whether '>sedr' io redirection is "pure sed" 
      
      # modify 'sedr'with [the sed RPN][1] to get <the calculated line number>
      
      罢工>
      # with judicious use of "sed"'s 'r' command and buffering will
      # realize the effective script to compute the desired result
      
      # this is left as an exercise needing perverse persistence with
      # a certain amount of masochistic agony
      

      作为如何进行的提示;使用解决方案#3 的技术sed 脚本$- 地址现在被$= 值和- 替换。所以sed再次被用来编辑自己的脚本。

      解析sed 脚本必须准确地修改地址中的$-

      此外,要使用 RPN 计算器,中缀算术必须具有后置固定运算符。将波兰表示法或其反转转换为中缀,反之亦然,这是自动机和形式语言理论中的传统范式。

      希望这可以确定可以做到的肯定答案(mais, pas par moi)和否定的答案不是微不足道的练习(c'est par moi)。

      任意解决方案的令人痛苦的理由在最后。

      用于经验测试的环境:

      linuxuser@ubuntu:~$ sed --version
      sed (GNU sed) 4.4
      Copyright (C) 2017 Free Software Foundation, Inc.
      
      linuxuser@ubuntu:~$  uname -a
      Linux ubuntu 4.15.0-74-generic #84-Ubuntu SMP Thu Dec 19 08:06:00 UTC 2019 i686 i686 i686 GNU/Linux
      
      linuxuser@ubuntu:~$ lsbname -a
      lsbname: command not found
      linuxuser@ubuntu:~$ apropos lsb
      lsb_release (1)      - print distribution-specific information
      lsblk (8)            - list block devices
      linuxuser@ubuntu:~$ lsb_release -a
      No LSB modules are available.
      Distributor ID: Ubuntu
      Description:    Ubuntu 18.04.2 LTS
      Release:    18.04
      Codename:   bionic
      

      解决方案 #1

      一种在盒子外面思考的技巧:

      seq 60  |  sed -n '$!p'  |  sed -n '$!p'  |  sed -n '$!p' |  sed -n '$p'
      

      哪个打印:

      57
      

      具体来说,对于倒数第二行:

      sed -n '$!p' file  |  sed -n '$p'
      

      更一般地说,脚本可以遍历 sed -n '$!p' 以从文件末尾“倒数”。


      嗯,答案是:

      是否可以在 sed 地址中进行简单的算术运算?

      在修辞上,它取决于一个人的能力、愿望和愿望以及对实用性的现实评估。同样,这意味着单个sed 调用应该专门用于此任务。但是是的,这是可能的。

      在自动机、形式语言和递归函数理论的研究中打下坚实的基础并没有什么坏处。

      如之前的答案所述:sed 不仅可以进行简单的算术运算,还可以执行任何包含复杂算术运算的可计算函数。然而,这样做需要实现递归函数理论 (RFT) 的原始递归函数 (PRF)(当然 sed 会这样做)。当然,机器架构的有限大小确实限制了没有无限磁带资源的计算,正如图灵机所证明的那样。在任何不希望证明这一点的情况下,可以在sed 手册中找到先例。

      具体来说,做算术(有限)一个 RPN 计算器:
      https://www.gnu.org/software/sed/manual/html_node/Increment-a-number.html#FOOT9

      现在,使用这样的工具可以创建一个sed 脚本,该脚本预先计算算术,然后嵌入到sed 脚本中以打印所需的输出。 OP 给出了一个简单的演示,指出现在可以使用 RPN sed 脚本完成 shell 算术计算。

      这会简化为(非常粗略)这样的形式

      sed '/$(sed RPN($= - 3*4) file)/;p;' file
      

      但仍需要提供 sed 一个 sed'd 脚本。 此外,可以说对使用bash $() 存在争议,但可以说bash 已经用于执行第一个“sed”,所以没有伤害没有犯规。

      认识到 sed 实现了 PRF 或等效地是图灵完备意味着是的,sed 的单个调用就足够了。

      因此范式可以做到这一点。

      一些可以加快这项任务的命令是:

      e, e command, r, R, w, W
      

      除了通常的保持和模式缓冲区命令。

      r, R, w, W 命令作为临时缓冲区空间特别有利。

      e [command]  [3.7 Commands Specific to GNU sed][2]
      
      This command allows one to pipe input from a shell command into
      pattern space. Without parameters, the e command executes the 
      command that is found in pattern space ... 
      

      更抽象地说,编写sed 脚本来执行sed 范式本身是完全有可能的,尽管非常不切实际,该范式本身也包括地址中的算术计算。


      sed 的特殊性。表达式 /\n/ 将不匹配任何地址,并且仅当 sed 命令(如 'N'ext 或 s/.*/\n/ 引入一个)时才匹配模式空间。 确认方式:

      echo -e '\n\n'  | sed -n '   /\n/    {s//hello/;p}'
      

      但是

      echo -e '\n\n'  | sed -n '0,/\n\n\n/ {s//hello/;p}'
      

      输出 3 个空行和

      echo -e '\n\n'  | sed -n '0,/\n/     {s/.*/hello/;p}'
      echo -e '\n\n'  | sed -n '0,/\n\n\n/ {s/.*/hello/;p}'
      

      每个输出3你好

      hello
      hello
      hello
      

      虽然这是表现良好:

      echo -e '\n\n'  | sed -n '0,/^$/     {s//hello/;p}'
      

      【讨论】:

      • 没有解决地址中的算术问题;您正在“手动”执行相同的操作 4 次。
      • 在框外思考,只使用 sed,因为它必须在 bash 中输入,所以 |'ng 是包容性的
      • - 确实有争议 - 但是 OP 确实问过:但也许有解决方法? - 对于 sed 加/减运算,请参阅 rosettacode.org/wiki/A%2BB#sed,然后使用 sed 编辑 sed 脚本来完成任务
      猜你喜欢
      • 2016-11-30
      • 2022-12-10
      • 2018-06-22
      • 1970-01-01
      • 2020-10-14
      • 1970-01-01
      • 2017-09-30
      • 1970-01-01
      • 2020-01-25
      相关资源
      最近更新 更多