44

For grep there's a fixed string option, -F (fgrep) to turn off regex interpretation of the search string. Is there a similar facility for sed? I couldn't find anything in the man. A recommendation of another gnu/linux tool would also be fine.

I'm using sed for the find and replace functionality: sed -i "s/abc/def/g"

jww
  • 97,681
  • 90
  • 411
  • 885
EoghanM
  • 25,161
  • 23
  • 90
  • 123
  • possible duplicate of [Escape a string for sed search pattern](http://stackoverflow.com/questions/407523/escape-a-string-for-sed-search-pattern) – Ingo Karkat Jul 24 '13 at 12:44
  • 11
    It is amazing no one answered your actual question of treating the string like a string instead of a regex. I would give my left arm for that answer right now. I really wish folks answering the question would read the question and answer the question that was asked... – jww Jun 22 '18 at 19:19

7 Answers7

11

Do you have to use sed? If you're writing a bash script, you can do

#!/bin/bash

pattern='abc'
replace='def'
file=/path/to/file
tmpfile="${TMPDIR:-/tmp}/$( basename "$file" ).$$"

while read -r line
do
  echo "${line//$pattern/$replace}"
done < "$file" > "$tmpfile" && mv "$tmpfile" "$file"

With an older Bourne shell (such as ksh88 or POSIX sh), you may not have that cool ${var/pattern/replace} structure, but you do have ${var#pattern} and ${var%pattern}, which can be used to split the string up and then reassemble it. If you need to do that, you're in for a lot more code - but it's really not too bad.

If you're not in a shell script already, you could pretty easily make the pattern, replace, and filename parameters and just call this. :)

PS: The ${TMPDIR:-/tmp} structure uses $TMPDIR if that's set in your environment, or uses /tmp if the variable isn't set. I like to stick the PID of the current process on the end of the filename in the hopes that it'll be slightly more unique. You should probably use mktemp or similar in the "real world", but this is ok for a quick example, and the mktemp binary isn't always available.

dannysauer
  • 3,793
  • 1
  • 23
  • 30
  • This is a slow solution. echo is called for every line of the file which could fork thousands of echo processes if you are using a large file or multiple files. – John Sep 13 '13 at 15:40
  • 3
    echo is typically implemented as a shell builtin, which should fork 0 processes on a modern shell like bash. – dannysauer Sep 13 '13 at 17:26
  • You can see the builtins at work by using "strace -e trace=process ./shellscript". Write the above script, and see how many fork calls there are. Using a current Bash on a current Ubuntu, there are 2 fork (actually clone(), which is faster than fork()) calls. One for the subshell where basename is executed, and one for the mv. If you rewrote this using "/bin/echo", on the other hand, then there is actually a new fork for every line. And you could see it taking a *lot* longer to run. :) – dannysauer Sep 13 '13 at 17:49
  • I guess this will only work for lines containing "save" characters. I tried to replace all lines that contain exactly one star "*" and it failed. – guettli Oct 22 '15 at 07:47
  • You need to escape shell metachars in $replace, probably like `replace='*'`. If you don't escape, the asterisk expands to all the files in the path where you run the script. But if you want exactly one *, you'd also need to use shell regexps. Which are a somewhat more advanced topic. – dannysauer Oct 22 '15 at 11:48
  • 1
    Also, to avoid read and echo replacing things like `\n` and `-e` you probably want to do something like `while read -r line;do printf "%s\n" "${line/$pattern/$replace}";done` – gmatht May 30 '16 at 07:49
  • The printf commands is a builtin less often than echo, depending on the shell, so one needs to be careful of that. In real life, I'd personally use ksh and `print --` instead of echo. But good point about `read -r`. I'm editing the answer since that's in the POSIX standard. – dannysauer May 30 '16 at 13:46
  • I'll just leave this here.... [Why is using a shell loop to process text considered bad practice?](http://unix.stackexchange.com/q/169716/135943) – Wildcard Oct 30 '16 at 15:43
  • 1
    There are inaccuracies in that explanation that most of the reasons are built upon ("variable scope is hard" and performance is bad if you use a crappy shell), and the rest is just opinion about "readability" - like putting the same code inside a loop suddenly makes it completely illegible. :) – dannysauer Oct 30 '16 at 15:56
6

Option 1) Escape regexp characters. E.g. sed 's/\$0\.0/0/g' will replace all occurrences of $0.0 with 0.

Option 2) Use perl -p -e in conjunction with quotemeta. E.g. perl -p -e 's/\\./,/gi' will replace all occurrences of . with ,.

You can use option 2 in scripts like this:

SEARCH="C++"
REPLACE="C#"
cat $FILELIST | perl -p -e "s/\\Q$SEARCH\\E/$REPLACE/g" > $NEWLIST
sebix
  • 2,943
  • 2
  • 28
  • 43
