skip to Main Content

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$REPOVER3#' 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:]]+/)$#1123#' 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$REPOVER3#" 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:]]+/)$#1123#" 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>.

4

Answers


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

    $ ( set -x ; sed -r "s#(.*/)([[:digit:]]+)([[:space:]]+/)$#1123#" )
    + sed -r 's#(.*/)([[:digit:]]+)([[:space:]]+/)01123#'
    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#'
    
    Login or Signup to reply.
  2. Your attempt 2 is almost correct. Just escape the $ signs that you want to preserve from shell expansion:

    sed -r "s#(.*/)([[:digit:]]+)([[:space:]]+/)$#1$REPOVER3#" 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 '%sn' "$REPOVER" ; cat testing.list; } | 
      sed -rn '1,/^deb/{/^deb/!{H;b;}};/^deb/{G;s#[[:digit:]]+([[:space:]]+/)nn(.*)#21#;};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 '%sn' "$REPOVER" ; cat testing.list; } | 
    sed -rn '1,/^deb/{/^deb/ba;H;b;};:a;/^deb/{G;s#[[:digit:]]+([[:space:]]+/)nn(.*)#21#;};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
    
    Login or Signup to reply.
  3. 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  /
    
    Login or Signup to reply.
  4. Changing the delimiter and placing it in double quotes worked for me.

    sed -r "s|(.*/)([[:digit:]]+)([[:space:]]+/)$|1$REPOVER3|"
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search