【问题标题】:remove the last occurrence of } from a file从文件中删除最后出现的 }
【发布时间】:2014-06-04 21:40:17
【问题描述】:

我正在尝试使用 sed 从文件中删除最后一次出现的 }。到目前为止,我有这个:

sed -i 's/\(.*\)}/\1/' file

但这会删除与文件末尾一样多的 }。因此,如果我的文件如下所示:

foo
bar
}
}
}

该命令将删除所有 3 个 } 字符。如何将其限制为最后一次出现?

【问题讨论】:

    标签: bash awk sed


    【解决方案1】:

    有人把这个当作解决方案来欺骗我

    sed -i '1h;1!H;$!d;g;s/\(.*\)}/\1/' file
    

    我只是不确定它是否与上述 awk 解决方案一样好。

    【讨论】:

    • 它是一次性的,和管道过滤器一样好用,它不需要任何额外的命令来就地修改文件,它只删除最后一个}而不是整行包含它。所以我认为它更好。编辑:sed 有 E 开关以启用更具可读性的 RE 语法:sed -iE '1h;1!H;$!d;g;s/(.*)}/\1/' file
    • @jthill 同意,尽管它只替换了最后一个匹配项,没有有效地留下空白行。 sed -r '1h;1!H;$!d;g;s/(.*)}\n/\1/' file 可能更合适。
    • @jaypal:你的修改不太有效:试试sed -E '1h;1!H;$!d;g;s/(.*)}\n/\1/' <<<$'a}\nb\n}'(注意<<<总是附加\n,所以有效的输入确实以@结尾987654329@)。 - 我认为原因是缓冲区的lastnot\n 结尾。如果你想修剪尾随换行符,你可以添加; s/\n+$//
    • @jaypal 我认为您可能已经屈服于优化测试用例的诱惑,我认为这也产生了早期的 awk 答案。问题只说~最后一个}~。
    • 尝试修改它以做一些不同的事情(例如,除了 } 之外,删除前面的换行符,或者如果删除发生,则将 msg 打印到 stderr,或者其他任何事情)并且会告诉你它是否和 awk 解决方案一样好。如果您的下一个问题的要求略有不同或对这一更改的要求 - 您是否希望新解决方案的起点是像这样的 sed 脚本作为构建的基础,还是一个简单、可读的 awk 脚本。
    【解决方案2】:

    sed 是在单行上进行简单替换的出色工具。对于其他任何事情,只需使用 awk,例如使用 GNU awk 进行 gensub() 和多字符 RS:

    $ cat file1
    foo
    bar
    }
    }
    }
    $
    $ cat file2
    foo
    bar
    }}}
    $
    gawk -v RS='^$' -v ORS= '{$0=gensub(/\n?}([^}]*)$/,"\\1","")}1' file1
    foo
    bar
    }
    }
    $
    $ gawk -v RS='^$' -v ORS= '{$0=gensub(/\n?}([^}]*)$/,"\\1","")}1' file2
    foo
    bar
    }}
    $
    

    请注意,上面将删除最后一个 } 字符和前面的换行符,如果我认为这可能是您真正想要的,但如果您只想删除 } 并在这些情况下留下尾随换行符(如我认为所有当前发布的 sed 解决方案都可以),然后从匹配的 RE 中删除 \n?

    $ gawk -v RS='^$' -v ORS= '{$0=gensub(/}([^}]*)$/,"\\1","")}1' file1
    foo
    bar
    }
    }
    
    $
    

    如果您想更改原始文件而不手动指定 tmp 文件,只需使用 -i inplace 参数:

    $ gawk -i inplace -v RS='^$' -v ORS= '{$0=gensub(/}([^}]*)$/,"\\1","")}1' file1
    $ cat file1
    foo
    bar
    }
    }
    
    $
    

    【讨论】:

    • 我唯一的问题是我必须将输出重定向到一个临时文件,然后将其复制到原始文件上。我尝试使用 gawk -i inplace 但这破坏了 awk 命令。
    • 为什么使用临时文件是个问题?只需执行awk '...' file file > tmp && mv tmp file。 nbd...
    • @Craig - }}} 可以出现在输入中吗?如果出现,预期的输出是什么?
    • 是的,可以。预期的输出将是 }}
    • 好的,那么我只是提供了一个不同的解决方案 - 我之前删除了包含最后一个 } 的行,现在我只是删除了最后一个 } 字符(如果存在,则可以选择前面的换行符)我猜这就是您想要的,而不是简单地删除字符,但如果这是不可取的,请发布更多示例输入和预期输出)。如果重要的话,我还使用-i inplace 而不是显式的 tmp 文件展示了这个解决方案。
    【解决方案3】:

    使用缓冲区可以直接修改文件:

    awk  'BEGIN{file=ARGV[1]}{a[NR]=$0}/}/{skip=NR}END{for(i=1;i<=NR;++i)if(i!=skip)print a[i]>file}' file
    

    【讨论】:

    • awk 有一个内置变量FILENAME,因此您不需要自己的file 变量,您可能应该在开始循环之前将close() END 部分开头的文件虽然可能 awk 会为你做这件事,但写信给它,idk。
    • @EdMorton 可能 awk 会这样做,因为 END{} 已经在另一个上下文中。实际的编程实践是关闭每个会话的文件(来自参数的输入),而不是在另一个会话之前关闭文件。至于 FILENAME,awk 的手册并没有明确说它会在输入END 之前保留它的值。如果您担心文件是否会默认关闭等默认行为,那么您也应该考虑这一点。不过还是可以用的。
    • 来自 POSIX 标准 wrt 文件名:Inside an END action the value shall be the name of the last input file processed.
    • 好的,请注意。下次我会考虑使用它。
    【解决方案4】:

    感谢@jthill 对 1 行文件问题的评论

    sed ':a
    $ !{N
        ba
        }
    $ s/}\([^}]*\)$/\1/' YourFile
    

    需要先加载缓冲区中的文件。如果 } 单独在一行上,这不会删除新行

    【讨论】:

    • 如果文件只有一行,这将删除它。为了缓冲分支,我会使用:a;$!{N;ba}
    【解决方案5】:

    当我读到“用last ...做某事”时,我认为“反转文件,用first做一些事情...,重新反转文件”

    tac file | awk '!seen && /}/ {$0 = gensub(/(.*)}/, "\\\1", 1); seen = 1} 1' | tac
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-12
      • 2011-06-20
      相关资源
      最近更新 更多