312

If I run these commands from a script:

#my.sh
PWD=bla
sed 's/xxx/'$PWD'/'
...
$ ./my.sh
xxx
bla

it is fine.

But, if I run:

#my.sh
sed 's/xxx/'$PWD'/'
...
$ ./my.sh
$ sed: -e expression #1, char 8: Unknown option to `s' 

I read in tutorials that to substitute environment variables from shell you need to stop, and 'out quote' the $varname part so that it is not substituted directly, which is what I did, and which works only if the variable is defined immediately before.

How can I get sed to recognize a $var as an environment variable as it is defined in the shell?

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
RomanM
  • 6,363
  • 9
  • 33
  • 41
  • 7
    $PWD contains a / which is ending the substitute command. – derobert Feb 25 '09 at 06:28
  • 1
    @derobert: tnx. One of the solutions addresses this ... – RomanM Feb 25 '09 at 06:29
  • 6
    Use `set -x` in the shell to get the shell to echo each command just before it executes them. This can clear up a lot of confusion. (Also, I often use `set -u` to make de-referencing unset variables a hard error. (See `set -e` too.)) – bobbogo Aug 15 '12 at 08:02
  • 1
    I was hoping to find a way for sed to handle the environment variables as not to leak the values into the process table, seems like sed is the wrong tool for installing secrets according to all the answers in this thread – ThorSummoner Jan 09 '19 at 22:45

12 Answers12

442

Your two examples look identical, which makes problems hard to diagnose. Potential problems:

  1. You may need double quotes, as in sed 's/xxx/'"$PWD"'/'

  2. $PWD may contain a slash, in which case you need to find a character not contained in $PWD to use as a delimiter.

To nail both issues at once, perhaps

sed 's@xxx@'"$PWD"'@'
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Norman Ramsey
  • 198,648
  • 61
  • 360
  • 533
  • 1
    but, then what character can i use i know for sure will not appear in a path name ? – RomanM Feb 25 '09 at 06:42
  • 9
    You can have several candidates like @#%! and check with a case expression to find if $PWD has them. E.g., case "$PWD" of *@*) ;; *) delim="@" ;; esac; repeat until $delim is not empty. – Norman Ramsey Feb 25 '09 at 06:47
  • 1
    There's another alternative instead of using double quotes. See my [answer](http://stackoverflow.com/a/23134318/450826) below. – Thales Ceolin Apr 17 '14 at 13:22
  • 1
    You can use another delimiter for sed. You dont need to use / you can use , as well if your environment variable is an url. – Guchelkaben Jun 05 '20 at 13:09
  • 1
    What if the string contains a \ followed by an n - how to stop sed from converting that into a single newline character? – Max Waterman Jul 24 '20 at 09:58
200

In addition to Norman Ramsey's answer, I'd like to add that you can double-quote the entire string (which may make the statement more readable and less error prone).

So if you want to search for 'foo' and replace it with the content of $BAR, you can enclose the sed command in double-quotes.

sed 's/foo/$BAR/g'
sed "s/foo/$BAR/g"

In the first, $BAR will not expand correctly while in the second $BAR will expand correctly.

Jeach
  • 8,656
  • 7
  • 45
  • 58
  • 6
    This is cleaner than messing with double quotes, single quotes etc. – Vladislavs Dovgalecs Sep 11 '14 at 22:54
  • this is what I had to use to get the environment variable to expand correctly in this command: sed -i "s/127.0.0.1/127.0.0.1 localhost $HOSTNAME/" hosts – izikandrw Jul 05 '16 at 14:03
  • 5
    This is neat but it is not working when you want to do some more complex substitutions, such as `"2,$s/^/$foo/"` as `$s` gets interpreted as a variable too and it should not. – mjp Nov 10 '17 at 18:15
  • This alone didn't work as I still needed a different delimiter for paths like the other answers suggest. – MoRe Dec 02 '20 at 07:57
  • @mjp, what you say is correct but I think one may write it as `"2,$ s/^/$foo/"` – NameOfTheRose Jan 28 '21 at 20:41
94

Another easy alternative:

Since $PWD will usually contain a slash /, use | instead of / for the sed statement:

sed -e "s|xxx|$PWD|"
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Thales Ceolin
  • 2,594
  • 1
  • 21
  • 15
  • 7
    You said "an alternative to using double quotes" and yet your example uses double quotes? – Jeach Dec 10 '14 at 05:59
  • 1
    I guess the point is it doesn't use double-quotes directly around `$PWD`...? – Christian Oct 11 '19 at 08:30
  • Nice clean pattern but the -e (--expression) is not needed here, since this example is not stringing several expressions together. – bananaforscale Aug 11 '21 at 18:10
  • This has been better using | than / as some of the value in my variable were having special characters like preview:1.2.7-2208.190204 – Lolorol Aug 22 '22 at 14:38
68

You can use other characters besides "/" in substitution:

sed "s#$1#$2#g" -i FILE
Paulo Fidalgo
  • 21,709
  • 7
  • 99
  • 115
  • 2
    In my specific case, $2 was a file path, so `sed` was barfing due to interpreting the `/` in the contents of $2. This was exactly what I needed to get past it. Thanks fora great tip! – Boyd Hemphill Sep 18 '15 at 17:31
20

一. bad way: change delimiter

