4

In the question How to append multiple lines to a file the OP was seeking a way to append multiple lines to a file within the shell. The solution:

cat <<EOT >> test.txt
line 1
line 2
EOT

I want to prepend lines and my attempt looks like:

echo 3 >test.text
cat test.text <<EOT >> test.text
1
2
EOT

But this results in an error:

cat: test.text: input file is output file

EDIT: for clarification, I am following a long server setup guide with instructions to manually edit configuration files. Editing at times involves prepending blocks of text to a file. In automating some of the steps, I want to retain the verbosity of the command by copying the text from the guide as-is and putting into a bash one-liner. For this reason the multi-line text input using EOT is preferred.

EDIT: other answers using sed require backslashes to be appended to the end of each line but I want to enter multiple lines without modification.

Is there a way to prepend multiple lines to a file in a similar fashion above (ideally without a temporary file and installing moreutils)?

don_crissti
  • 82,805
  • The reason that this is a problem is because it is the shell that does all the redirection work before starting cat (but without reading or writing anything yet) and it can detect that you will overwrite your file and gives an error. The solution is to use a program that can rewrite a file (perl -i comes to mind) and not the shell. – Thorbjørn Ravn Andersen Jan 28 '19 at 16:45
  • It seems to me that this answer https://unix.stackexchange.com/a/99351/117549 achieves that goal...? – Jeff Schaller Jan 28 '19 at 21:25
  • Thanks @JeffSchaller, but not quite as It fails for multiline entries. Please see the output at: https://imgur.com/a/XfvfDjH – user1330734 Jan 29 '19 at 10:04
  • 1
    All the solutions given here or to the duplicate Q are using temporary files. That being said, Jeff Schaller's post now includes a solution that does exactly what you want: it inserts the text as-is via a here-document. – don_crissti Jan 29 '19 at 14:24
  • @don_crissti Jeff's updated answer is what I was looking for I was not familiar with here documents before. – user1330734 Jan 29 '19 at 23:29

4 Answers4

7

The safest way to do this would be to copy the original file to a temporary location, and the concatenate the new contents with that file into the old name:

tmpfile=$(mktemp)

cp myfile "$tmpfile" &&
cat - "$tmpfile" <<NEW_CONTENTS >myfile
this is
new contents
at top of file
NEW_CONTENTS

rm "$tmpfile"

Notice that the cat command itself is almost the same as your

cat myfile <<EOT >>myfile
1
2
3
EOT

However, cat has to be told to also read its standard input, which it does when one of its filename operands is -. We also can't redirect from the original file and then append the result to the same file, as that creates a loop that would make the file grow until it fills all space on the partition. This is why I first copy the file to a temporary file and then use that to create new contents for the old name.

We could also have done

tmpfile=$(mktemp)

cat - myfile <<NEW_CONTENTS >"$tmpfile" &&
this is
new contents
at top of file
NEW_CONTENTS
mv "$tmpfile" myfile

I.e., write the result to a temporary file and then move that so that it replaces the original. This order of operations is however not guaranteed to preserve ownerships and permissions on myfile.


A small shell function that takes standard input and places it atop a given filename:

paste_header () {
    local tmpfile=$(mktemp)

    trap "rm -f '$tmpfile'" EXIT

    cat - "$1" >"$tmpfile" &&
    cat "$tmpfile" >"$1"
}

The way this is written, it would allow the user to, for example, paste in contents from the clipboard interactively, but would not modify the original file if the input was interrupted by Ctrl+C. The interactive input would need to be terminated by pressing Ctrl+D (which sends EOT, i.e. end-of-text).

The function would be used as

paste_header filename

to interactively paste data to be added into filename (end input with Ctrl+D).

or

paste_header filename <otherfile

to insert data from another file.

Kusalananda
  • 333,661
5

If you need to read in the output of a command, you could use ed as in the linked question, with this variation:

ed -s test.txt <<< $'0r !echo stuff\nw\nq'

This reads the output of the command echo stuff into test.txt after line zero.


To insert multi-line text before the 1st line via here-doc you'd run

ed -s test.txt <<EOT
1i
add some line
and some more
to the beginning
.
w
q
EOT

The dot signals the end of input-mode which means the last solution assumes your text doesn't contain lines consisting of single dots.

don_crissti
  • 82,805
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
3

You can use the plain ol' ex text editor which should be available in almost all POSIX comliant systems.

To insert in the first line you just do

ex -sc '1i|new first line' -cx test.txt

For the case of multiple lines to be added, just add the lines in a loop, with the top most element to be added at the last (like a stack)

for number in 5 4 3 2 1; do
    ex -sc '1i|'"$number"'' -cx newfile
done

of if you want to avoid multiple file write operations on the file, construct a variable with the multi-line content and insert it directly

multiple_lines=$'line1\nline2\nline3\nline4\nline5\n'
ex -sc '1i|'"$multiple_lines"'' -cx newfile
Inian
  • 12,807
0

Using sed:

$ sed -e 'i1' -e 'i2' test.text
1
2
3
Olorin
  • 4,656