Why does `sudo -i` but not `sudo` remove newline characters?

Running that sudo -i echo $'line1\nline2' under strace shows Bash gets started like this:

9183  execve("/bin/bash", ["-bash", "--login", "-c", "echo line1\\\nline2\\\n"], ...

Now, strace presents special characters with backslash-escapes when it displays the strings, so what Bash actually gets as the argument to -c is echo line1[backslash][newline]line2[backslash][newline] and for the shell, a backslash at the end of a line marks a continuation line and removes the backslash and the following newline.

Without -i, sudo runs echo directly, without going through the shell:

9189  execve("/bin/echo", ["echo", "line1\nline2\n"], ... 

Here, that's a literal newline going to echo, and echo duly prints that.

The idea here must be that sudo tries to add a layer of shell escaping to accommodate for the fact that sh -c takes a single string, while sudo itself takes the command as distinct arguments.

Compare the following cases:

sudo escapes the space (this is just the name of the command, no arguments!):

$ sudo -i 'echo foo'
-bash: echo foo: command not found

sudo escapes the backslash, so that this actually works (Bash's echo doesn't process the backslash):

$ sudo -i echo 'foo\bar'

Same with a tab:

$ sudo -i echo $'foo\tbar'
foo     bar

Here, there's no extra quoting on the backslash, so Bash removes it while processing the shell command line (b isn't a special character to the shell, and doesn't need quoting. This is basically the same as bash -c 'echo foo"b"ar'):

$ bash -c 'echo foo\bar'

The problem is just that you can't escape a newline with a backslash, and sudo doesn't seem to take that into account.

In any case, quoting issues like this probably turn quite a bit easier if you store the commands you want in a file, and run that as a script.

You could change the structure of the command:

echo "echo \"line1${n}line2${n}\"" | sudo -i bash -s

This way sudo does not see the argument and thus cannot mess it up.