对于字符串中的“#”,您将如何使用 sed 从文件(用 # 定义)中删除所有注释?
除了字符串部分之外,这有很大帮助。
if#
总是意味着注释,并且可以出现在一行的任何地方(比如在一些代码之后):
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\(\("[^"]*"\)\|\('\''[^'\'']*'\''\)\)/\1\n/;ta;s/\n\([^#]\)/\1\n/;ta;s/\n.*//' file
/#/!b
如果该行不包含#
救助s/^/\n/
插入唯一标记 ( \n
)ta;:a
跳转到循环标签(重置替代真/假标志)s/\n$//;t
如果标记在行尾,删除并退出s/\n\(\("[^"]*"\)\|\('\''[^'\'']*'\''\)\)/\1\n/;ta
如果标记后面的字符串是带引号的字符串,则将标记向前碰撞并循环。s/\n\([^#]\)/\1\n/;ta
如果标记后面的字符不是 a #
,则将标记向前碰撞并循环。s/\n.*//
该行的其余部分是注释,删除标记和行的其余部分。由于提问者没有提供示例输入,因此我将假设几种情况,并且 Bash 是输入文件,因为 bash 被用作问题的标签。
案例1:整行是注释
在大多数情况下,以下内容应该足够了:
sed '/^\s*#/d' file
它匹配任何没有或至少有一个前导空白字符(空格、制表符或其他几个字符,请参阅man isspace
)的行,后跟 a ,然后通过命令#
删除该行。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
to 的第一个参数sed
是一个字符串,其中包含一个 sed 程序,该程序由两个/
regex/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:\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
假设“在一个字符串中”意味着“出现在一对引号之间,无论是单引号还是双引号”,这个问题可以改写为“删除第一个未引用的 # 之后的所有内容”。反过来,您可以将带引号的字符串定义为两个引号之间的任何内容,反斜杠引号除外。作为一个小的改进,将整行替换为第一个未引用的 # 之前的所有内容。
所以我们得到了类似[^\"'#]
普通情况的东西——一段既不是注释符号,也不是反斜杠,也不是开头引号的字符串。然后我们可以接受一个反斜杠后跟任何内容:\\.
-- 这不是文字点,而是文字反斜杠,后跟一个匹配任何字符的点元字符。
然后我们可以允许引用字符串的零次或多次重复。为了接受单引号或双引号,每个引号允许零个或多个。带引号的字符串应定义为开引号,后跟零个或多个反斜杠任意字符或除右引号之外的任何字符:"\(\\.\|[^\"]\)*"
或类似的单引号字符串'\(\\.\|[^\']\)*'
。
将所有这些拼凑在一起,您的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 将无法正常工作。例如,您可以在字符串中找到 # 或相当常见的$#
and ${#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
假设这些行以单个 # 注释开头,上述命令将从文件中删除所有注释。