Test if variable contains newline (POSIX)

You can put a hard newline in a variable, and pattern match with case.

$ cat nl.sh
#!/bin/sh
nl='
'
case "$1" in
    *$nl*)  echo has newline ;;
    *)      echo no newline  ;;
esac

$ dash nl.sh $'foo\nbar'
has newline
$ dash nl.sh $'foobar'
no newline

The alternative way to make the newline is something like this:

nl=$(printf "\nx"); nl=${nl%x}

The obvious command substitution doesn't work because trailing newlines are removed by the substitution.


Yes,

nl='
'
case $var in
  (*"$nl"*) echo yes;;
  (*)       echo no;;
esac

(by principle, I like to quote all variable expansions inside case pattern unless I want them to be treated as a pattern, though here it doesn't make a difference as $nl does not contain wildcards).

or

case $var in
  (*'
'*) echo yes;;
  (*) echo no;;
esac

Should all work and are POSIX compliant, and what I would use for that. If you remove the (s, it would even work in the old Bourne shell.

For another way to set the $nl variable:

eval "$(printf 'nl="\n"')"

Note that $'\n' is planned for inclusion in the next version of the POSIX standard. It's already supported by ksh93, zsh, bash, mksh, busybox and FreeBSD sh at least (as of February 2018).

As for whether the test you have is enough, you've got a test for both cases so would be testing all the code paths.

There's currently something that is not clearly specified in the POSIX spec: whether * matches on a string that contains byte sequences that don't form valid characters or whether shell variables may contain those strings.

In practice, apart from yash whose variables can only contain characters, and apart from the NUL bytes (which no shell but zsh can store in their variables), *$nl* should match on any string that contains $nl even if they contain sequences of bytes that don't form valid characters (like $'\x80' in UTF-8).

Some find implementations for instance would fail to match with -name "*$nl*" on them, so if testing a new shell, and if you intend to deal with things that are not guaranteed to be text (like filenames), you may want to add a test case for it. Like with with:

test=$(printf '\200\n\200')

in a UTF-8 locale.