【问题标题】:Remove everything after 2nd occurrence in a string in unix在 unix 中的字符串中第二次出现后删除所有内容
【发布时间】:2014-05-16 00:12:52
【问题描述】:

我想在特定的第二次出现后删除所有内容 字符串中的模式。在 Unix 中最好的方法是什么?什么是最优雅和最简单的方法来实现这一点; sedawk 或者只是像 cut 这样的 unix 命令?

我的意见是

After-u-math-how-however

输出应该是

After-u

第二个- 之后的所有内容都应该被删除。正则表达式也应该匹配 模式的零次出现,因此零次或一次应被忽略,并且 从第 2 次开始,所有东西都应该被删除。

所以如果输入如下

输出应该是

【问题讨论】:

    标签: regex bash unix awk sed


    【解决方案1】:

    这样的事情就可以了。

    echo "After-u-math-how-however" | cut -f1,2 -d'-'
    

    这会将字符串拆分(剪切)为字段,使用破折号 (-) 作为分隔符。将字符串拆分为字段后,cut 将打印第一个和第二个字段。

    【讨论】:

    • 看起来最好!关于如何在 sed 或 awk 中获得相同的任何想法?
    • 如何反向实现?只需在字符串中剪切“然而”并打印它。不管字符串有多大
    • @HussainK - 使用stackoverflow.com/questions/22727107/…,你可以... | rev | cut -f1 -d'-' | rev
    【解决方案2】:

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

    sed 's/-[^-]*//2g' file
    

    【讨论】:

      【解决方案3】:

      您可以使用以下正则表达式来选择您想要的:

      ^[^-]*-\?[^-]*
      

      例如:

      echo "After-u-math-how-however" | grep -o "^[^-]*-\?[^-]*"
      

      结果:

      After-u
      

      【讨论】:

      • +1;但是请注意,FreeBDS grep 2.5.1(例如,从 OS X 10.9.3 开始)中似乎存在 bug,导致 ^ 锚被忽略,从而可能导致multiple 匹配(因此有多个输出行)。适用于 GNU grep
      【解决方案4】:

      @EvanPurkisher 的 cut -f1,2 -d'-' 解决方案是恕我直言最好的解决方案,但既然您询问了 sed 和 awk:

      使用 GNU sed 为-r

      $ echo "After-u-math-how-however" | sed -r 's/([^-]+-[^-]*).*/\1/'
      After-u
      

      使用 GNU awk 获取 gensub()

      $ echo "After-u-math-how-however" | awk '{$0=gensub(/([^-]+-[^-]*).*/,"\\1","")}1'
      After-u
      

      可以在非 GNU sed 上使用 \(* 完成,在非 GNU awk 上使用 match()substr() 如有必要。

      【讨论】:

      • +1 表示sed 解决方案;使用 -E 而不是 -r 将使该命令同时适用于 GNU (Linux) 和 BSD (OSX) sed。 POSIX sed,使用 basic 正则表达式,可以模拟+,即\{1,\}: sed 's/\([^-]\{1,\}-[^-]*\).*/\1/'
      • @IsinAltinkaya 表达偏好的方式是支持您喜欢的答案。例如,我赞成 potong 的回答。
      【解决方案5】:
      awk -F - '{print $1 (NF>1? FS $2 : "")}' <<<'After-u-math-how-however'
      
      • 根据字段分隔符 -(选项规范。-F -)将行拆分为字段 - 可作为特殊变量 FS awk 程序中访问。
      • 始终打印第一个字段 (print $1),然后是:
        • 如果有超过 1 个字段 (NF&gt;1),则附加 FS(即-)和第二个字段 ($2)
        • 否则:追加"",即:仅打印第一个字段(如果输入为空,则该字段本身可能为空)。

      【讨论】:

        【解决方案6】:

        这可以在纯 bash 中完成(这意味着没有分叉,没有外部进程)。读入在 '-' 上拆分的数组,然后对数组进行切片:

        $ IFS=-
        $ read -ra val <<< After-u-math-how-however
        $ echo "${val[*]}"
        After-u-math-how-however
        $ echo "${val[*]:0:2}"
        After-u
        

        【讨论】:

        • 很好的解决方案。不过,您应该在之后重置 IFS,不是吗?
        • @EvanPurkhiser 不,您应该使用范围来管理值。将上面的代码放在带有local IFS的函数中,而不是尝试手动保存和恢复原始IFS。
        • 因此,积极的一面是有 no fork, no external process(我们为什么要关心?)但消极的一面是您仍然需要编写更多代码来管理 IFS 更改的范围,另外,如果您想要在多于 1 行上执行此操作,您需要手动编写一个循环来处理每一行(与 sed 和 awk 解决方案不同),此外,它会错误地处理输入中的任何反斜杠,另外您需要考虑是否存在globbing 影响,此外,您还需要考虑回声是否会按预期运行。 Shell 是一个调用工具的环境。
        • @EdMorton 所有这些“否定”都以“如果”开头。 “如果”你没有明确你的要求,那么你会得到一个概括的答案,在某些情况下可能是最佳的,而在其他情况下可能不是最佳的。 Shell 是一个从中调用工具的环境,通常了解哪些工具内置于 shell 中是很有价值的,而不是总是依赖awksed
        • @EdMorton 还有什么全局影响? 1. Bash 不会扩展字符串中的 glob。 2. shell 不会在双引号参数扩展中扩展 glob,包括数组扩展。在此答案中遇到 glob 问题的唯一方法是删除引号,这将大大改变答案。
        【解决方案7】:
        awk '$0 = $2 ? $1 FS $2 : $1' FS=-
        

        结果

        后你 后

        【讨论】:

          【解决方案8】:

          这将在 awk 中完成:

          echo "After" | awk -F "-" '{printf "%s",$1; for (i=2; i<=2; i++) printf"-%s",$i}'
          

          【讨论】:

          • 好吧,我又破解了。尽管我的判断更好,因为 OP 没有进行任何研究或尝试解决。
          猜你喜欢
          • 1970-01-01
          • 2015-09-19
          • 1970-01-01
          • 1970-01-01
          • 2021-11-26
          • 1970-01-01
          • 2010-11-11
          • 2017-07-07
          • 1970-01-01
          相关资源
          最近更新 更多