You could preserve the trailing empty lines by using readarray to save the data, and printf for output.
Something like:
readarray last_n2_lines < <(tail -n+3 $file)
printf "%s" "${last_n2_lines[@]}" > ${file}.new
Demo:
$ cat test; echo end
1
2
3
4
end
$ tail -n+3 test ; echo end
3
4
end
$ readarray data < <(tail -n+3 test)
$ printf "%s" "${data[@]}" ; echo end
3
4
end
Explanation:
readarray data reads lines from standard input into the array called data.
cmd1 < <(cmd2 param1 param2 ...) redirects the output of cmd2 param1 param2 ... to the standard input if cmd1. Careful with the syntax: it must be < [space] <(...), no space between <(, space required between the two < <.
So after the first line, data contains all the lines that tail -n+3 output. Each item in the array is a line of the input with the line terminator included.
Here's how you access each element. Notice that two lines are printed for each item defined in the array: the one in the array (which contains a newline), and the newline added by echo.
$ echo "${data[0]}"
3
$ echo "${data[1]}"
4
$ echo "${data[2]}"
$ echo "${data[3]}"
$ echo "${data[4]}" # out of bounds
$
${data[@]} expands to all elements of the array data
printf "%s" param1 param2 ... prints all of its arguments unchanged (i.e. doesn't add or remove line terminators).
So the second statement prints back everything that was read in, unchanged.