5

I have a variable x with trailing newline.

printf -v x 'hello\n'

How to find out if the last character of x is \n?

4
  • 2
    The echo will also have a line break, unless you use the -n flag.
    – annahri
    Jun 5, 2023 at 4:17
  • 1
    That is why i put the echo there. Jun 5, 2023 at 4:25
  • 1
    Neither of them have newlines at the end because command substitution eats trailing newlines.
    – muru
    Jun 5, 2023 at 5:21
  • 1
    bad news. Then x='hello\n' I need a variable with a trailing newline Jun 5, 2023 at 5:24

2 Answers 2

13

You describe the last character of the string, not characters, so you're looking for a way to detect if it's the single-character \n (aka newline or, in ASCII, line feed). You are not looking to detect if the string ends in the two characters \ (backslash) and n.

Here's one way to do this in fairly modern versions of Bash:

#!/usr/bin/env bash

printf -v x 'hello\n'

[[ "${x: -1:1}" == $'\n' ]] && echo "Ends in newline"

echo done

${x: -1:1} uses Bash's substring expansion to return the last character of the string. The syntax in the man page is ${parameter:offset:length} and notes that a negative offset starts from the end of the string rather than the start. It also notes that a space is needed between the first : and -1 to avoid confusion with a different type of parameter expansion.

$'\n' uses Bash's "special variant of single quotes" syntax to represent characters as specified by "the ANSI C standard."

The built-in printf command uses the -v option to write the output into the specified variable rather than to stdout. This option may not be available in all shells (and isn't available in an external printf binary). printf supports characters like \n directly without the "special variant" syntax.

As written, this will only work for newline at the end of a variable, but that's what you asked for. Checking for other control characters and/or other locations in the string will need different syntax that depends on your particular needs.

9

In any POSIX shell:

case "$string" in
  (*'
')    echo The string ends in a newline character;;
  (*) echo It does not;;
esac

In shells including bash that support the $'...' form of quote from ksh93 (and that will be specified for sh in the next version of the POSIX standard), that can be made easier to read with:

case "$string" in
  (*$'\n') echo The string ends in a newline character;;
       (*) echo It does not;;
esac

Other option for shells that don't support $'...' is to store the newline in a global variable:

NL='
'

or

eval "$(printf ' NL="\n" CR="\r" TAB="\t" FF="\f" BS="\b" BEL="\a" ')"

to make up a variable for each of those control characters and use:

case "$string" in
  (*"$NL") echo The string ends in a newline character;;
  (  *   ) echo It does not;;
esac

In a few shells (ksh93, zsh, bash, mksh at least), you can also do:

if [[ "$string" = *$'\n' ]]; then
  echo The string ends in a newline character
else
  echo It does not;;
fi

To get the last character of a parameter, POSIXly, you can abuse the pattern stripping operators:

if [ "${string#"${string%?}"}" = "$NL" ]; then
  echo The string ends in a newline character
else
  echo It does not;;
fi

In zsh, or yash, you'd do:

if [ "${string[-1]}" = "$NL" ]; then...

Or simplified in zsh:

if [[ $string[-1] = $'\n' ]]; then...

Because its arrays are sparse arrays, and that $var is short for ${var[0]} already and it supports multi-dimensional arrays, ksh93 introduced an awkward syntax for string or array slicing instead:

if [[ ${string:${#string}-1} = $'\n' ]]; then...

bash (which doesn't have multi-dimensional arrays but otherwise copied most of its array design from ksh) copied that in 2.0.

In bash, the offset can also be negative to count from the end (even the length now since 4.2). However as ${var:-default} is already something else from the Bourne shell, the first character after the : cannot be a - (nor +) in the arithmetic expression, so you need ${string: -1} or ${string:(-1)} or S{string:0-1}...

ksh93 copied that back in ksh93m, zsh also eventually added support for those ${var:offset[:length]} for compatibility with ksh (though with even more limitations as zsh supports the csh-style ${var:modifiers}), so did mksh.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .