【问题标题】:Use a variable as replacement in bash sed command在 bash sed 命令中使用变量作为替换
【发布时间】:2020-03-17 03:03:50
【问题描述】:

我在 Ubuntu 上使用sed 命令来替换内容。

这个初始命令来自here。

sed -i '$ s/$/ /replacement/' "$DIR./result/doc.md"

但是,如您所见,我在替换部分中有一个斜线。斜杠导致命令抛出:

sed: -e expression #1, char 9: `s' 的未知选项

此外,我的替换存储在一个变量中。

因此,由于斜线,以下 将不起作用:

sed -i "$ s/$/ $1/" "$DIR./result/doc.md"

如here and in duplicate 所述,我应该使用另一个分隔符。如果我尝试使用@:

   sed -i "$ s@$@ $1@" "$DIR./result/doc.md"

它给出了错误:

sed: -e expression #1, char 42: unterminated `s' command

我的问题是:

如何在此命令中使用变量以及 / 以外的其他分隔符?

【问题讨论】:

  • $@ 在 bash 中有一个含义。使用其他东西作为分隔符,或使用例如Perl 带有实变量而不是 shell 的宏扩展。
  • 您刚刚选择了错误的分隔符。 @ 是一个 shell 参数,所以双引号内的 $@ 被扩展。使用单引号和双引号字符串的组合构建sed 脚本,或选择不同的分隔符。但请记住,如果不格外小心,$1 的任何值都可能破坏脚本。
  • _ 也是一个shell参数:)试试|。
  • 您也可以在脚本中转义$,以避免意外的参数扩展,无论您选择何种分隔符:例如"\$ s@\$@ $1@"。

标签: bash sed


【解决方案1】:

不要在这里使用sed; perl 和 awk 允许更强大的方法。

sed 不允许变量从代码带外传递,因此它们总是需要转义。使用不受此限制的语言,无论您的数据包含什么字符,您的代码都会始终有效。

简答:使用perl

以下摘自BashFAQ #21:

inplace_replace() {
  local search=$1; shift; local replace=$1; shift
  in="$search" out="$replace" perl -pi -e 's/\Q$ENV{"in"}/$ENV{"out"}/g' "$@"
}
inplace_replace '@' "replacement" "$DIR/result/doc.md" 

更长的答案:使用awk

...或者,使用 awk 进行流式替换,并使用 shell 函数进行文件替换:

# usage as in: echo "in should instead be out" | gsub_literal "in" "out"
gsub_literal() {
  local search=$1 replace=$2
  awk -v s="${search//\\/\\\\}" -v r="${rep//\\/\\\\}" 'BEGIN {l=length(s)} {o="";while (i=index($0, s)) {o=o substr($0,1,i-1) r; $0=substr($0,i+l)} print o $0}'
}

# usage as in: inplace_replace "in" "out" /path/to/file1 /path/to/file2 ...
inplace_replace() {
  local search=$1 replace=$2 retval=0; shift; shift
  for file; do
    tempfile=$(mktemp "$file.XXXXXX") || { retval |= $?; continue; }
    if gsub_literal "$search" "$replace" <"$file" >"$tempfile"; then
      mv -- "$tempfile" "$file" || (( retval |= $? ))
    else
      rm -f -- "$tempfile" || (( retval |= $? ))
    fi
  done
}

【讨论】:

    【解决方案2】:

    TL;DR:

    试试:

    sed -i '$ s@$@ '"$1"'@' "$DIR./result/doc.md"
    

    长版:

    让我们从您的原始代码开始:

    sed -i '$ s/$/ /replacement/' "$DIR./result/doc.md"
    

    让我们将其与您引用的代码进行比较:

    sed -i '$ s/$/abc/' file.txt
    

    我们可以看到它们并不完全匹配。我看到你正确地做了这个替换:

    file.txt --> "$DIR./result/doc.md"
    

    看起来不错(尽管我对 $DIR 之后的 . 确实有疑问)。但是,其他替换看起来不太好:

    abc -->  /replacement
    

    您实际上引入了另一个分隔符 /。但是,如果我们用“@”替换分隔符,我们会得到:

    sed -i '$ s@$@ /replacement@' "$DIR./result/doc.md"
    

    我认为以上在 sed/bash 中是完全有效的。 $@ 不会被 shell 替换,因为它是单引号的。 $DIR 变量 将被 shell 插值,因为它是双引号的。

    查看您的一项尝试:

    sed -i "$ s@$@ $1@" "$DIR./result/doc.md"
    

    由于双引号中 $@ 的 shell 插值,您将遇到问题。让我们用单引号代替(但不加引号)来纠正这个问题:

    sed -i '$ s@$@ '"$1"'@' "$DIR./result/doc.md"
    

    注意“$1”。我不得不用 '' 将 $1 括起来,以基本上取消周围的单引号。但是后来我用双引号将 $1 括起来,这样我们就可以保护字符串免受空格的影响。

    【讨论】:

    • 我测试了它并且它有效。但是,我更喜欢 cmets 中提供给我的解决方案。该解决方案只是“用管道替换分隔符”。 sed -i "$ s|$| $1|" "$DIR./result/doc.md"
    • @FlorianCastelain,仅在您的输入或输出数据中有管道之前有效;这不是一个完整而强大的解决方案。尝试$1 包含'foo|bar|baz' 的位置。
    • @FlorianCastelain:我也不喜欢 @ 作为分隔符。当正斜杠/变得笨拙时,我通常使用冒号:。我的回答是为了说明正确的引用如何避免 $@ 的 shell 插值。同时,引用足够灵活,可以指示您需要插值的位置。尽管如此,我无法反驳 Charles 的论点,即您的 $1 变量中的分隔符会破坏您的 sed 脚本。您可以通过在 $1 中搜索会破坏您的脚本的字符或使用 Charles 共享的方法来防御它。
    【解决方案3】:

    使用shell parameter expansion 为变量中的斜杠添加转义:

    $ cat file
    foo
    bar
    baz
    
    $ set -- ' /repl'
    
    $ sed "s/$/$1/" file
    sed: 1: "s/$/ /repl/": bad flag in substitute command: 'r'
    
    $ sed "s/$/${1//\//\\\/}/" file
    foo /repl
    bar /repl
    baz /repl
    

    这是一个倾斜的牙签的怪物,但它有助于改变这一点:

    sed "s/$/ /repl/"
    

    进入

    sed "s/$/ \/repl/"
    

    同样的技术可以用于任何你选择的 sed s/// 分隔符。

    【讨论】:

      猜你喜欢
      • 2017-04-16
      • 2023-01-02
      • 1970-01-01
      • 2011-12-02
      • 2021-11-20
      • 1970-01-01
      • 1970-01-01
      • 2016-12-23
      • 2017-12-05
      相关资源
      最近更新 更多