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.

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.

  • 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. – cas Jan 17 '18 at 10:45
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.

4

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.

  • 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. – G-Man Jan 17 '18 at 19:21
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

  • 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) – don_crissti Jan 17 '18 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
    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?). – Jeff Schaller Jan 17 '18 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. – Nubarke Jan 18 '18 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%. – Wildcard Jan 18 '18 at 13:42

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.