sed 's/xxx/'"$PWD"'/'
sed 's:xxx:'"$PWD"':'
sed 's@xxx@'"$PWD"'@'

maybe those not the final answer,

you can not known what character will occur in $PWD, / : OR @.
if delimiter char in $PWD, they will break the expression

the good way is replace(escape) the special character in $PWD.

二. good way: escape delimiter

for example: try to replace URL as $url (has : / in content)

x.com:80/aa/bb/aa.js

in string $tmp

<a href="URL">URL</a>

A. use / as delimiter

escape / as \/ in var (before use in sed expression)

## step 1: try escape
echo ${url//\//\\/}
x.com:80\/aa\/bb\/aa.js   #escape fine

echo ${url//\//\/}
x.com:80/aa/bb/aa.js      #escape not success

echo "${url//\//\/}"
x.com:80\/aa\/bb\/aa.js   #escape fine, notice `"`


## step 2: do sed
echo $tmp | sed "s/URL/${url//\//\\/}/"
<a href="x.com:80/aa/bb/aa.js">URL</a>

echo $tmp | sed "s/URL/${url//\//\/}/"
<a href="x.com:80/aa/bb/aa.js">URL</a>

OR

B. use : as delimiter (more readable than /)

escape : as \: in var (before use in sed expression)

## step 1: try escape
echo ${url//:/\:}
x.com:80/aa/bb/aa.js     #escape not success

echo "${url//:/\:}"
x.com\:80/aa/bb/aa.js    #escape fine, notice `"`


## step 2: do sed
echo $tmp | sed "s:URL:${url//:/\:}:g"
<a href="x.com:80/aa/bb/aa.js">x.com:80/aa/bb/aa.js</a>
yurenchen
  • 1,897
  • 19
  • 17
17

With your question edit, I see your problem. Let's say the current directory is /home/yourname ... in this case, your command below:

sed 's/xxx/'$PWD'/'

will be expanded to

sed `s/xxx//home/yourname//

which is not valid. You need to put a \ character in front of each / in your $PWD if you want to do this.

Eddie
  • 53,828
  • 22
  • 125
  • 145
6

Actually, the simplest thing (in GNU sed, at least) is to use a different separator for the sed substitution (s) command. So, instead of s/pattern/'$mypath'/ being expanded to s/pattern//my/path/, which will of course confuse the s command, use s!pattern!'$mypath'!, which will be expanded to s!pattern!/my/path!. I’ve used the bang (!) character (or use anything you like) which avoids the usual, but-by-no-means-your-only-choice forward slash as the separator.

ib.
  • 27,830
  • 11
  • 80
  • 100
adeger
  • 61
  • 1
  • 1
5
VAR=8675309
echo "abcde:jhdfj$jhbsfiy/.hghi$jh:12345:dgve::" |\
sed 's/:[0-9]*:/:'$VAR':/1' 

where VAR contains what you want to replace the field with

ib.
  • 27,830
  • 11
  • 80
  • 100
PsychoData
  • 1,198
  • 16
  • 32
4

Dealing with VARIABLES within sed

[root@gislab00207 ldom]# echo domainname: None > /tmp/1.txt

[root@gislab00207 ldom]# cat /tmp/1.txt

domainname: None

[root@gislab00207 ldom]# echo ${DOMAIN_NAME}

dcsw-79-98vm.us.oracle.com

[root@gislab00207 ldom]# cat /tmp/1.txt | sed -e 's/domainname: None/domainname: ${DOMAIN_NAME}/g'

 --- Below is the result -- very funny.

domainname: ${DOMAIN_NAME}

 --- You need to single quote your variable like this ... 

[root@gislab00207 ldom]# cat /tmp/1.txt | sed -e 's/domainname: None/domainname: '${DOMAIN_NAME}'/g'


--- The right result is below 

domainname: dcsw-79-98vm.us.oracle.com
guido
  • 18,864
  • 6
  • 70
  • 95
  • I was struggling to get this working in Azure DevOps YAML pipelines. This comment helped me to successfully use this trick to tokenize some configuration files. – andov Feb 18 '20 at 12:04
3

If your replacement string may contain other sed control characters, then a two-step substitution (first escaping the replacement string) may be what you want:

PWD='/a\1&b$_' # these are problematic for sed
PWD_ESC=$(printf '%s\n' "$PWD" | sed -e 's/[\/&]/\\&/g')
echo 'xxx' | sed "s/xxx/$PWD_ESC/" # now this works as expected
Joseph Sturtevant
  • 13,194
  • 12
  • 76
  • 90
2

I had similar problem, I had a list and I have to build a SQL script based on template (that contained @INPUT@ as element to replace):

for i in LIST 
do
    awk "sub(/\@INPUT\@/,\"${i}\");" template.sql >> output
done
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
aniskop
  • 21
  • 1
1

for me to replace some text against the value of an environment variable in a file with sed works only with quota as the following:

sed -i 's/original_value/'"$MY_ENVIRNONMENT_VARIABLE"'/g' myfile.txt

BUT when the value of MY_ENVIRONMENT_VARIABLE contains a URL (ie https://andreas.gr) then the above was not working. THEN use different delimiter:

sed -i "s|original_value|$MY_ENVIRNONMENT_VARIABLE|g" myfile.txt
Andreas Panagiotidis
  • 2,763
  • 35
  • 32