12

I'm trying to change a single word on a specific line in a file, but I'm having some trouble connecting all together.

Basically, on one line in my file there is a keyword 'firmware_revision', and on this line (and only this line) I want to replace the word 'test' with the word 'production'.

So I can do this:

grep 'firmware_revision' myfile.py | sed 's/test/production'

This will pick out the line I want and perform the substitution, but I can't figure out how to get this new line into the original file to replace the old line. I obviously cannot just redirect it back to the file, so what should I do?

Even if I use temporaries, by using grep to get just the line I need I lose all of the other data in the file, so I can no longer just redirect it all to a temp file then replace the original with the temp.

Edit - Someone asked for more information

Lets say I have a file full of lines like this

[
  ('key_name1', str, 'value1', 'Description'),
  ('key_name2', str, 'value2', 'Description'),
  ('key_name3', str, 'value3', 'Description'),
  ('firmware_revision', str, 'my-firmware-name-test', 'Firmware revision name')
]

now I want to write a script (ideally a one-liner) that will find the line that contains 'firmware_revision', and changes all instances of the word 'test' on that line to 'production'. The word 'test' might be in other places in that file and I do not want those changed. So to be clear, I want to change the above line to

('firmware_revision', str, 'my-firmware-name-production', 'Firmware revision name')

How do I do this?

5
  • 5
    sed is very powerful, it can perform both functions (grep and substitution) but we will need more info on how the line looks to help you.
    – grochmal
    Commented Jul 12, 2016 at 0:12
  • I'll add more information to the original post Commented Jul 12, 2016 at 0:13
  • 7
    Try: sed -i.bak '/firmware_revision/ s/test/production/' myfile.py
    – John1024
    Commented Jul 12, 2016 at 0:17
  • 1
    Ah it worked perfectly! Can you explain the syntax? Commented Jul 12, 2016 at 0:19
  • @JohnAllard Very good. I added an answer with explanation.
    – John1024
    Commented Jul 12, 2016 at 0:30

2 Answers 2

23

Try:

sed -i.bak '/firmware_revision/ s/test/production/' myfile.py

Here, /firmware_revision/ acts as a condition. It is true for lines that match the regex firmware_revision and false for other lines. If the condition is true, then the command which follows is executed. In this case, that command is a substitute command that replaces the first occurrence of test with production.

In other words, the command s/test/production/ is executed only on lines which match the regex firmware_revision. All other lines pass through unchanged.

By default, sed sends its output to standard out. You, however, wanted to change the file in place. So, we added the -i option. In particular, -i.bak causes the file to be changed in place with a back-up copy saved with a .bak extension.

If you have decided that the command works for you and you want to live dangerously and not create a backup, then, with GNU sed (Linux), use:

sed -i '/firmware_revision/ s/test/production/' myfile.py

By contrast, on BSD (OSX), the -i option must have an argument. If you don't want to keep a backup, provide it with an empty argument. Thus, use:

sed -i '' '/firmware_revision/ s/test/production/' myfile.py

Edit

In the edit to the question, the OP asks for every occurrence of test on the line to be replaced with production. In that case, we add the g option to the substitute command for a global (for that line) replacement:

sed -i.bak '/firmware_revision/ s/test/production/g' myfile.py
7
  • 5
    For completeness, you may wish to explain the -i.bak part as well. Commented Jul 12, 2016 at 0:37
  • 5
    @StephenHarris Good point. Explanation of -i added.
    – John1024
    Commented Jul 12, 2016 at 0:47
  • I feel you could probably reach the stars, just standing on the book explaining all the things that sed can do...
    – KlaymenDK
    Commented Jul 12, 2016 at 8:33
  • @John1024 For the not BSD people, could you add the reasons of the first '' after -i ?
    – Hastur
    Commented Jul 12, 2016 at 8:37
  • 3
    OP wanted to changes all instances of the word 'test' on that line to 'production'. It's important to append /g to the sed command in this case: sed -i '/firmware_revision/ s/test/production/g' myfile.py, otherwise only the first instance is changed.
    – knub
    Commented Jul 12, 2016 at 8:38
4

On older machines with old-school sed that doesn't support option -i:

TF=$( mktemp -t "${0##*/}"_$$_XXXXXXXX ) && \
trap 'rm -f "$TF"' EXIT HUP INT QUIT TERM && \
sed '/firmware_revision/ s/test/production/' myfile.py >"$TF" && \
mv -f "$TF" myfile.py
5
  • 1
    On those older machines you can simply use ed and do it in one line: printf %s\\n g/firmware_revision/s/test/production/g w q | ed -s myfile.py Commented Jul 12, 2016 at 9:20
  • 1
    @don_crissti Very true that, but ed(1) doesn't offer any pretext to show the use of mktemp(1). Commented Jul 12, 2016 at 12:22
  • If you're using a tempfile, you may as well keep the inode and perms of the original file, same as ed does. Instead of mv -f "$TF" myfile.pl, use cat "$TF" > myfile.pl && rm -f "$TF". BTW, it's good practice to use lowercase variable names ($tf instead of $TF), they're guaranteed not to conflict with any bash built-in vars (probably other bourne shells too).
    – cas
    Commented Jul 12, 2016 at 14:30
  • @cas Re: cat "$TF" > myfile.pl: try this: touch a b; chmod 0444 a; cat b >a (BTW, sed -i won't work in that case, either). Better let the user deal with that. Re: rm -f "$TF": not needed, see trap ... EXIT. Re: $tf instead of $TF: maybe; it's a matter of style. Commented Jul 12, 2016 at 14:39
  • That's normal and expected behaviour of unix file permissions. My point was that creating a new file and mv-ing it over the original will 1. result in a new inode for that filename (breaking any hard-links). 2. possibly change perms, if current umask differs from original perms. As for uppercase vars, it's not just a matter of style - lots of people who have made the mistake of using uppercase PATH or RANDOM or SHELL or whatever for their own variables have found out the hard way. (and yes, i often use uppercase vars myself. I know it's wrong, I'm trying to break the habit).
    – cas
    Commented Jul 12, 2016 at 14:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .