Why doesn't printf escape newlines?

This is nothing to do with printf, and everything to do with the argument that you have given to printf.

In a double-quoted string, the shell turns \\ into \. So the argument that you have given to printf is actually hi\n, which of course printf then performs its own escape sequence processing on.

In a double-quoted string, the escaping done through \ by the shell is specifically limited to affecting the ␊, \, `, $, and " characters. You will find that \n gets passed to printf as-is. So the argument that you have given to printf is actually hi\n again.

Be careful about putting escape sequences into the format string for printf. Only some have defined meanings in the Single Unix Specification. \n is defined, but \c is actually not, for example.

Further reading

  • https://unix.stackexchange.com/a/359510/5132
  • POSIX Shell: inside of double-quotes, are there cases where `\` fails to escape `$`, ```, `"`, `\` or `<newline>`?
  • Why is a single backslash shown when using quotes
  • Echo new line and string beginning \t
  • Why does dash expand \\\\ differently to bash?
  • https://unix.stackexchange.com/a/558665/5132

Within double quotes, \\n is an escaped (quoted) backslash followed by a n. This is given to printf as \n and printf will output a newline.

Within double quotes (still), \n is the string \n. Again, printf receives a \n string and will print a newline.

Within double quotes, backslash is special only when preceding another backslash, a newline, or any of $, ` or ". "Special" means that it removes the special meaning of the next character. If a backslash precedes any other character (n for example), then it's just a backslash character.

This is explained in the POSIX standard.

To print \n in a printf format string, use printf '\\n' or printf "\\\\n", or use printf '%s' '\n'

In general, the printf format string should be single quoted and any variable data should be given as additional arguments to be inserted into the format string:

printf 'This is how you write a newline: %s\n' '\n'