59

I have been told it was possible, but without a single working example, that sed can read from a string variable without need for an input file. I have yet to get it to work. For general safety, I’m writing the $PATH variable to another variable, as I mess with this, because I don’t need other problems to arise until I know exactly how to do this.

Consider the following:

~$x=$PATH
~$sed -i 's/:/ /g' $x

this fails with: No such file or directory.

Here are some others I have tried:

~$ sed -i 's/:/ /g' | (read x) 
sed: no input files
~$ sed -i 's/:/ /g' | (read $x) 
bash: read: `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games': not a valid identifier
sed: no input files
~$ sed -i 's/:/ /g' | $(read x) 
sed: no input files
~$ sed -i 's/:/ /g' | $(read $x) 
bash: read: `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games': not a valid identifier
sed: no input files
~$ sed -i 's/:/ /g' < $(read $x) 
bash: read: `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games': not a valid identifier
bash: $(read $x): ambiguous redirect
~$ sed -i 's/:/ /g' < read $x 
bash: read: No such file or directory
~$ sed -i 's/:/ /g' < (read x) 
bash: syntax error near unexpected token `('
~$ sed -i 's/:/ /g' < $((read x)) 
bash: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games: syntax error: operand expected (error token is "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games")
~$ sed -i 's/:/ /g' < $((read $x)) 
bash: read /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games: division by 0 (error token is "usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games")
~$ sed -i 's/:/ /g' << $((read $x)) 
> ;^C
~$ sed -i 's/:/ /g' << $(read $x)
> ;^C
~$ sed -i 's/:/ /g' $x
sed: can't read /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games: No such file or directory
~$ sed -i 's/:/ /g' < $x
bash: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games: No such file or directory
~$ sed -i 's/:/ /g' < echo $x
bash: echo: No such file or directory
~$ sed -i 's/:/ /g' |  echo $x
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
sed: no input files

Can this even work? I would prefer not to need to write files I don't need just so I can use sed. For this particular example, if ~$x=$PATH ~$sed -i 's/:/ /g' $x actually worked the way I would have hoped, I would get:

/usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin /usr/games /usr/local/games

which I could then assign to a variable, and use in future commands, like ls $x

αғsнιη
  • 35,660
j0h
  • 14,825

2 Answers2

63

This has nothing to do with sed as such, it applies to any program that reads standard input. Anyway, what you were looking for is

sed 's/:/ /g' <<<"$PATH"

There's no reason to save in another variable, nothing you do this way will affect the value stored in the variable. You need the assignment operator (=) for that. The syntax used above is called a "here string" and is specific to bash, ksh and zsh. It serves to pass a variable's value as input to a program that reads from standard input.

I have written up an answer on U&L that lists all the various shell operators like this one. You might want to have a look.

Note that you could also have done

echo "$PATH" | sed 's/:/ /g' 

Finally, you really don't need sed at all for this. You can do the whole thing in bash directly:

echo "${PATH//:/ }"

The above is a bash replacement construct. Given a variable $var, a pattern pat and a replacement (rep), to replace all occurrences of pat with rep, you would do

echo "${var//pat/rep}"

The same thing with a single slash replaces only the first occurrence:

echo "${var/pat/rep}"
terdon
  • 100,812
  • 2
    Thanks for 'bash replacement construct'. I was able to use: "${1/from/to}" in a Bash function to alter the first argument into subsequent parameters. – AAAfarmclub Nov 14 '17 at 23:31
  • 2
    For future Googlers, this is sometimes called "pattern substitution" and lets you search and replace a pattern (string) that exists within a bash variable. This page has a lot of useful examples: https://www.shellscript.sh/tips/pattern-substitution/ – Jesse Nickles May 22 '21 at 12:13
  • @JesseNickles for what it's worth, that page doesn't seem very good. It is using CAPS for shell variables which is bad practice (by convention, environment variables are in caps so using caps for your own variables can lead to variable name collision and hard to debug issues) and uses all variables unquoted with is really bad practice. – terdon May 22 '21 at 12:22
  • @terdon I have always used CAPS for bash variables myself, idk maybe because I define them in a config/functions script. Anyway you're right about the lack of quotation marks... but linked examples can be replaced with: "${VARIABLE}" – Jesse Nickles May 23 '21 at 08:10
29

May be:

~$ x="$PATH"
~$ x="$(echo $x | sed 's/:/ /g')"
~$ echo "$x"
/usr/local/bin /usr/bin /bin /usr/local/games /usr/games
bolichep
  • 418
  • 4
  • 5
  • @jOh --- notice that sed here is not working on the "string variable" (?), but on the standard input --- which is how it should be. – Rmano Mar 10 '15 at 21:47
  • i think Ill make a feature request to the sed devs – j0h Mar 10 '15 at 22:29
  • 4
    @j0h this has nothing to do with sed, it is a question of how the shell redirects input. sed deals with input streams and it reads them through its standard input. Whether they be files or not is irrelevant. It can accept anything as long as it comes through standard input. – terdon Mar 10 '15 at 22:48
  • This solution stores a variable which the OP seems to desire. It can be further shortened using "here string" format as described by @terdon, eg: x="$(sed 's/:/ /g' <<<"$x")" – David Cook Jan 28 '22 at 05:20