从文件中删除注释,同时忽略引用的注释标志

从文件中删除注释,同时忽略引用的注释标志

我想#从文件中删除以 开头的注释。我尝试过中描述的更简单的方法如何从文件中删除所有注释?但我还有一些额外的规则:

  • 如果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 描述的功能。grepsed

答案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 语法荧光笔在大多数情况下都会出错)。

覆盖这些将需要数百行awksed代码。

cshfishperlpythonruby其他具有"..."'...'引号并且#作为注释引导者的语言的规则将完全不同。

如果

  • 这与 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

'

相关内容