您将如何从文件(用#定义)中删除所有关于字符串中"#"的sed注释?
除了字符串部分外,这帮助很大。
如果#
总是意味着注释,并且可以出现在一行的任何位置(例如在某些代码之后):
sed 's:#.*$::g' <file-name>
如果要就地更改它,请添加-i
开关:
sed -i 's:#.*$::g' <file-name>
这将从任何#
删除到行尾,忽略任何上下文。如果您在不是注释的任何地方(例如在字符串中)使用#
,它也会删除它。
如果注释只能从一行的开头开始,请执行以下操作:
sed 's:^#.*$::g' <file-name>
如果它们前面可能有空格,但没有其他内容,请执行以下操作:
sed 's:^s*#.*$::g' <file-name>
这两个会更安全一些,因为它们可能不会删除代码中#
的有效用法,例如字符串中的用法。
编辑:
没有一种很好的方法来检测字符串中是否有某些东西。如果这能满足你的语言的限制,我会使用最后两个。
检测您是否在字符串中的问题在于正则表达式无法执行所有操作。有几个问题:
- 字符串可能跨越行
- 正则表达式无法区分撇号和单引号 正则
表达式不能匹配嵌套引号(这些情况会混淆正则表达式):
# "hello there" # hello there" "# hello there"
如果双引号是定义字符串的唯一方式,双引号永远不会出现在注释中,并且字符串不能跨越多行,请尝试如下操作:
sed 's:#[^"]*$::g' <file-name>
这是很多先决条件,但如果它们都成立,你就在做生意了。否则,恐怕你是SOL,你最好用Python这样的东西来写它,在那里你可以做更高级的逻辑。
这可能对你有用(GNU sed):
sed '/#/!b;s/^/n/;ta;:a;s/n$//;t;s/n(("[^"]*")|('''[^''']*'''))/1n/;ta;s/n([^#])/1n/;ta;s/n.*//' file
/#/!b
如果线路不包含#
救助s/^/n/
插入唯一标记 (n
)ta;:a
跳转到循环标签(重置替换真/假标志)s/n$//;t
如果标记在行尾,请将其移除并救助s/n(("[^"]*")|('''[^''']*'''))/1n/;ta
如果标记后面的字符串是带引号的字符串,请将标记向前凸起并循环。s/n([^#])/1n/;ta
如果标记后面的字符不是#
,请将标记向前凸起并循环。s/n.*//
行的其余部分是注释,请删除标记和行的其余部分。
由于 asker 没有提供示例输入,我将假设几种情况,Bash 是输入文件,因为 bash 被用作问题的标签。
情况1:整行为注释
在大多数情况下,以下内容应该足够了:
sed '/^s*#/d' file
它匹配任何没有或至少有一个前导空格字符(空格、制表符或其他几个,见man isspace
)的行,后跟一个#
,然后通过d
命令删除该行。
任何行,例如:
# 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
[^"']
用于防止引号字符串混淆,但是,这也意味着不会删除带有引号'
或"
的注释。
最终的sed
sed "/^s*#/d;s/s*#[^"']*$//" file
要删除注释行(第一个非空格字符为#
的行),但要删除 shebang 行(第一个字符为#!
的行):
sed '/^[[:space:]]*#[^!]/d; /#$/d' file
要sed
的第一个参数是一个字符串,其中包含一个 sed 程序,该程序由两个形式的删除行命令组成/
正则表达式/d
. 命令之间用;
分隔。 第一个命令删除注释行,但不删除 shebang 行。 第二个命令删除任何剩余的空注释行。 它不处理尾随注释。
要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:nn${S0}nn"
S1=$(sed '/^[[:space:]]*#[^!]/d; /#$/d' <<< "${S0}")
printf "nAFTER removal:nn${S1}nn"
输出:
$ 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
假设"在字符串中"意味着"发生在一对引号之间,无论是单引号还是双引号",这个问题可以改写为"删除第一个未引号#之后的所有内容"。 反过来,您可以将带引号的字符串定义为两个引号之间的任何内容,反斜杠引号除外。 作为一个小的改进,将整行替换为第一个未引用的 # 之前的所有内容。
因此,对于琐碎的情况,我们得到了类似[^"'#]
的东西 - 一段既不是注释符号,也不是反斜杠,也不是开头引号的字符串。 然后我们可以接受一个反斜杠,后跟任何内容:\.
-- 这不是文字点,而是文字反斜杠,后跟一个与任何字符匹配的点元字符。
然后我们可以允许带引号的字符串零次或多次重复。 为了接受单引号或双引号,每个引号允许零个或多个。 带引号的字符串应定义为左引号,后跟零个或多个反斜杠任意字符,或除右引号以外的任何字符:"(\.|[^"])*"
或类似的单引号字符串'(\.|[^'])*'
。
将所有这些拼凑在一起,您的sed
脚本可能如下所示:
s/^([^"'#]*|\.|"(\.|[^"])*"|'(\.|[^'])*')*)#.*/1/
但是因为它需要被引号,并且单引号和双引号都包含在字符串中,所以我们还需要一个额外的复杂性。 回想一下,shell 允许您将字符串粘合在一起,例如"foo"'bar'
被替换为foobar
-foo
用双引号括起来,bar
用单引号括起来。 因此,您可以通过将它们放在与单引号字符串相邻的双引号中来包含单引号 -'"foo"'"'"
在双引号旁边的'
旁边"foo"
单引号,因此"foo"'
;"'
可以表示为"'"
相邻的'"'
。因此,一个包含双引号的单引号字符串foo"'bar
可以用'foo"'
与"'bar"
相邻引用,或者,也许更现实的是,在这种情况下'foo"'
相邻"'"
相邻于另一个单引号字符串'bar'
,产生'foo'"'"'bar'
。
sed 's/^((\.|[^#"'"'"']*|"(\.|[^"])*"|'"'"'(\.|[^'"'"'])*'"'"')*)#.*/1/p' file
这是在Linux上测试的;在其他平台上,sed
方言可能略有不同。例如,您可能需要省略分组和更改运算符之前的反斜杠。
唉,如果你可能有多行带引号的字符串,这将不起作用;sed
,根据设计,一次只检查一个输入行。 你可以构建一个复杂的脚本,将多行收集到内存中,但到那时,切换到Perl开始变得很有意义。
正如您所指出的,如果脚本的任何部分看起来像注释但实际上并非如此,sed 将无法正常工作。例如,您可以在字符串中找到#,或者相当常见的$#
和${#param}
。
我写了一个名为shfmt的shell格式化程序,它具有缩小代码的功能。这包括删除评论,其中包括:
$ 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 程序以你想要的确切方式删除注释应该相当容易。
sed 's:^#(.*)$:1:g' filename
假设这些行以单个#注释开头,上面的命令会从文件中删除所有注释。