9

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
    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 Jul 12 '16 at 0:12
  • I'll add more information to the original post – John Allard Jul 12 '16 at 0:13
  • 7
    Try: sed -i.bak '/firmware_revision/ s/test/production/' myfile.py – John1024 Jul 12 '16 at 0:17
  • 1
    Ah it worked perfectly! Can you explain the syntax? – John Allard Jul 12 '16 at 0:19
  • @JohnAllard Very good. I added an answer with explanation. – John1024 Jul 12 '16 at 0:30
22

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
  • 5
    For completeness, you may wish to explain the -i.bak part as well. – Stephen Harris Jul 12 '16 at 0:37
  • 5
    @StephenHarris Good point. Explanation of -i added. – John1024 Jul 12 '16 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 Jul 12 '16 at 8:33
  • @John1024 For the not BSD people, could you add the reasons of the first '' after -i ? – Hastur Jul 12 '16 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 Jul 12 '16 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
  • 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 – don_crissti Jul 12 '16 at 9:20
  • 1
    @don_crissti Very true that, but ed(1) doesn't offer any pretext to show the use of mktemp(1). – Satō Katsura Jul 12 '16 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 Jul 12 '16 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. – Satō Katsura Jul 12 '16 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 Jul 12 '16 at 14:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.