Kim Burgaard
  • 3,508
  • 18
  • 11
  • Has perl got an equivalent of sed's -i? I'm using sed as follows: find -name "*myfiles*" |xargs sed -i "s/abc/def/g" – EoghanM Nov 09 '10 at 12:33
  • 1
    I'm seeing your perl example get interpreted as a regex! – EoghanM Nov 09 '10 at 12:34
  • $ echo "a.b.c" | perl -p -e 's/./,/g' gives me ",,,,," and not "a,b,c" – EoghanM Nov 09 '10 at 12:35
  • @EoghanM: That's because you need to escape `.`, otherwise it will match any character. – Hasturkun Nov 09 '10 at 13:26
  • @EogchanM -- I missed the leading \Q and trailing \E. So the first example should be perl -p e 's/\Q.\E/,/gi'. I added i as well to make it case insensitive. – Kim Burgaard Nov 10 '10 at 04:16
  • On re-editing the post, I just found that I did indeed escape the . the first time with a '\', but it doesn't show up in the finally formatted message. Oh well. Hope I didn't confuse more than I helped! :-) – Kim Burgaard Nov 10 '10 at 04:18
  • @Kim, you can escape inline text by putting backticks around it (the same way as you escape block text by indenting it by 4 chars). – EoghanM Nov 10 '10 at 09:54
  • 2
    Perl does have a -i argument which works just like the sed -i arg. – dannysauer May 30 '16 at 13:49
4

If you're not opposed to Ruby or long lines, you could use this:

alias replace='ruby -e "File.write(ARGV[0], File.read(ARGV[0]).gsub(ARGV[1]) { ARGV[2] })"'

replace test3.txt abc def

This loads the whole file into memory, performs the replacements and saves it back to disk. Should probably not be used for massive files.

Hubro
  • 56,214
  • 69
  • 228
  • 381
4

If you don't want to escape your string, you can reach your goal in 2 steps:

  1. fgrep the line (getting the line number) you want to replace, and
  2. afterwards use sed for replacing this line.

E.g.

#/bin/sh
PATTERN='foo*[)*abc'    # we need it literal
LINENUMBER="$( fgrep -n "$PATTERN" "$FILE" | cut -d':' -f1 )"

NEWSTRING='my new string'
sed -i "${LINENUMBER}s/.*/$NEWSTRING/" "$FILE"
Pablo Bianchi
  • 1,824
  • 1
  • 26
  • 30
Bastian Bittorf
  • 447
  • 4
  • 6
0

You can do this in two lines of bash code if you're OK with reading the whole file into memory. This is quite flexible -- the pattern and replacement can contain newlines to match across lines if needed. It also preserves any trailing newline or lack thereof, which a simple loop with read does not.

mapfile -d '' < file
printf '%s' "${MAPFILE//"$pat"/"$rep"}" > file

For completeness, if the file can contain null bytes (\0), we need to extend the above, and it becomes

mapfile -d '' < <(cat file; printf '\0')
last=${MAPFILE[-1]}; unset "MAPFILE[-1]"
printf '%s\0' "${MAPFILE[@]//"$pat"/"$rep"}" > file
printf '%s' "${last//"$pat"/"$rep"}" >> file
Grisha Levit
  • 8,194
  • 2
  • 38
  • 53
0
perl -i.orig -pse  'while (($i = index($_,$s)) >= 0) { substr($_,$i,length($s), $r)}'--\ 
     -s='$_REQUEST['\'old\'']' -r='$_REQUEST['\'new\'']' sample.txt
  • -i.orig in-place modification with backup.
  • -p print lines from the input file by default
  • -s enable rudimentary parsing of command line arguments
  • -e run this script
  • index($_,$s) search for the $s string
  • substr($_,$i,length($s), $r) replace the string
  • while (($i = index($_,$s)) >= 0) repeat until
  • -- end of perl parameters
  • -s='$_REQUEST['\'old\'']', -r='$_REQUEST['\'new\'']' - set $s,$r

You still need to "escape" ' chars but the rest should be straight forward.

Note: this started as an answer to How to pass special character string to sed hence the $_REQUEST['old'] strings, however this question is a bit more appropriately formulated.

Sorin
  • 5,201
  • 2
  • 18
  • 45
-5

You should be using replace instead of sed.

From the man page:

   The replace utility program changes strings in place in files or on the
   standard input.

   Invoke replace in one of the following ways:

       shell> replace from to [from to] ... -- file_name [file_name] ...
       shell> replace from to [from to] ... < file_name

   from represents a string to look for and to represents its replacement.
   There can be one or more pairs of strings.
Brian Clements
  • 3,787
  • 1
  • 25
  • 26
  • 7
    hmmm, don't have 'replace' installed - it looks to be a MySQL related tool? I have MySql installed; is there a separate install for replace? – EoghanM Nov 15 '10 at 15:16
  • It doesn't come in a separate package, sorry. You could try to find it in the mysql source tree and build it yourself, but that is a lot of hassle. So you have mysql, but not `/usr/bin/replace`? You could try and reinstall the mysql package (`mysql-server`). – Brian Clements Nov 15 '10 at 21:32
  • not in /usr/bin and can't reinstall! I'll accept your answer assuming that it works :) – EoghanM Nov 16 '10 at 17:04
  • 58
    Due to the absence of `/usr/bin/replace` on most systems, this does not appear as a universal answer to me. – Serge Stroobandt Jan 28 '14 at 13:31
  • another major flaw with `replace` is that it doesn't do backups when doing inplace edits. – Sorin Mar 06 '20 at 18:03
  • I installed MariaDB, copied `/usr/bin/replace` to my `~/.local/bin` (in `$PATH`) and then uninstalled it. Funny how this answer [get a lot of votes on superuser](https://superuser.com/a/1170402/500826). – Pablo Bianchi Jun 16 '20 at 04:51
  • maybe `$someone` should package [mysql/extra/replace.c](http://www.iskm.org/mysql56/replace_8c_source.html) ... – milahu Oct 07 '21 at 09:58