【问题标题】:Parsing simple string with awk or sed in linux在 linux 中使用 awk 或 sed 解析简单的字符串
【发布时间】:2021-12-28 07:44:09
【问题描述】:

原始字符串:
A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/

目录的深度会有所不同,但 /trunk 部分将始终保持不变。 /trunk 前面的单个字符是该行的指示符。

想要的输出:

A /trunk/apple
B /trunk/apple
Z /trunk/orange
Q /trunk/melon/juice/venti/straw

***编辑
对不起,我犯了一个错误,在原始字符串的每个路径的末尾添加了一个斜杠,这使输出变得混乱。原始字符串的大写字母前面没有斜线,但我会保留它。

我的尝试:

echo $str1 | sed 's/\(.\/trunk\)/\n\1/g'

我觉得它应该可以工作,但它没有。

【问题讨论】:

  • 您的问题未明确说明。请清楚地解释您如何定义所需输出的第一个和第二个字段。它是基于字母大小写吗?关于斜线的数量?其他?请注意,您展示的解决方案不会满足您的要求:它不会删除前 2 个输出行的尾部斜杠,而是添加一个空的第一个输出行。请修复它,或者解释它不是您想要的,或者更改您想要的输出示例。请通过编辑您的问题来做到这一点,而不是在 cmets 中。
  • @RenaudPacalet 编辑了问题
  • @Lunartist 第三个字符串的输出应该是Z /trunk/orange/citrus吧?
  • @Lunartist,您最初的问题是其中有一个很好的尝试代码。在编辑时它已经消失了,所以我已经阅读了它,努力(尝试过的代码)使一个问题成为一个好问题,并从关闭的问题中保存。因此,请在这个很棒的论坛上继续分享和学习。

标签: parsing awk sed


【解决方案1】:

使用 GNU awk 进行多字符 RS 和 RT:

$ awk -v RS='([^/]+/){2}[^/\n]+' 'RT{sub("/",OFS,RT); print RT}' file
A trunk/apple
B trunk/apple
Z trunk/orange

我将RS 设置为描述您要匹配的每个字符串的正则表达式,即两次重复非/s,然后是/,然后是最后一个非/s 字符串(和输入行上最后一个字符串的非换行符)。 RT 会自动设置为每个匹配的字符串,所以我只需将第一个 / 更改为空白并打印结果。

如果每条路径并不总是 3 层深,但总是以 something/trunk/ 开头,例如:

$ cat file
A/trunk/apple/banana/B/trunk/apple/Z/trunk/orange

然后:

$ awk -v RS='[^/]+/trunk/' 'RT{if (NR>1) print pfx $0; pfx=gensub("/"," ",1,RT)} END{printf "%s%s", pfx, $0}' file
A trunk/apple/banana/
B trunk/apple/
Z trunk/orange

