24

I have a multiline variable, and I only want the first line in that variable. The following script demonstrates the issue:

#!/bin/bash

STRINGTEST="Onlygetthefirstline
butnotthesecond
orthethird"

echo "  Take the first line and send to standard output:"
echo ${STRINGTEST%%$'\n'*}
#   Output is as follows:
# Onlygetthefirstline

echo "  Set the value of the variable to the first line of the variable:"
STRINGTEST=${STRINGTEST%%$'\n'*}

echo "  Send the modified variable to standard output:"
echo $STRINGTEST
#   Output is as follows:
# Onlygetthefirstline butnotthesecond orthethird

Question: Why does ${STRINGTEST%%$'\n'*} return the first line when placed after an echo command, but replace newlines with spaces when placed after assignment?

4
  • 2
    Cannot reproduce it. It works for me as expected.
    – Peque
    Commented May 22, 2015 at 10:57
  • 1
    Can't reproduce with any of 2.05b, 3.1, 3.2, 4.0, 4.1, 4.2, 4.3 either. Sounds like a user error like trying to run it with a shell that doesn't support $'...' instead of bash. Commented May 22, 2015 at 11:12
  • Your solution in the Question section seems to works for me as a correct answer
    – pihentagy
    Commented Aug 3, 2020 at 18:42
  • The ${STRINGTEST%%$'\n'*} works on my system as intended, on assignment as well; my Bash version is 5.0.17.
    – Marcus
    Commented Mar 22, 2022 at 13:43

6 Answers 6

33

might not be most efficient but one liner...

firstLine=`echo "${multiLineVariable}" | head -1`
2
  • 2
    I like it for clarity and brevity. Commented Aug 29, 2017 at 9:36
  • 4
    This firstLine=`echo "${test_var}" | sed -n 1p also works if you have a reason to use sed instead (e.g., it means you can simultaneously perform a replacement to the line: echo "${test_var}" | sed -nE '1 s/# *(.*)/\1/p'. Commented May 28, 2019 at 17:36
12

Maybe there is other way to archive what you want to do, but this works

#!/bin/bash

STRINGTEST="
Onlygetthefirstline
butnotthesecond
orthethird
"

STRINGTEST=(${STRINGTEST[@]})
echo "${STRINGTEST[0]}"
6
  • That assumes the lines in $STRINGTEST don't contain blanks or wildcards. Also note that empty lines (as in the first line in that variable) are ignored. Commented May 22, 2015 at 11:10
  • 3
    Also note that using STRINGTEST=(${STRINGTEST[@]}) makes little sense and is equivalent to STRINGTEST=($STRINGTEST) since STRINGTEST was previously defined as a scalar (not array) variable. Commented May 22, 2015 at 13:15
  • No!!! It's funny you even quoted ${STRINGTEST[0]}
    – pihentagy
    Commented Jul 24, 2020 at 15:07
  • @pihentagy could you explain why is funny?
    – c4f4t0r
    Commented Jul 24, 2020 at 21:15
  • 2
    @c4f4t0r yep. In the previous line you have just splitted the string to WORDs. Try changing the first line of stringtest to hello world. The expression mentioned in the question itself (${STRINGTEST%%$'\n'*}) however seems to correctly solve the problem
    – pihentagy
    Commented Aug 5, 2020 at 13:01
8

That code works for me with all versions of bash I tried between 2.05b and 4.3. More likely you tried to run that script with a different shell that doesn't support the $'...' form of quoting.

That $'...' syntax is not standard sh syntax (yet) and only supported (as of 2015-05-22 and AFAIK) by ksh93 (where it originated), zsh, bash, recent versions of mksh and the sh or recent versions of FreeBSD.

My bet would be that you tried to run that script with sh instead of bash and your sh is based on versions of ash, pdksh, yash or ksh88 that don't support it yet.

If you want to make that code POSIX 2008 compatible, you'd need to write it:

STRINGTEST="Onlygetthefirstline
butnotthesecond
orthethird"

NL='
'
STRINGTEST=${STRINGTEST%%"$NL"*}
printf '%s\n' "$STRINGTEST"

Then, you can have it interpreted by any POSIX compliant shell like bash or any leaner/faster ones like your sh.

(and remember that leaving a variable unquoted in list context has a very special meaning in Bourne-like shells).

2
  • Or it could be an older version of bash. I could reproduce the behavior with 2.03. Commented May 22, 2015 at 21:43
  • @Gilles, the last supported OS that included bash-2.03 (released 16 years ago) was probably Solaris 8 which went EOL over 3 years ago. That hypothesis seems pretty unlikely. Commented May 22, 2015 at 23:00
1

This works for me:

STRINGTEST="Some Text 1
Some Text 2
Some Text 3"

readarray -t lines < <(echo "$STRINGTEST")
echo "${lines[0]}"

And it also works for blank lines:

STRINGTEST="
Some Text 1
Some Text 2
Some Text 3"

readarray -t lines < <(echo "$STRINGTEST")
echo "${lines[0]}"
1
  • If one bothers to fire up process substitution one could as well simply read once into a simple variable (instead of readarray plus indexing). Commented Aug 29, 2017 at 9:39
0

With Bash built-in read and here-string:

#!/usr/bin/env bash

STRINGTEST="
Some Text 1
Some Text 2
Some Text 3"


IFS=$'\n' read -r STRINGTEST <<<"$STRINGTEST"

Using POSIX parameters expansion:

#!/usr/bin/env sh

STRINGTEST="
Some Text 1
Some Text 2
Some Text 3"

# Disables globing
set -f

# Field separator is newline only
IFS="
"

# No quotes, split lines as arguments because of IFS
set -- $STRINGTEST

# First argument is first line
STRINGTEST="$1"
0

Hey i have found this post with the same problem , but my solution was using the head commnand.

Like as an example:

vfail2ban=$(fail2ban-server --version |head -1)
root@vm:~# echo ${vfail2ban}
Fail2Ban v0.10.2

You must log in to answer this question.

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