4

I have a food.txt file as follows :-

mangoes|foo|faa  
oranges|foo|faa  
chocolates|foo|baz  

I am trying to replace foo with bar if condition baz is met. (would like to use a regular expression here b*z )
Currently using the sed command below, but this does not directly modify the existing file. I am also not able to use a regular expression (b*z).

sed '/baz/s/foo/bar/g' food.txt

Please suggest a way other than "sed" to modify the existing file directly.

PS : I tried sed -i but I would like to use some other command than sed.
I am using mobaxterm (OS - Windows)

jasmin
  • 43

4 Answers4

7

I see nothing wrong with using sed in this case. It's the right tool for the job.

Your command works well (on the given data):

$ sed '/baz/s/foo/bar/g' food.txt
mangoes|foo|faa
oranges|foo|faa
chocolates|bar|baz

Using a regular expression to match any string beginning with |b and ending with z at the end of the line (instead of baz anywhere on the line):

$ sed '/|b.*z$/s/foo/bar/g' food.txt
mangoes|foo|faa
oranges|foo|faa
chocolates|bar|baz

To make the change to the file (with GNU sed):

$ sed -i '/|b.*z$/s/foo/bar/g' food.txt

or (with any sed implementation):

$ sed '/|b.*z$/s/foo/bar/g' food.txt >tmpfile && mv tmpfile food.txt

You could also use awk:

$ awk 'BEGIN { OFS=FS="|" } $3 == "baz" { $2 = "bar" }; 1' file
mangoes|foo|faa
oranges|foo|faa
chocolates|bar|baz

or matching ^b.*z$ in the 3rd field,

$ awk 'BEGIN { OFS=FS="|" } $3 ~ /^b.*z$/ { $2 = "bar" }; 1' file
mangoes|foo|faa
oranges|foo|faa
chocolates|bar|baz

... or Miller (mlr), here reading the input as a header-less CSV file that uses | as field delimiters:

$ mlr --csv -N --fs pipe put '$3 == "baz" { $2 = "bar" }' file
mangoes|foo|faa
oranges|foo|faa
chocolates|bar|baz

or,

$ mlr --csv -N --fs pipe put '$3 =~ "^b.*z$" { $2 = "bar" }' file
mangoes|foo|faa
oranges|foo|faa
chocolates|bar|baz

The benefit of using awk or Miller is that it's easier and safer to match a pattern against an isolated field. Miller has the added benefit of understanding CSV quoting rules.

Kusalananda
  • 333,661
  • 2
    @terdon Thanks for the edit. The reason I didn't use sed -i was that it works very differently between GNU and BSD sed. Just saying sed -i script will use script as a backup suffix with BSD sed. Doing sed ... && mv is safer and more portable. – Kusalananda Aug 15 '16 at 10:58
  • Ah, fair point, that's very true. In any case, it turns out the OP is using mobaxterm so I have no idea what sed flavor will be available. Feel free to roll my edit back. – terdon Aug 15 '16 at 11:00
  • @terdon No worries, I added extra comments instead. – Kusalananda Aug 15 '16 at 11:01
5

Using awk with gsub():

awk '/baz$/ {gsub("foo", "bar")};1' food.txt
  • Use any Regex pattern to match instead of /baz$/, if you want

  • if the pattern matches, do gsub() to substitute desired strings


For inpace editing, Recent version of GNU awk (>=4.1.0) has inplace modification option:

awk -i inplace '/baz$/ {gsub("foo", "bar")};1' food.txt

Otherwise you can use sponge from GNU moreutils or use a temporary file:

awk '/baz$/ {gsub("foo", "bar")};1' food.txt >temp_food.txt && \
      mv temp_food.txt food.txt

Example:

$ cat file.txt
mangoes|foo|faa
oranges|foo|faa
chocolates|foo|baz

$ awk '/baz$/ {gsub("foo", "bar")};1' file.txt
mangoes|foo|faa
oranges|foo|faa
chocolates|bar|baz
heemayl
  • 56,300
3

While I have no idea why you don't want to use sed -i which does exactly what you need, another option would be perl:

$ perl -pe 's/foo/bar/g if /b*z/' food.txt 
mangoes|foo|faa  
oranges|foo|faa  
chocolates|bar|baz

The -pe means "print every line of the input file after applying the script to it.

And you can use -i to edit the file in place:

perl -i -pe 's/foo/bar/g if /b*z/' food.txt 

Also, note that the regular expression b*z means "match 0 or more b followed by a z. It will work here because b*z matches bar by ignoring b and a and just matching z. In other words, it will match any z since any z will be an example of 0 b followed by a z. I think what you probably mean to use is b.*z (a b followed by 0 or more characters and then a z):

perl -i -pe 's/foo/bar/g if /b.*z/' food.txt 
terdon
  • 242,166
2

The actual best tool for automated text edits is ex.

Although it should be possible to call ex directly, I have found in MobaXterm that I have to call vim -e instead.

So, the best way to do this automated edit in MobaXterm (and which will also work on other *nix systems) is:

printf '%s\n' 'g/b.*z/s/foo/bar/g' x | vim -es food.txt

To be fully POSIX compliant, it is only necessary to alter it to:

printf '%s\n' 'g/b.*z/s/foo/bar/g' x | ex -s food.txt

However, calling the ex command may not work correctly on MobaXterm (it doesn't on my installation.) Try the vim -es version of the command if the ex version fails.

Wildcard
  • 36,499
  • Thank you. ex -s is working for me on Mobaxterm. Can u please explain the code. I understand that %s stands for search but not able to grasp the rest of it.. – jasmin Aug 16 '16 at 10:38
  • @jasmin, ironically enough, the %s doesn't stand for search but for "string." :) I'll add an explanation soon, but in the meantime if you start with this answer and also read the links you will learn a fair bit more about ex. – Wildcard Aug 16 '16 at 17:22