【问题标题】:Delete all comments in a file using sed使用 sed 删除文件中的所有注释
【发布时间】:2012-11-12 23:27:39
【问题描述】:

如果'#'在字符串中,你将如何从文件(用#定义)中删除所有使用sed的cmets?

This 帮了很多忙,除了字符串部分。

【问题讨论】:

  • # 可以在任何上下文中出现在评论开始之前吗?
  • 字符串是如何定义的?评论中会出现字符串字符(如引号)吗?
  • 如果您在 shell 脚本中谈论 cmets,您需要担心的不仅仅是字符串。例如echo foo#barecho ${foo#bar} ${foo##bar}中没有评论。
  • 这里还需要处理docs和多行字符串。
  • @WilliamPursell 我忘了还有$#${#foobar}

标签: bash sed


【解决方案1】:

如果# 总是表示注释,并且可以出现在一行的任何位置(例如在某些代码之后):

sed 's:#.*$::g' <file-name>

如果要原地更改,请添加-i 开关:

sed -i 's:#.*$::g' <file-name>

这将从任何# 删除到行尾,忽略任何上下文。如果您在不是评论的任何地方(例如在字符串中)使用#,它也会将其删除。

如果 cmets 只能从行首开始,请执行以下操作:

sed 's:^#.*$::g' <file-name>

如果它们前面可能有空格,但没有别的,这样做:

sed 's:^\s*#.*$::g' <file-name>

这两个会更安全一些,因为它们可能不会删除代码中# 的有效用法,例如字符串。

编辑:

实际上并没有一种很好的方法来检测字符串中是否存在某些内容。如果能满足您的语言限制,我会使用最后两个。

检测你是否在一个字符串中的问题是正则表达式不能做所有事情。有几个问题:

  • 字符串可能跨行
  • 正则表达式无法区分撇号和单引号
  • 正则表达式不能匹配嵌套引号(这些情况会混淆正则表达式):

    # "hello there"
    # hello there"
    "# hello there"
    

如果双引号是定义字符串的唯一方式,双引号将永远不会出现在注释中,并且字符串不能跨越多行,请尝试以下操作:

sed 's:#[^"]*$::g' <file-name>

这是很多先决条件,但如果它们都成立,那么你就是在做生意。否则,恐怕你是 SOL,你最好用 Python 之类的东西来写它,在那里你可以做更高级的逻辑。

【讨论】:

  • sed 's:#[^"]*$::g' &lt;file-name&gt; 错误地删除了 sed 's/\*/\&amp;#42;/g'# 之后的所有内容
  • 在# 之前需要一个空格是个好主意。分两步,以 # 开头的行和包含 (space#) 的行,如下所示:sed -e 's/^[ \t]*#[^!].*$//g' -e 's/[ \t]#.*$//g'。这将避免大多数其他脚本用于#:echo "${#a} $# ${a#3} $(( 16#11 ))"。 `
  • g 标志是必要的吗?它不像一行文本会有多个行尾。
【解决方案2】:

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

sed '/#/!b;s/^/\n/;ta;:a;s/\n$//;t;s/\n\(\("[^"]*"\)\|\('\''[^'\'']*'\''\)\)/\1\n/;ta;s/\n\([^#]\)/\1\n/;ta;s/\n.*//' file
  • /#/!b 如果该行不包含 # bail out
  • s/^/\n/ 插入唯一标记 (\n)
  • ta;:a 跳转到循环标签(重置替代真/假标志)
  • s/\n$//;t如果标记在行尾,删除并退出
  • s/\n\(\("[^"]*"\)\|\('\''[^'\'']*'\''\)\)/\1\n/;ta 如果标记后面的字符串是带引号的,则将标记向前移动并循环。
  • s/\n\([^#]\)/\1\n/;ta 如果标记后面的字符不是#,则将标记向前撞并循环。
  • s/\n.*// 行的其余部分是注释,删除标记和行的其余部分。

【讨论】:

  • 这是否也替换了 HERE 文档中的 cmets?
  • 此代码不处理双引号字符串中的转义双引号。它也不处理像${parameter#word}这样的扩展命令。
【解决方案3】:

由于提问者没有提供示例输入,因此我将假设几种情况,并且 Bash 是输入文件,因为 bash 被用作问题的标签。

案例1:整行都是注释

在大多数情况下,以下内容应该足够了:

sed '/^\s*#/d' file

它匹配任何没有或至少有一个前导空白字符(空格、制表符或其他几个,请参阅man isspace)的行,后跟#,然后删除@987654324 的行@ 命令。

任何行,如:

# comment started from beginning.
         # any number of white-space character before
    # or 'quote' in "here"

它们将被删除。

但是

a="foobar in #comment"

不会被删除,这是想要的结果。

案例2:实际代码后注释

例如:

if [[ $foo == "#bar" ]]; then # comment here

评论部分可以删除

sed "s/\s*#*[^\"']*$//" file

[^\"'] 用于防止引号字符串混淆,但这也意味着带有引号'" 的cmets 不会被删除。

最终的 sed

sed "/^\s*#/d;s/\s*#[^\"']*$//" file

【讨论】:

  • 不,对于问题中所述的特定问题,这特别失败;也就是说,它不会单独留下带引号的字符串。
  • 或者最好的,一个完整的文件来测试预期的输出。我们不是通灵者,我们无法猜测。在我的回答中,它确实处理带引号的字符串。
  • @tripleee,我刚刚意识到你不是提问者。我的代码确实将# 单独留在字符串或带引号的字符串中,至少在单行字符串中。由于提问者没有具体说明字符串是什么,所以我的回答是正确的。
  • @tripleee 还是,我想看看你在说什么引用的字符串,请用示例案例评论。
【解决方案4】:

要删除注释行(第一个非空白字符为# 的行)但不是 shebang 行(第一个字符为#! 的行):

sed '/^[[:space:]]*#[^!]/d; /#$/d' file

sed 的第一个参数是一个包含 sed 程序的字符串,该程序由 /regex/d 形式的两个删除行命令组成。命令由; 分隔。第一个命令删除注释行但不删除 shebang 行。第二个命令删除任何剩余的空注释行。它不处理尾随 cmets。

sed 的最后一个参数是用作输入的文件。在 Bash 中,您还可以像这样对字符串变量进行操作:

sed '/^[[:space:]]*#[^!]/d; /#$/d' <<< "${MYSTRING}"

例子:

# test.sh
S0=$(cat << HERE
#!/usr/bin/env bash
# comment
  # indented comment
echo 'FOO' # trailing comment
# last line is an empty, indented comment
  #
HERE
)
printf "\nBEFORE removal:\n\n${S0}\n\n"
S1=$(sed '/^[[:space:]]*#[^!]/d; /#$/d' <<< "${S0}")
printf "\nAFTER removal:\n\n${S1}\n\n"

输出:

$ bash test.sh

BEFORE removal:

#!/usr/bin/env bash
# comment
  # indented comment
echo 'FOO' # trailing comment
# last line is an empty, indented comment
  #    


AFTER removal:

#!/usr/bin/env bash
echo 'FOO' # trailing comment

【讨论】:

    【解决方案5】:

    假设“在一个字符串中”的意思是“出现在一对引号之间,无论是单引号还是双引号”,这个问题可以改写为“删除第一个未加引号的 # 之后的所有内容”。反过来,您可以将带引号的字符串定义为两个引号之间的任何内容,反斜杠引号除外。作为一个小改进,将整行替换为第一个未加引号的 # 之前的所有内容。

    所以我们得到了类似[^\"'#] 的简单情况——一段既不是注释符号,也不是反斜杠,也不是开头引号的字符串。然后我们可以接受一个反斜杠后跟任何内容:\\.——这不是文字点,而是文字反斜杠,后跟一个匹配任何字符的点元字符。

    然后我们可以允许引用字符串的零次或多次重复。为了接受单引号或双引号,每个引号允许零个或多个。带引号的字符串应定义为开引号,后跟零个或多个反斜杠任意字符或除右引号以外的任何字符:"\(\\.\|[^\"]\)*" 或类似的单引号字符串'\(\\.\|[^\']\)*'

    将所有这些拼凑在一起,您的 sed 脚本可能看起来像这样:

    s/^\([^\"'#]*\|\\.\|"\(\\.\|[^\"]\)*"\|'\(\\.\|[^\']\)*'\)*\)#.*/\1/
    

    但是因为它需要被引用,并且单引号和双引号都包含在字符串中,所以我们需要额外的复杂性。回想一下,shell 允许您将字符串粘合在一起,例如 "foo"'bar' 被替换为双引号中的 foobar -- foo 和单引号中的 bar。因此,您可以通过将单引号放在与单引号字符串相邻的双引号中来包含单引号——'"foo"'"'" 在单引号中是 "foo",在双引号中 ' 旁边是 "foo"';而"'可以表示为与"'"相邻的'"'。因此,包含两个双引号foo"'bar 的单引号字符串可以用与"'bar" 相邻的'foo"' 引用,或者,对于这种情况,'foo"'"'" 相邻与另一个单引号字符串@ 相邻可能更现实987654343@,产生'foo'"'"'bar'

    sed 's/^\(\(\\.\|[^\#"'"'"']*\|"\(\\.\|[^\"]\)*"\|'"'"'\(\\.\|[^\'"'"']\)*'"'"'\)*\)#.*/\1/p' file
    

    这是在 Linux 上测试过的;在其他平台上,sed 方言可能略有不同。例如,您可能需要在分组和更改运算符之前省略反斜杠。

    唉,如果你可能有多行引用的字符串,这将不起作用; sed,按照设计,一次只检查一个输入行。您可以构建一个复杂的脚本,将多行收集到内存中,但到那时,切换到例如Perl 开始变得很有意义。

    【讨论】:

      【解决方案6】:

      正如您所指出的,如果脚本的任何部分看起来像 cmets 但实际上并非如此,则 sed 将无法正常工作。例如,您可以在字符串中找到 #,或者相当常见的 $#${#param}

      我编写了一个名为shfmt 的shell 格式化程序,它具有缩小代码的功能。这包括移除 cmets 等:

      $ cat foo.sh
      echo $# # inline comment
      # lone comment
      echo '# this is not a comment'
      [mvdan@carbon:12] [0] [/home/mvdan]
      $ shfmt -mn foo.sh
      echo $#
      echo '# this is not a comment'
      

      解析器和打印机是 Go 包,因此如果您想要自定义解决方案,编写一个 20 行的 Go 程序以您想要的确切方式删除 cmets 应该相当容易。

      【讨论】:

        【解决方案7】:
        sed 's:^#\(.*\)$:\1:g' filename
        

        假设这些行以单个 # 注释开头,上述命令将从文件中删除所有 cmets。

        【讨论】:

        • // , 有 cmets 但不以 '#' 开头的行呢?
        猜你喜欢
        • 2014-08-10
        • 2018-11-07
        • 2016-12-03
        • 2020-08-01
        • 2015-10-19
        • 2023-03-18
        • 2014-11-16
        • 2014-06-13
        • 2012-02-03
        相关资源
        最近更新 更多