4

We have the following example file:

tcpmux 1/tcp # TCP port service multiplexer tcpmux 1/udp # TCP port service multiplexer rje 5/tcp # Remote Job Entry rje 5/udp # Remote Job Entry echo 7/tcp echo 7/udp discard 9/tcp sink null discard 9/udp sink null systat 11/tcp users systat 11/udp users daytime 13/tcp daytime 13/udp qotd 17/tcp quote qotd 17/udp quote msp 18/tcp # Message send protocol (historic) msp 18/udp # Message send protocol (historic) chargen 19/tcp ttytst source chargen 19/udp ttytst source 

How may we append the following lines to the beginning of the file?

# The latest IANA port assignments can be gotten from # http://www.iana.org/assignments/port-numbers # The Well Known Ports are those from 0 through 1023. # The Registered Ports are those from 1024 through 49151 # The Dynamic and/or Private Ports are those from 49152 through 65535 # # Each line describes one service, and is of the form: # # service-name port/protocol [aliases ...] [# comment] 

So that the file will look like:

# The latest IANA port assignments can be gotten from # http://www.iana.org/assignments/port-numbers # The Well Known Ports are those from 0 through 1023. # The Registered Ports are those from 1024 through 49151 # The Dynamic and/or Private Ports are those from 49152 through 65535 # # Each line describes one service, and is of the form: # # service-name port/protocol [aliases ...] [# comment] tcpmux 1/tcp # TCP port service multiplexer tcpmux 1/udp # TCP port service multiplexer rje 5/tcp # Remote Job Entry rje 5/udp # Remote Job Entry echo 7/tcp echo 7/udp discard 9/tcp sink null discard 9/udp sink null systat 11/tcp users systat 11/udp users daytime 13/tcp daytime 13/udp qotd 17/tcp quote qotd 17/udp quote msp 18/tcp # Message send protocol (historic) msp 18/udp # Message send protocol (historic) chargen 19/tcp ttytst source chargen 19/udp ttytst source 

The simple solution is to copy the original file to file.bck, append the new lines to the file, and append file.bck to the file.

But this isn't an elegant solution.

2
  • 1
    sed -i with i command is your friend. Commented Jan 17, 2018 at 9:30
  • 3
    In reply to my comment/request here you said "ok, understood". What exactly did you understand ? Commented Jan 17, 2018 at 14:06

6 Answers 6

6

Relatively elegant solution using POSIX specified file editor ex—at least elegant in the sense that this will handle any arbitrary contents rather than depending on a specific format (trailing backslashes) or a specific absence of format.

printf '0r headerfile\nx\n' | ex file-with-contents 

This will open file-with-contents in ex, read in the full contents of the headerfile at the very top, and then save the modified buffer back to file-with-contents.

If performance is a SEVERE concern and the files are huge this may not be the right way for you, but (a) there is no performant general way to prepend data to a file and (b) I don't expect you will be editing your /etc/services file that often.


A slightly cleaner syntax (the way I would actually code this):

printf '%s\n' '0r headerfile' x | ex file-with-contents 

A more complicated, but convergent, bit of code that will check whether the beginning of services EXACTLY matches the entirety of header, byte for byte, and IF NOT will then prepend the entire contents of header to services and save the changes, follows.

This is fully POSIX compliant.

dd if=services bs=1 count="$(wc -c < header)" 2>/dev/null | cmp -s - header || printf '%s\n' '0r header' x | ex services 

A much simpler version, using GNU cmp's "-n" option:

cmp -sn "$(wc -c <header)" header services || printf '%s\n' '0r header' x | ex services 

Of course, neither of these is smart enough to check for PARTIAL matches, but that's getting far beyond the ability of a simple one liner, since guesswork would be intrinsically involved.

1
  • 4
    +1. I was going to comment with your cleaner syntax second version, but it's already there. that's the way I'd do it....in fact, it's the way i have done it but with ed rather than ex. btw, one more advantage of using ed or ex is that (unlike the -i options of sed or perl) it's a real in-place edit - the resulting file has the same inode as the original. Commented Jan 17, 2018 at 10:45
5

