7

I want to insert a new line in two lines before the last line. So if my original file is:

1
2
3
4
5

The result should be

1
2
3
New line
4
5
14

Using ed:

$ printf '$-1i\nNew line\n.\n,p\n' | ed -s file
1
2
3
New line
4
5

The ed editing script:

$-1i
New line
.
,p

This first moves to the line one line up from the end ($-1) and inserts (i) new contents above that line. The contents inserted is ended with the single dot (it's allowed to be multiple lines). The last ,p displays the complete modified buffer on the terminal.

You may redirect this to a new file, or you may write it back to the original file using

printf '$-1i\nNew line\n.\nw\n' | ed -s file

(the ,p is changed to w).

This latter is also how you would similarly use ex for this job:

printf '$-1i\nNew line\n.\nw\n' | ex -s file

ed and ex are standard line-oriented editors (as opposed to full-screen editors) that should come with your system. Note that -s means different things to each, but is appropriate for both when doing batch mode editing tasks like this.

  • ed. "Shell and utilities". Base specifications. IEEE 1003.1:2017. The Open Group.
  • ex. "Shell and utilities". Base specifications. IEEE 1003.1:2017. The Open Group.

Further reading:

  • 1
    This is the best solution so far but keep in mind that it has its limitations too (dealing with huge files or inserting lines that consist of a single dot). – don_crissti May 15 '18 at 12:24
4

You could use GNU sed too:

sed -zE 's/(\n[^\n]*){3}$/\nNew-line&/' infile

Insert a line New-line in third last line of the file (Insert a line two lines before the last line).

This (\n[^\n]*){3}$ matches \newline followed by anything but not a \newline and maximum 3 times from end of the file where -z option casing sed to read a file as a single line (separate lines by NUL characters). So it will match below only (between asterisks I highlighted):

3*\n
4\n
5\n*

Portability, you would use:

sed -e ':t;N;$!bt; s/\(\n[^\n]*\)\{2\}$/\nNew-line&/' infile

if still your sed cannot handle a \newline in replacement part, you can have actual newline or press Ctrl+V followed by Enter which will print ^M control characters of enter key:

sed -e ':t;N;$!bt; s/\(\n[^\n]*\)\{2\}$/\
New-line&/' infile

How it works

The N append each line sed read to the pattern space followed by a new line added until all lines read. The :t defines a label and $!bt telling sed jump to the label called t as long as it's not end of the file.

  • I suspect this will get really slow with a huge input file and a large no. of lines that follow the insertion... Also, just like Rakesh's solution, this requires sanitizing NEW_LINE before using it. – don_crissti May 15 '18 at 12:14
4

It can be done by simple head and tail:

$ output=$(head -n -2 file ; echo 'new line' ; tail -2 file)
$ echo "$output" > file

As mentioned in comments , it will eat any trailing blank lines. So , for preserving trailing blank lines,

$ head -n -2 file >> file.tmp
$ echo 'new line' >> file.tmp
$ tail -2 file >> file.tmp
$ mv file.tmp file

or single liner

$ head -n -2 file >> file.tmp ; echo 'new line' >> file.tmp; tail -2 file >> file.tmp ; mv file.tmp file
1

Following awk could help too.

awk -v line=$(wc -l < Input_file) -v val="new_line" 'FNR==(line-1){print val} 1' Input_file
1

This one should be quite robust against contents of the inserted line, but requires the file to be scanned twice (wc for the first pass).

sed "$(($(wc -l < yourfile)-$N))a"'new line' yourfile
0
sed -e '
   1N
   $!{N;P;D;}
   H;g;s/^/NEW_LINE/
' input-data.txt

How it works

  • At all times maintain two ines in the pattern space.
  • Print the first of the two lines in the pattern space, chop it off, and read and append the next.
  • Rinse...Lather...Repeat so long as the last line has not been read into the pattern space.
  • At which point, we insert a newline (\n) at the beginning and then put the required string there.
  • This is POSIX-ly sed
0
tac input.txt | sed '2 a New line' | tac

Explanation

  • tac - prints lines in reverse, so the last line becomes the first, the last - 1 line becomes the second, etc.
  • sed '2 a New line - append New line after the second line.
  • tac - reverses lines again.

Testing

$ tac <(seq 1 5) | sed '2 a New line' | tac
1
2
3
New line
4
5

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.