简体   繁体   中英

Sed string substitution with Shell Variable

Input:

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

$ echo $REPOVER
12

Expected output:

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

Please note the change in the number at the end.

Attempt 1 with single quotes (Not working):

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

Attempt 1.1 with single quotes without the variable (Working):

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

Attempt 2 with double quotes (Not Working):

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

Attempt 2.1 with double quotes without the variable (Not Working):

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

So, it seems like sed does not like double quotes . However, Shell won't expand variables unless I use double quotes .

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>.

The $# is a variable in shell with number of arguments.

$ ( 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

So $# is replaced by 0 (in case of my shell, it's interactive, has no arguments).

Research difference between single and double quotes. Research when to quote in shell. Research how quotation works in shell. You seem to want:

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

Your attempt 2 is almost correct. Just escape the $ signs that you want to preserve from shell expansion:

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

Another option is to use a mixture of simple quotes and double-quotes:

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

But all this works only if the value of your variable does not contain special characters that sed would interpret. If you have such characters you will need to first escape them. If, for instance, the value of REPOVER can contain # , & or \\ characters you can:

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

A last, complicated one, in case REPOVER can really be anything, including endline characters, bash and sed special characters, etc. But let's however assume it does not itself contain lines beginning with deb (if it did we would need a custom delimiter but the principle would be almost the same).

We first print the variable, next cat the file, and pass all this to sed. The sed script first collects the preamble in the hold space, that is, everything until the first ^deb line excluded. It then uses the content of the hold space for the replacement in all ^deb lines it encounters. Non ^deb lines after the preamble are printed unmodified:

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

Demo:

$ 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

Based on the sample data provided the proposed sed can be simplified a bit:

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

Find and replace the pattern /<at_least_one_number><space> with /${REPOVER}<space>

Both of which produce:

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM