0

Is it possible to either find and replace a line in a file OR append a string to the end if it is not there?

I know I can use this to find and replace:

sed -i -e "s/^SEARCH/LINE 1\nLINE 2/" file

I know I can append to the file like this:

cat << EOF | tee -i file1 file2
LINE 1
LINE 2
EOF

Is it possible to somehow to combine this. So if /^SEARCH.*$/ matches then replace it, if it doesn't, then append the replacement to the end of the file.

Update with a better input/output example:

For example, if I had this input file testfile:

Alpha
Bravo
Charlie

Let's say I wanted to find and replace Bravo with Bravo=bingo, OR add Bravo=bingo if Bravo is not there, the expected output is:

Alpha
Bravo=bingo
Charlie

This is because Bravo exists in the file, so it is replaced.

Let's say I wanted to find and replace Delta with Delta=bingo, OR add Delta=bingo if Delta is not there, the expected output is:

Alpha
Bravo
Charlie
Delta=bingo

This is because Delta is not in the file so it is appended.

IMTheNachoMan
  • 5,343
  • 5
  • 40
  • 89
  • I'm not stuck on `sed`. Isn't SO/SE about the *best* way to do something? The way you showed works, and its especially useful for inserting something with multiple lines, but are you saying its the **best** way to do it if you don't need to insert multiple lines? If you are then I'll secede. The reason I want to re-open the question is to see if there is a better way to do this when you don't need to insert something with multiple lines. – IMTheNachoMan Feb 03 '19 at 20:17
  • Yes that's exactly what I'm saying as it's clear, simple, portable, efficient, robust, and easy to enhance. idk what SO/SE means btw and couldn't find a definition for it by googling. – Ed Morton Feb 04 '19 at 00:25
  • Your explanatory text doesn't match your sample outputs. You say you want to replace `^SEARCH=.*$` but this doesn't match your "Bravo" example. You say you want to append `Delta=bingo` but you actually append `Delta`. – jhnc Feb 04 '19 at 22:21
  • @jhnc Oops. Sorry for the typos. I fixed. – IMTheNachoMan Feb 04 '19 at 22:25

4 Answers4

2

The replacement part is exactly the same as at https://stackoverflow.com/a/54504046/1745001 but here it is simplified for this specific use case and the tweak of appending if not found is done just by setting a flag if it's found and printing at the END if that flag isn't set:

awk 'BEGIN{new="LINE 1\nLINE 2"} /^SEARCH/{$0=new; f=1} {print} END{if (!f) print new}' file

Again the above is using strings for the replacement and so will work for any chars, the /^SEARCH/ is a regexp since you don't appear to have regexp metachars but if you did you'd change that to index($0,"SEARCH")==1 or similar to do a string rather than regexp match.

Ed Morton
  • 188,023
  • 17
  • 78
  • 185
1

awk one-liner:

awk 'gsub(/^SEARCH/,"LINE 1\nLINE 2"){s=1}END{if(!s)print "LINE 1\nLINE 2"}1' file

This does not replace in-place though, if you want in-place change:

awk 'gsub(/^SEARCH/,"LINE 1\nLINE 2"){s=1}END{if(!s)print "LINE 1\nLINE 2"}1' file | tee file

Remember to escape the orginal and replacement if they contain regex characters, it's easier than change to match/substr method when the texts are not long.

Til
  • 5,150
  • 13
  • 26
  • 34
  • So this basically tries to do the replace, and if it works it sets a variable, and if doesn't then it'll print that line in the end? – IMTheNachoMan Feb 03 '19 at 16:30
  • @IMTheNachoMan Right. – Til Feb 03 '19 at 16:34
  • @IMTheNachoMan No matter replace works or not the lines will be print. the `s` var is for determine append at last or not. – Til Feb 03 '19 at 16:35
  • 1
    That will fail given various possible characters in the original and replacement texts. This problem needs to be solved with string operations, not regexps and back-reference enabled text. – Ed Morton Feb 03 '19 at 16:36
  • @IMTheNachoMan Check what Ed mentioned, if you do have such scenarios be careful and remember to escape (easier than change to match and substr etc if the text's not much). – Til Feb 03 '19 at 16:39
  • 1
    @EdMorton Thanks for mention it. – Til Feb 03 '19 at 16:39
0

Don't know about a single sed command, but you can just have sed backup the file, then test the difference. If there is no difference, append to end of file:

sed -i.bak -e 's/^SEARCH/LINE 1\nLINE 2/' file
diff file file.bak > /dev/null
if [ $? -eq 0 ]; then
    sed -i -e '$aLINE 1\nLINE 2' file
fi
Jack
  • 5,801
  • 1
  • 15
  • 20
  • That last sed line would treat `$aLINE` as a shell variable that expands to null. Always use single quotes around strings and scripts unless you have a very specific NEED to use double quotes (e.g. to expose the script/string to the shell for variable expansion) and fully understand the implications and consequences. It'd fail in various ways given various values of SEARCH and each LINE, and calling multiple tools on the file is obviously a very inefficient approach (e.g. why do the first sed+diff instead of just one grep for SEARCH?). – Ed Morton Feb 04 '19 at 15:37
  • Thanks, @EdMorton. Fixed it. – Jack Feb 05 '19 at 17:46
0

GNU sed one-liner:

sed -i 's/^SEARCH/LINE 1\nLINE 2/;ts;bt;:s;h;:t;${x;/./!{x;s/$/\nLINE 1\nLINE 2/;be};x};:e' file
Til
  • 5,150
  • 13
  • 26
  • 34