我想#
从文件中删除以 开头的注释。我尝试过中描述的更简单的方法如何从文件中删除所有注释?但我还有一些额外的规则:
- 如果A
#
作为带引号的字符串的一部分出现,则不会开始注释。 - 字符串可以用单引号
'
或双引号引起来"
。 - 如果前面有反斜杠,双引号字符串可以包含引号
\"
,反斜杠被引用为\\
。 - 输入中的所有引号均匹配。但是,对于属于字符串内容一部分的引号(换句话说
"'"
,"\""
并且'"'
是有效字符串),这不是必需的。 - 引用的字符串不能包含换行符。
- 注释可以包含任意字符,包括任意数量的
#
、'
和。"
\
- 任何
#
内容都会开始评论(如斯蒂芬·查泽拉斯指出大多数 shell 的代码遵循更复杂的规则 - 想想 Bash 的代码$#
,它不开始注释)。
例如以下输入
# comment only
# comments are allowed to contain quotes "' and # number signs
# comments are allowed to contain pairs 'of' "quotes"
some text # with an explanation
some "quoted text # not a comment" # comment
'# not a comment' and '# not a comment either' # comment
"# not a comment containing 'quotes\"" # another comment
应转换为以下输出
some text
some "quoted text # not a comment"
'# not a comment' and '# not a comment either'
"# not a comment containing 'quotes\""
我想在现代 Debian/Ubuntu 系统上使用流行的 Unix 命令行工具(例如awk
、 )来完成此任务。尽管首选符合 POSIX 的解决方案,但我并不严格限于 POSIX 描述的功能。grep
sed
答案1
如果重点是从 POSIX sh 脚本中删除注释,请注意,只有在下面的代码中标记为 YES 的才是注释:
echo 1 # YES
echo 2 $# NO foo# NO
echo 3;#YES
# YES
cat << E
# NO
E
echo 4 " # NO \" # NO" \" # YES
echo "5
# NO
$(echo 6 # YES
)
`echo 7 \" # NO \"`
"
eval 'echo 8 # NO, then YES'
(您可以看到 stackexchange 语法荧光笔在大多数情况下都会出错)。
覆盖这些将需要数百行awk
或sed
代码。
而csh
、fish
、perl
、python
等ruby
其他具有"..."
和'...'
引号并且#
作为注释引导者的语言的规则将完全不同。
如果
- 这与 shell 语法无关,
- 你可以假设没有转义引号,
- 引用的字符串不包含换行符,
- 所有引号都匹配,
- 引号之外的任何
#
内容都会开始注释,而不仅仅是那些跟随空白或其他分隔符的注释, - 输入是当前语言环境中的有效文本
如果通过标准你的意思是 POSIX 2018 或更早版本,你可以这样做sed
:
sed "s/^\(\(\([^\"'#]\)*\(\"[^\"]*\"\)\{0,1\}\('[^']*'\)\{0,1\}\)*\)#.*/\1/"
POSIX 2018sed
不支持-E
交替运算符所需的 ERE,但在这里我们通过\(a\{0,1\}b\{0,1\}\)*
((a?b?)*
在 ERE 中)相当于(a|b)*
.使用(a*b*)*
如拉克什的回答也会起作用。
grep
不会是一个选项,因为标准grep
仅打印完整的匹配行。awk
不过使用 ERE。标准 awk 没有捕获组,但您应该能够执行以下操作:
awk "match(\$0, /^([^'\"#]|\"[^\"]*\"|'[^']*')*#/) {
\$0 = substr(\$0, 1, RLENGTH-1)
}
{print}"
"(\\.|[^\\"])*"
根据您编辑的要求,您可以使用或其 BRE 等效项来处理转义引号:
sed 's/^\(\(\([^"\\'\''#]\)*\(\\.\)\{0,1\}\("\([^"\\]*\(\\.\)\{0,1\}\)*"\)\{0,1\}\('"'[^']*'\)\{0,1\}\)*\)#.*/\1/"
或者:
awk 'match($0, /^([^'\''"\\#]|\\.|"(\\.|[^\\"])*"|'\''(\\.|[^\\'\''])*'\'')*#/) {
$0 = substr($0, 1, RLENGTH-1)
}
{print}'
两者都还处理转义引号外部引号(如foo\"bar # comment
)。
我在这里改用单引号来减少需要插入的反斜杠数量来获取文字\\
,但是数据中的文字单引号必须插入为'before'\''after'
,即'\''
第一个'
关闭'before'
带引号的字符串,\'
使用反斜杠来引用/转义文字'
(因为您不能在单引号字符串中插入单引号),然后'after'
是带引号的字符串。
答案2
根据指定的规则,我们区分 5 种单词:
双引号单词(它们也可以包含转义双引号)
"... \"... "
单引号单词
'...'
不会包含单引号。反斜杠引用的单词
\.
基本上是任何转义字符。非注释起始字符
[^'#"]
剩下的就是评论了。
#! /bin/bash
# whitespace and horizontal whitespace
_ws_=$(printf '\t \nx')
ws="[${_ws_%?}]" hws="[${_ws_%??}]"
_nac_="[^\"'#]" nac="\($_nac_\)" #not a comment char
_bqw_='[\].' bqw="\($_bqw_\)" # backslashed word
_sqw_="'[^']*'" sqw="\($_sqw_\)" # single quoted word
#double quoted word
_dqw_='
"
\(
[^\"]* \([\][\]\)* [\]"
\)*
[^"]*
"
'
dqw="\(${_dqw_//$ws/}\)"
sed \
-e '/#/!b' \
-e "s/^\(\($sqw*$dqw*$bqw*$nac*\)*\).*/\1/" \
-e "s/$hws*$//" \
< file
请注意,这完全是 POS IX
答案3
解决方案
以下解决方案适用于流行的sed
实现,例如 GNU sed
,它支持扩展正则表达式(ERE):
sed -E "s/^(([^#\"'\\]|'[^']*'|\"([^\"\\\\]|\\\\.)*\")*)#.*/\1/" input.txt
该解决方案的主要优点是比许多其他解决方案具有更好的可读性。
笔记:该-E
开关还不是 POSIX 2018 的一部分,但是它正在成为 POSIX 2020 的一部分。如果您需要 POSIX-2018 兼容的解决方案,请参阅斯特凡·查泽拉斯的回答。
怎么运行的
以下较长的版本将上述正则表达式分解为更容易理解的部分:
NON_QUOTED_TEXT="[^#\"'\\]"
SINGLE_QUOTED_STRING="'[^']*'"
DOUBLE_QUOTED_STRING='"([^"\\]|\\.)*"'
REMOVE_COMMENTS="^((${NON_QUOTED_TEXT}|${SINGLE_QUOTED_STRING}|${DOUBLE_QUOTED_STRING})*)#.*"
sed -E "s/${REMOVE_COMMENTS}/\1/" input.txt
我们用于sed
搜索与 中包含的正则表达式匹配的文本${REMOVE_COMMENTS}
,并将每个匹配项替换为第一个捕获组的内容\1
。该捕获组包含第一个左括号(
和最后一个右括号之间的正则表达式匹配)
。正则表达式的这一部分与第一个注释符号 ( #
) 之前的任何文本匹配,该注释符号不会作为带引号的字符串的一部分出现。详细来看,我们正在匹配*
以下选项的 0 到 N()序列(a|b|c)
:
- 非引号文本:除
#
、"
、'
和 之外的字符\
。 - 单引号文本:除了由一对单引号引起来的(
*
) 之外的任意数量 ( ) 的字符。^
'
- 双引号文本:用一对双引号括起来的字符串。该字符串允许包含任意数量的字符,除了
"
and\
或 ((a|b)
) 前面带有反斜杠 (\\.
) 的任意字符。
当您将这些部分组合成上面的完整解决方案时,我们必须记住,在使用单引号和双引号时,Bash 规则需要稍微不同的引号。看Bash 中单引号和双引号的区别了解详情。
答案4
命令
sed -e '/^#/d' filename| sed "s/# comment$//g"
Python
#!/usr/bin/python
import re
d=re.compile(r'^#')
r=re.compile(r'#\scomment$')
l=open('p','r')
for i in l:
if not re.search(d,i):
e=re.sub(r,"",i)
print e.strip()
输出
some text # with a comment
some "quoted text # not a comment"
'# not a comment' "# it's not a comment" '#still not a comment
'