55

I'm trying to add a line of text to the middle of a text file in a bash script. Specifically I'm trying add a nameserver to my /etc/resolv.conf file. As it stands, resolv.conf looks like this:

# Generated by NetworkManager
domain dhcp.example.com
search dhcp.example.com
nameserver 10.0.0.1
nameserver 10.0.0.2
nameserver 10.0.0.3

My goal is to add nameserver 127.0.0.1 above all other nameserver lines, but below any text above that. In the end I want to my resolve.conf file to look like this:

# Generated by NetworkManager
domain dhcp.example.com
search dhcp.example.com
nameserver 127.0.0.1
nameserver 10.0.0.1
nameserver 10.0.0.2
nameserver 10.0.0.3

How is this possible via a bash script? Is this something sed or awk can do? Or would creative greping to recreate the file be my best move?

PHLAK
  • 22,023
  • 18
  • 49
  • 52

6 Answers6

45

Here is a solution using sed:

$ sed -n 'H;${x;s/^\n//;s/nameserver .*$/nameserver 127.0.0.1\n&/;p;}' resolv.conf

# Generated by NetworkManager
domain dhcp.example.com
search dhcp.example.com
nameserver 127.0.0.1
nameserver 10.0.0.1
nameserver 10.0.0.2
nameserver 10.0.0.3

How it works: first, suppress the output of sed with the -n flag. Then, for each line, we append the line to the hold space, separating them with newlines:

H

When we come to the end of the file (addressed by $) we move the content of the hold space to the pattern space:

x

If the first line in pattern space is blank we replace it with nothing.

s/^\n//

Then we replace the first line starting with nameserver by a line containing nameserver 127.0.0.1, a new line (Your version of sed may not support \n, in which case replace the n with a literal newline) and the original line (represented by &):

s/nameserver .*$/nameserver 127.0.0.1\n&/

Now we just need to print the results:

p
JDiPierro
  • 802
  • 1
  • 9
  • 28
brandizzi
  • 26,083
  • 8
  • 103
  • 158
  • 1
    This is great. Only thing weird is it adds a blank line at the top of the new file. Not a big deal, but it's causing an OCD moment. – PHLAK Jul 18 '11 at 21:15
  • 4
    Oh, yes, you are right! The command `sed -n 'H;${x;s/^\n//;s/nameserver .*\n/nameserver 127.0.0.1\n&/;p;}' resolv.conf`, however, solves this problem. I added `s/^\n//` to the command so it will remove the newline at the beginning of the content of pattern space. – brandizzi Jul 18 '11 at 21:17
  • When running this same script on another machine with only one nameserver line it's not inserting anything. – PHLAK Jul 19 '11 at 03:34
  • 3
    Probably because there is no newline at the end of the line. It is easy to solve, too: `sed -n 'H;${x;s/^\n//;s/nameserver .*$/nameserver 127.0.0.1\n&/;p;}' resolv.conf` Now we replace from the first nameserver until the end of the file - I replaced the `\n` in `/nameserver .*\n/` by the marker of end of line `$`. Again, not that the `\n` in `/nameserver 127.0.0.1\n&/` may have to be replaced by a `\` followed by an actual newline in some sed versions. – brandizzi Jul 19 '11 at 13:18
  • Could this answer cause a problem due to the hold space if the file were massive and couldn't fit in memory? – Matthew Herbst Feb 11 '16 at 23:43
  • 1
    I was inspired by your answer to help sleske : http://serverfault.com/a/745968/329412 – Nolwennig Aug 11 '16 at 07:27
34

Assuming you want to insert immediately after the search line, this is much simpler:

sed -ie '/^search/a nameserver 127.0.0.1' filename
  • -i : edit file in place
  • -e : allows the execution of a script/commands inside sed expression
  • a mynewtext : command that tells sed to insert the text mynewtext after matched pattern
Pom12
  • 7,622
  • 5
  • 50
  • 69
Jim Garrison
  • 85,615
  • 20
  • 155
  • 190
  • That's the thing... that search line may or may not be there. The nameserver lines should always be there, and I want the new line inserted immediately before them regardless of the rest of the contents of the file. – PHLAK Jul 18 '11 at 21:16
  • 1
    @PHLAK, the solution offered by Jim is good. I would modify it, though. Simply add a _special comment_ to mark the line where new stuff is supposed to be entered. E.g. a line like `#<>` or something. Then, instead of `/^search/` you can look for `/^#<>$/`, making it far more unlikely to match the wrong line. – bitmask Jul 18 '11 at 22:22
  • How would you modify this (if possible) to use a single line from another file (assume that this file has only one line), instead of `nameserver 127.0.0.1`? – Juan May 13 '13 at 23:31
  • @Pom12 what do you mean by `-e : allows the execution of a script/commands inside sed expression`. Correct me if I'm wrong but I believe the `-e` flag does nothing. AFAIK it's used to specify multiple different sed commands and that's it. This is just 1 – CervEd Dec 16 '21 at 22:39
11

awk '/^nameserver/ && !modif { printf("INSERT\n"); modif=1 } {print}'

Karoly Horvath
  • 94,607
  • 11
  • 117
  • 176
  • 1
    Great `awk` solution, maybe the only one that does not copy the entire file to memory... – MestreLion May 25 '17 at 08:04
  • @MestreLion how do you figure? It reads every line, doesn't it? (I also like this solution) – Daniel Kaplan Aug 20 '21 at 22:10
  • @DanielKaplan: it does read and print every line, but _one at a time_, so the memory footprint does not depend on file size. Other solutions might accumulate until/if/after the insertion point is found, which is not desirable for arbitrarily large files. – MestreLion Aug 21 '21 at 06:29
2

How about something like:

sed -e ':a;N;$!ba;s/nameserver/nameserver 127.0.0.1\nnameserver/' /etc/resolv.conf

(similar to this: sed: Find pattern over two lines, not replace after that pattern)

Community
  • 1
  • 1
sagi
  • 5,619
  • 1
  • 30
  • 31
1

Here's a Perl solution:

perl -lne 'if (not $f and /^nameserver/){ print "nameserver 127.0.0.1"; $f=1 }; print' resolv.conf

  • -n loop around every line of the input file, do not automatically print every line

  • -l removes newlines before processing, and adds them back in afterwards

  • -e execute the perl code

$f is used as a flag to indicate that the nameserver string has already been found

Chris Koknat
  • 3,305
  • 2
  • 29
  • 30
0

This might work for you:

 sed -e '/nameserver/{x;/./b;x;h;i\nameserver 127.0.0.1' -e '}' resolv.conf

Or GNU sed:

sed -e '0,/nameserver/{//i\nameserver 127.0.0.1' -e '}' resolv.conf
potong
  • 55,640
  • 6
  • 51
  • 83