在 sed 中使用特殊字符转义变量 - 注释和取消注释任意一行源代码

Escaping a variable with special characters within sed - comment and uncomment an arbitrary line of source code

我需要通过脚本在crontab文件中注释掉一行,所以它包含目录、空格和符号。这个特定的行存储在一个变量中,我开始对如何转义变量感到困惑。由于线路会定期更改,因此我不想在其中进行任何转义。我不想简单地在它前面添加#,因为我还需要切换它并用没有# 的原始行再次替换该行。

所以我们的目标是将 $line 替换为 #$line(注释),并有可能以相反的方式进行替换(取消注释)。

所以我有一个变量:

line="* * * hello/this/line & /still/this/line"

这是文件中出现的一行,file.txt。需要发表评论。

第一次尝试:

sed -i "s/^${line}/#${line}/" file.txt

第二次尝试:

sed -i 's|'${line}'|'"#${line}"'|g' file.txt

Perl 有办法为您完成quoting/escaping:

line=$line perl -i~ -pe '$regex = quotemeta $ENV{line}; s/^$regex/#$ENV{line}/' -- input.txt

显示了使用 perl.

的有效解决方案

sed解法

如果你想使用sed,你必须使用单独的sed命令来转义$line变量值,因为sed 没有 built-in 转义字符串以在正则表达式上下文中用作文字的方法:

lineEscaped=$(sed 's/[^^]/[&]/g; s/\^/\^/g' <<<"$line") # escape $line for use in regex
sed -i "s/^$lineEscaped$/#&/" file.txt # Note the $ to escape the end-of-line anchor $

使用 BSD/macOS sed,使用 -i '' 而不是仅 -i 进行 in-place 无备份更新。

反之(un-评论):

sed -i "s/^#\($lineEscaped\)$//" file.txt

请参阅我的 以了解用于转义的 sed 命令的解释,该命令适用于任何输入字符串。

还要注意变量 $lineEscapedsregex 部分中如何仅被引用 一次命令,而 substitution-string 部分仅引用正则表达式匹配的内容(这避免了使用不同规则再次转义变量的需要):
替换字符串中的&代表整个匹配,</code>第一个捕获组(带括号的子表达式,<code>\(...\))。

为简单起见,第二个sed命令使用双引号,以便在sed脚本中嵌入shell变量$lineEscaped的值,但通常最好使用 引号脚本,以避免混淆 shell 预先解释的内容与 sed 最终看到的内容。

例如,$对于shell和sed都是特殊的,在上面的脚本中end-of-line锚点$sed 因此必须将正则表达式转义为 $ 以防止 shell 解释它。
避免混淆的一种方法是有选择地将 double-quoted shell-variable 引用拼接到其他 single-quoted 脚本中:

sed -i 's/^'"$lineEscaped"'$/#&/' file.txt

awk解决方案

awk 提供文字字符串匹配,这避免了转义的需要:

awk -v line="$line" '[=13=] == line { [=13=] = "#" [=13=] } 1' file.txt > $$.tmp && mv $$.tmp file.txt

如果你有 GNU Awk v4.1+,你可以使用 -i inplace 进行 in-place 更新。

反之(un-评论):

awk -v line="#$line" '[=14=] == line { [=14=] = substr([=14=], 2) } 1' file.txt > $$.tmp && 
  mv $$.tmp file.txt