I’m trying to insert (or prepend) a file into another file using sed -i however this approach breaks hardlinks. How do I solve this?

Some versions of sed have an -i option and my manual gives the following description for it.

-i[SUFFIX], –in-place[=SUFFIX]

edit files in place (makes backup if SUFFIX supplied)

I’m not sure why they say “edit” and “in place” because it actually creates a new file with the updated changes. Unfortunately this causes a lot of confusion and as mentioned it breaks hardlinks.

$ cat myfile
moo
$ stat myfile
  File: ‘myfile’
  Size: 4               Blocks: 8          IO Block: 4096   regular file
Device: 801h/2049d      Inode: 4358555     Links: 1
Access: (0600/-rw-------)  Uid: (101642/user)   Gid: (  107/group)
Access: 2017-06-09 13:11:40.594059471 +0100
Modify: 2017-06-09 13:11:31.425928132 +0100
Change: 2017-06-09 13:11:31.425928132 +0100
 Birth: -

Make note of the Inode number here which is 4358555 as we will now run sed -i on the file.

$ sed -i 's/omg/lol/' myfile
$ cat myfile
moo

We tried to replace omg with lol which wasn’t present in the file resulting in the same content we started with.

Let’s check the file again with the stat command.

$ stat myfile
  File: ‘myfile’
  Size: 4               Blocks: 8          IO Block: 4096   regular file
Device: 801h/2049d      Inode: 6306549     Links: 1
Access: (0600/-rw-------)  Uid: (101642/user)   Gid: (  107/group)
Access: 2017-06-09 13:11:57.422300547 +0100
Modify: 2017-06-09 13:11:54.602260148 +0100
Change: 2017-06-09 13:11:54.602260148 +0100
 Birth: -

As we can see the Inode number has changed meaning we now have a different file albeit with the same filename as before. It’s as if we first performed a rm myfile and then recreated it.

What we need to use is an “actual file editor” one of which is the ed command.

First we will create some test files.

file1

$ cat file1
file1 line1
file1 line2

file2

$ cat file2
file2 line1
file2 line2

As mentioned at the start the goal is to “insert” file1 into file2 or you could also say we want to “prepend” file2 with file1 resulting in the following output.

file1 line1
file1 line2
file2 line1
file2 line2

We will also check the inode number of file2 using the stat command before we proceed.

$ stat file2
  File: ‘file2’
  Size: 24              Blocks: 8          IO Block: 4096   regular file
Device: 801h/2049d      Inode: 6290327     Links: 1
Access: (0600/-rw-------)  Uid: (101642/user)   Gid: (  107/group)
Access: 2017-06-09 13:34:45.149894132 +0100
Modify: 2017-06-09 13:34:39.397811729 +0100
Change: 2017-06-09 13:34:39.397811729 +0100
 Birth: -

ed

It’s now time to say hello to mr. ed

$ ed -s file2 <<< $'0r file1\nw'

Check if file2 was modified.

$ cat file2
file1 line1
file1 line2
file2 line1
file2 line2

… and check the inode number.

$ stat file2
  File: ‘file2’
  Size: 48              Blocks: 8          IO Block: 4096   regular file
Device: 801h/2049d      Inode: 6290327     Links: 1
Access: (0600/-rw-------)  Uid: (101642/user)   Gid: (  107/group)
Access: 2017-06-09 13:35:13.902306029 +0100
Modify: 2017-06-09 13:35:11.794275829 +0100
Change: 2017-06-09 13:35:11.794275829 +0100
 Birth: -

It has remained as 6290327 meaning this time we actually edited the file “in place”.

ed can be used interactively however we’re using <<< here to send our command string into ed

<<< is called a Here String and as mentioned it sends a given string to the stdin of a given command.

We could have also used a pipeline approach e.g.

echo $'0r file1\nw' | ed -s file2

The -s option tells ed to be silent and not produce any output which is useful for “scripting” or “automating” file edits.

The $'' used here is a special form of shell quoting:

“Words of the form $'string' are treated specially. The word expands to string, with backslash-escaped characters replaced as specified by the ANSI C standard.”

This means that the \n here produces an “actual” newline character.

$ echo $'0r file1\nw'
0r file1
w

Another common approach for generating such a string is to use printf

$ printf '0r file1\nw\n'
0r file1
w

There is also of course the option of a Here Document:

$ ed -s file2 <<\.
0r file1
w
.

So we are dealing with 2 ed commands here:

  • 0r file1
  • w

The r command when given a filename will “Read file to after the addressed line.” We’ve used 0 as the line number to address as we want to insert the contents before line 1 as opposed to after.

Finally, the w command writes the file.