Usually, you do just that. Prepending lines to a file is hard, since files are just sequences of bytes, so you'd need to move the existing data ahead to make space for the new data, and there's no direct method for that (at least no standard method). In theory, one might imagine a filesystem based on variable length records, where you could add new records at the start or between existing records, but that's not how it works in practice.

Some filesystems can move blocks of data around, but they're fixed-size blocks, and so not much use for text files, where the lines have variable lengths.

Even if you do something like sed -i or perl -i, they're going to create a temporary file behind the scenes just for that reason.

So, be it elegant or not, I'd go with:

cat prefix data > data.new && mv data.new data 

For a few lines, you could use (in GNU sed):

sed -i.bak -e '1i first prefix line' -e '1i second prefix line' data 

But generating the insert commands or adding backslashes for each line to be added isn't elegant either.

1
  • Alternatively, cat prefix data > data.new  &&  cp data.new data  &&  rm data.new will keep data in the same inode (which is important if you have hard links, or if you need to preserve attributes like ownership or ACLs), at the cost of doing twice as much I/O. Commented Jan 17, 2018 at 19:21
4

OK I have decided to write an answer besides a comment.

You can use the i command of sed like:

sed -i '1i \ # The latest IANA port assignments can be gotten from\ # http://www.iana.org/assignments/port-numbers\ # The Well Known Ports are those from 0 through 1023.\ # The Registered Ports are those from 1024 through 49151\ # The Dynamic and/or Private Ports are those from 49152 through 65535\ #\ # Each line describes one service, and is of the form:\ #\ # service-name port/protocol [aliases ...] [# comment]' file 

The is for GNU sed. For sed on Macs you need to use sed -i '' -e ..., and for POSIX sed there is no simple way to do things in place.

0

Used below sed one liner to achieve the result. As Tested it worked fine

Let me know for any doubts

 sed '1s/.*/\n&/g' examplefile| sed '1r file2.txt' | sed '1d' 

examplefile===> its the inputfile

file2.txt===> it is the file which contains the content which need to appended in the beginning of examplefile

1
  • 2
    This doesn't edit the file in-place. If you just need to output the combined content of files to stdout you can simply do cat file1 file2 there's no need to chain 3 sed invocations (and btw, this can be done with a single sed invocation) Commented Jan 17, 2018 at 14:23
0

Another approach: Using sponge from the moreutils package that is available in most Linux distributions, you could also do

$ cat - file.txt | sponge file.txt # The latest IANA port assignments can be gotten from # http://www.iana.org/assignments/port-numbers # The Well Known Ports are those from 0 through 1023. # The Registered Ports are those from 1024 through 49151 # The Dynamic and/or Private Ports are those from 49152 through 65535 # # Each line describes one service, and is of the form: # # service-name port/protocol [aliases ...] [# comment] 

This uses cat to combine reproduce the standard input (-) followed by the original file (file.txt), and pipes the combined output into sponge, which then writes the result back to the same file in-place. You then paste your header into the terminal and finish with Ctrl+D.

Alternatively, if you have already stored the header in a separate file, you can use

cat header.txt file.txt | sponge file.txt 

to achieve the same result.

-2

In the absence of any unusual details (such as dealing with huge files), certainly, concatenating the header and the file into a temporary, and then renaming the temporary into its place, is the most elegant solution for me.

cat header file.txt >file.tmp ; mv file.tmp file.txt 
3
  • 3
    This is worse than ilkkachu's very similar answer from 4 hours ago: cat prefix data > data.new && mv data.new data, as yours will clobber file.txt if the initial cat fails (full filesystem?). Commented Jan 17, 2018 at 17:08
  • You have a point there, but I did start the answer by assuming a normal, sane environment. A full filesystem is a rare condition, yet very easily noticed when it happens (and as such you can take special precautions, such as when working with a system out of memory). Otherwise, inserting an explanatory header into a regular text config file is rather unlikely to be the tipping point. Commented Jan 18, 2018 at 9:10
  • 1
    The thing is that for scripted solutions, you don't get to assume a sane operating environment. So at the very least an answer like this should have a big bold warning message that it should only be used interactively—and in that case, it really shouldn't be on one line (i.e. the semicolon should be a literal newline). The percentage of Production outages that are caused by lazy scripting that assumes a sane operating environment is probably in excess of 75%. Commented Jan 18, 2018 at 13:42

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.