使用 Shell 变量替换 sed 字符串

时间:2021-07-21 13:14:21

标签: linux bash shell sed

输入:

$ cat testing.list
deb [trusted=yes] http://10.47.4.220/repos/test-repo/11  /

$ echo $REPOVER
12

预期输出:

$ cat testing.list
deb [trusted=yes] http://10.47.4.220/repos/test-repo/12  /

请注意最后数字的变化。

使用单引号尝试 1(不起作用):

$ sed -r 's#(.*/)([[:digit:]]+)([[:space:]]+/)$#\1$REPOVER\3#' testing.list
deb [trusted=yes] http://10.47.4.220/repos/test-repo/$REPOVER  /

在没有变量的情况下使用单引号尝试 1.1(工作):

 $ sed -r 's#(.*/)([[:digit:]]+)([[:space:]]+/)$#\112\3#' testing.list
deb [trusted=yes] http://10.47.4.220/repos/test-repo/12  /

使用双引号尝试 2(不起作用):

$ sed -r "s#(.*/)([[:digit:]]+)([[:space:]]+/)$#\1$REPOVER\3#" testing.list
sed: -e expression #1, char 44: unterminated `s' command

在没有变量的情况下使用双引号尝试 2.1(不工作):

$ sed -r "s#(.*/)([[:digit:]]+)([[:space:]]+/)$#\112\3#" testing.list
sed: -e expression #1, char 44: unterminated `s' command

所以,似乎sed 不喜欢double quotes。但是,除非我使用 double quotes,否则 Shell 不会扩展变量。

FWIW:

$ bash --version | head -1
GNU bash, version 5.0.3(1)-release (x86_64-pc-linux-gnu)

$ sed --version
sed (GNU sed) 4.7
Packaged by Debian
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.

4 个答案:

答案 0 :(得分:2)

$# 是 shell 中带有多个参数的变量。

$ ( set -x ; sed -r "s#(.*/)([[:digit:]]+)([[:space:]]+/)$#\112\3#" )
+ sed -r 's#(.*/)([[:digit:]]+)([[:space:]]+/)0\112\3#'
sed: -e expression #1, char 44: unterminated `s' command

所以 $#0 替换(在我的 shell 中,它是交互式的,没有参数)。

研究单引号和双引号的区别。研究何时在 shell 中引用。研究引用在 shell 中是如何工作的。你似乎想要:

sed -r 's#(.*/)([[:digit:]]+)([[:space:]]+/)$#\1'"$REPOVER"'\3#'

答案 1 :(得分:1)

您的尝试 2 几乎是正确的。只需转义您想避免 shell 扩展的 $ 标志:

sed -r "s#(.*/)([[:digit:]]+)([[:space:]]+/)\$#\1$REPOVER\3#" testing.list

另一种选择是混合使用单引号和双引号:

sed -r 's#(.*/)([[:digit:]]+)([[:space:]]+/)$#\1'"$REPOVER"'\3#' testing.list

但是,只有当您的变量的值不包含 sed 可以解释的特殊字符时,所有这些才有效。如果你有这样的字符,你需要先转义它们。例如,如果 REPOVER 的值可以包含 #&\ 字符,您可以:

ESCAPED_REPOVER="$(echo "$REPOVER" | sed -r 's/(#|\\|&)/\\\1/g')"
sed -r 's#(.*/)([[:digit:]]+)([[:space:]]+/)$#\1'"$ESCAPED_REPOVER"'\3#' testing.list

最后一个复杂的,以防 REPOVER 真的可以是任何东西,包括结束符、bash 和 sed 特殊字符等。但是我们假设它本身不包含以 deb 开头的行(如果是这样,我们将需要一个自定义分隔符,但原理几乎相同)。

我们首先打印变量,然后 cat 文件,并将所有这些传递给 sed。 sed 脚本首先收集保持空间中的前导码,即排除第一个 ^deb 行之前的所有内容。然后它使用保留空间的内容在它遇到的所有 ^deb 行中进行替换。前导码后的非 ^deb 行未经修改地打印:

{ printf '%s\n' "$REPOVER" ; cat testing.list; } | \
  sed -rn '1,/^deb/{/^deb/!{H;b;}};/^deb/{G;s#[[:digit:]]+([[:space:]]+/)\n\n(.*)#\2\1#;};p'

演示:

$ cat testing.list 
deb [trusted=yes] http://10.47.4.220/repos/test-repo/11  /
deb [trusted=yes] http://10.47.4.220/repos/foo-repo/11  /
# comment

deb [trusted=yes] http://10.47.4.220/repos/bar-repo/11  /

# another comment
$ printf -v REPOVER '\\$&#'
$ { printf '%s\n' "$REPOVER" ; cat testing.list; } | \
sed -rn '1,/^deb/{/^deb/ba;H;b;};:a;/^deb/{G;s#[[:digit:]]+([[:space:]]+/)\n\n(.*)#\2\1#;};p'
deb [trusted=yes] http://10.47.4.220/repos/test-repo/\$&#  /
deb [trusted=yes] http://10.47.4.220/repos/foo-repo/\$&#  /
# comment

deb [trusted=yes] http://10.47.4.220/repos/bar-repo/\$&#  /

# another comment

答案 2 :(得分:1)

根据提供的示例数据,可以稍微简化提议的 sed

$ sed -r "s|/[0-9]+ |/${REPOVER} |" testing.list
$ sed -r "s|/[[:digit:]]+ |/${REPOVER} |" testing.list

查找模式 /<at_least_one_number><space> 并将其替换为 /${REPOVER}<space>

两者都产生:

deb [trusted=yes] http://10.47.4.220/repos/test-repo/12  /

答案 3 :(得分:1)

更改分隔符并将其放在双引号中对我有用。

sed -r "s|(.*/)([[:digit:]]+)([[:space:]]+/)$|\1$REPOVER\3|"
相关问题