【讨论】:

    【解决方案2】:

    要处理复杂的样本输入,比如可能有N个/和单行主干后的值,请尝试以下操作。

    awk '
    {
      gsub(/[^/]*\/trunk/,OFS"&")
      sub(/^ /,"")
      sub(/\//,OFS"&")
      gsub(/ +[^/]*\/trunk\/[^[:space:]]+/,"\n&")
      sub(/\n/,OFS)
      gsub(/\n /,ORS)
      gsub(/\/trunk/,OFS"&")
      sub(/[[:space:]]+/,OFS)
    }
    1
    '  Input_file
    

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

    awk '                                            ##Starting awk program from here.
    {
      gsub(/[^/]*\/trunk/,OFS"&")                    ##Globally substituting everything from / to till next / followed by trunk/ with space and matched value.
      sub(/^ /,"")                                   ##Substituting starting space with NULL here.
      sub(/\//,OFS"&")                               ##Substituting first / with space / here.
      gsub(/ +[^/]*\/trunk\/[^[:space:]]+/,"\n&")    ##Globally substituting spaces followed by everything till / trunk till space comes with new line and matched values.
      sub(/\n/,OFS)                                  ##Substituting new line with space.
      gsub(/\n /,ORS)                                ##Globally substituting new line space with ORS.
      gsub(/\/trunk/,OFS"&")                         ##Globally substituting /trunk with OFS and matched value.
      sub(/[[:space:]]+/,OFS)                        ##Substituting spaces with OFS here.
    }
    1                                                ##Printing edited/non-edited line here.
    '  Input_file                                    ##Mentioning Input_file name here.
    


    使用您展示的示例,请尝试关注awk 代码。

    awk '{gsub(/\/trunk/,OFS "&");gsub(/trunk\/[^/]*\//,"&\n")} 1' Input_file
    

    【讨论】:

    • 如果我可以在橙色之后有更多深度怎么办?有些可能是 /trunk/pine/queen/razor/
    • @Lunartist,你的意思是说A/trunk/apple/B/trunk/apple/Z/trunk/orange 可能是:A/trunk/apple/B/trunk/apple/Z/trunk/orange/C/apple/banana?像这样?或A/trunk/apple/B/trunk/apple/Z/trunk/orange/banana/apple 像这样?
    • 后一种。我用笨拙的双 sed 实现了我想要的,但我想用你的方法向你学习。
    • @Lunartist 如果您想要一个解决方案在每个 /trunk/ 之后不止 1 个字,那么在您的示例输入/输出中包含这样的案例,因为现在您有很多人帮助你做你在问题示例中所要求的,但 your comment 你所要求的可能不是你真正想要的。
    • 你说得对
    【解决方案3】:

    awk 你可以试试这个解决方案。它处理下一个字符为大写时删除正斜杠的特殊要求。不会赢得设计奖,但很有效。

    $ echo "A/trunk/apple/B/trunk/apple/Z/trunk/orange" | 
        awk -F '' '{ x=""; for(i=1;i<=NF;i++){ 
          if($(i+1)~/[A-Z]/&&$i=="/"){$i=""}; 
          if($i~/[A-Z]/){ printf x""$i" "}
          else{ x="\n"; printf $i } }; print "" }'
    A /trunk/apple
    B /trunk/apple
    Z /trunk/orange
    

    也适用于 n 个单词。实际上适用于任何遵循给定模式的东西。

    $ echo "A/fruits/apple/mango/B/anything/apple/pear/banana/Z/ball/orange/anything" | 
        awk -F '' '{ x=""; for(i=1;i<=NF;i++){
          if($(i+1)~/[A-Z]/&&$i=="/"){$i=""};
          if($i~/[A-Z]/){ printf x""$i" "}
          else{ x="\n"; printf $i } }; print "" }'
    A /fruits/apple/mango
    B /anything/apple/pear/banana
    Z /ball/orange/anything
    

    【讨论】:

      【解决方案4】:

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

      sed 's/[^/]*/& /;s/\//\n/3;P;D' file
      

      用空格分隔第一个单词和第一个/

      用换行符替换第三个/

      打印/删除第一行并重复。


      如果第一个单词的属性只有一个字符:

      sed 's/./& /;s#/\(./\)#\n\1#;P;D' file
      

      或者如果第一个单词具有以大写字符开头的属性:

      sed 's/[[:upper:]][^/]*/& /;s#/\([[:upper:][^/]*/\)#\n\1#;P;D' file
      

      或者如果第一个单词具有其后跟/trunk/的属性:

      sed -E 's#([^/]*)(/trunk/)#\n\1 \2#g;s/.//' file
      

      【讨论】:

      • 对于n个单词有这样的简洁解决方案吗?
      • @AndreWildberg 我想您可以将3 更改为n+1 以获取n 字词?也许另一个例子可以说明你想要什么?
      • 例如A/trunk/apple/orange/mange/B/trunk/apple/Z/trunk/orange
      • @AndreWildberg by n words 我认为您的意思是 variable 的单词数,在这种情况下,您必须定义第一个单词的属性。见编辑。
      • @EdMorton 我不是 OP,我只是好奇。
      【解决方案5】:

      使用 GNU sed:

      $ str="A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/"
      $ sed -E 's|/?(.)(/trunk/)|\n\1 \2|g;s|/$||' <<< "$str"
      
      A /trunk/apple
      B /trunk/apple
      Z /trunk/orange/citrus
      Q /trunk/melon/juice/venti/straw
      

      注意第一个空输出行。如果不希望我们可以分开处理第一行输出:

      $ sed -E 's|(.)|\1 |;s|/(.)(/trunk/)|\n\1 \2|g;s|/$||' <<< "$str"
      A /trunk/apple
      B /trunk/apple
      Z /trunk/orange/citrus
      Q /trunk/melon/juice/venti/straw
      

      【讨论】:

      • 非常清晰直接,谢谢。
      • 's|(.)|\1 | 更短为:s/./&amp; /。很好的答案。
      【解决方案6】:

      使用gnu awk,您可以使用 FPAT 使用模式设置每个字段的内容。

      循环字段时,将第一个/替换为 /

      str1="A/trunk/apple/B/trunk/apple/Z/trunk/orange"
      
      echo $str1 | awk -v FPAT='[^/]+/trunk/[^/]+' '{    
      for(i=1;i<=NF;i++) {
          sub("/", " /", $i)
          print $i
          }
      }'
      

      模式匹配

      • [^/]+ 匹配除/ 之外的任何字符
      • /trunk/[^/]+ 匹配 /trunk/ 和除 / 之外的任何字符

      输出

      A  /trunk/apple
      B  /trunk/apple
      Z  /trunk/orange
      

      更新问题后 FPAT 可以使用的其他模式:

      匹配单词边界 \\&lt; 和大写字符 A-Z 并在 /trunk 之后重复 / 和小写字符

      FPAT='\\<[A-Z]/trunk(/[a-z]+)*'
      

      如果/trunk之后的目录的字符串长度至少为2个字符:

      FPAT='\\<[A-Z]/trunk(/[^/]{2,})*'
      

      如果没有单独的文件夹由单个大写字符 A-Z 组成

      FPAT='\\<[A-Z]/trunk(/([^/A-Z][^/]*|[^/]{2,}))*'
      

      输出

      A /trunk/apple
      B /trunk/apple
      Z /trunk/orange/citrus
      Q /trunk/melon/juice/venti/straw
      

      【讨论】:

        【解决方案7】:

        假设您的数据将始终采用作为单个字符串提供的格式,您可以试试这个sed

        $ sed 's/$/\//;s|\([A-Z]\)\([a-z/]*\)/\([a-z]*\?\)|\1 \2\3\n|g' input_file
        
        $ echo "A/trunk/apple/pine/skunk/B/trunk/runk/bunk/apple/Z/trunk/orange/T/fruits/apple/mango/P/anything/apple/pear/banana/L/ball/orange/anything/S/fruits/apple/mango/B/rupert/cream/travel/scout/H/tall/mountains/pottery/barnes" | sed 's/$/\//;s|\([A-Z]\)\([a-z/]*\)/\([a-z]*\?\)|\1 \2\3\n|g'
        A /trunk/apple/pine/skunk
        B /trunk/runk/bunk/apple
        Z /trunk/orange
        T /fruits/apple/mango
        P /anything/apple/pear/banana
        L /ball/orange/anything
        S /fruits/apple/mango
        B /rupert/cream/travel/scout
        H /tall/mountains/pottery/barnes
        

        【讨论】:

        • 你能解释一下为什么第二行使用|而不是/作为分隔符吗?
        • @Lunartist 那是因为数据本身包含一个斜杠/,这将与seds默认分隔符冲突,从而引发错误。第一个 sed 命令只专注于替换字符串的结尾绕过冲突,因为那里没有斜线。
        【解决方案8】:

        perl 的一些乐趣,您可以使用非消耗正则表达式自动拆分为 @F 数组,然后随意打印。

        perl -lanF'/(?=.{1,2}trunk)/' -e 'print "$F[2*$_] $F[2*$_+1]" for 0..$#F/2'

        第 1 步:拆分

        • perl -lanF/(?=.{1,2}trunk)/'
        • 这将获取输入流,并在遇到.{1,2}trunk 模式时分割每一行
        • 因为我们想要保留 trunk 和前面的 1 或 2 个字符,所以我们将拆分模式包装在 (?=) 中以实现不消耗的前瞻
        • 这样拆分:
        $ echo A/树干/苹果/B/树干/苹果/Z/树干/橙子/柑橘/Q/树干/甜瓜/果汁/venti/稻草/ | perl -lanF'/(?=.{1,2}trunk)/' -e 'print join " ", @F' A /树干/苹果/ B /树干/苹果/ Z /树干/橙子/柑橘/ Q /树干/甜瓜/果汁/文蒂/稻草/

        第二步:格式化输出:

        • @F 数组包含我们要按顺序打印的对,因此我们将迭代一半的数组索引,并一次打印 2 个:
        • print "$F[2*$_] $F[2*$_+1]" for 0..$#F/2 --> 将迭代器加倍,并打印对
        • 使用perl -l 意味着每个print 在末尾都有一个隐含的\n
        • 结果:
        $ echo A/树干/苹果/B/树干/苹果/Z/树干/橙子/柑橘/Q/树干/甜瓜/果汁/venti/稻草/ | perl -lanF'/(?=.{1,2}trunk)/' -e 'print "$F[2*$_] $F[2*$_+1]" for 0..$#F/ 2' 一个/树干/苹果/ B /树干/苹果/ Z /树干/橙子/柑橘/ Q /树干/甜瓜/果汁/venti/稻草/

        尾注:Perl 混淆不起作用。

        • perl 中的任何数组都可以转换为散列,格式为 (key,val,key,val....)
        • 所以%F=@F; print "$_ $F{$_}" for keys %F 看起来真的很漂亮
        • 但你失去了秩序:
        $ echo A/树干/苹果/B/树干/苹果/Z/树干/橙子/柑橘/Q/树干/甜瓜/果汁/venti/稻草/ | perl -lanF'/(?=.{1,2}trunk)/' -e '%F=@F;为键 %F' 打印 "$_ $F{$_}" Z /树干/橙子/柑橘/ 一个/树干/苹果/ Q /树干/甜瓜/果汁/venti/稻草/ B /树干/苹果/

        【讨论】:

          【解决方案9】:

          更新

          使用您的新数据文件:

          $ cat file
          A/trunk/apple/B/trunk/apple/Z/trunk/orange/citrus/Q/trunk/melon/juice/venti/straw/
          

          这个 GNU awk 解决方案:

          awk '
          {
          sub(/[/]$/,"")
          gsub(/[[:upper:]]{1}/,"& ")
          print gensub(/([/])([[:upper:]])/,"\n\\2","g")
          }' file
          A /trunk/apple
          B /trunk/apple
          Z /trunk/orange/citrus
          Q /trunk/melon/juice/venti/straw
              
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-08-07
            • 2019-09-16
            • 2015-10-12
            • 2015-10-25
            • 2011-04-07
            • 1970-01-01
            • 1970-01-01
            • 2023-01-14
            相关资源
            最近更新 更多