How to output a multiline string in Bash?

Bash

Bash Problem Overview


How can I output a multipline string in Bash without using multiple echo calls like so:

echo "usage: up [--level <n>| -n <levels>][--help][--version]"
echo 
echo "Report bugs to: "
echo "up home page: "

I'm looking for a portable way to do this, using only Bash builtins.

Bash Solutions


Solution 1 - Bash

Here documents are often used for this purpose.

cat << EOF
usage: up [--level <n>| -n <levels>][--help][--version]

Report bugs to: 
up home page:
EOF

They are supported in all Bourne-derived shells including all versions of Bash.

Solution 2 - Bash

or you can do this:

echo "usage: up [--level <n>| -n <levels>][--help][--version]
 
Report bugs to: 
up home page: "

Solution 3 - Bash

Inspired by the insightful answers on this page, I created a mixed approach, which I consider the simplest and more flexible one. What do you think?

First, I define the usage in a variable, which allows me to reuse it in different contexts. The format is very simple, almost WYSIWYG, without the need to add any control characters. This seems reasonably portable to me (I ran it on MacOS and Ubuntu)

__usage="
Usage: $(basename $0) [OPTIONS]

Options:
  -l, --level <n>              Something something something level
  -n, --nnnnn <levels>         Something something something n
  -h, --help                   Something something something help
  -v, --version                Something something something version
"

Then I can simply use it as

echo "$__usage"

or even better, when parsing parameters, I can just echo it there in a one-liner:

levelN=${2:?"--level: n is required!""${__usage}"}

Solution 4 - Bash

Use -e option, then you can print new line character with \n in the string.

Sample (but not sure whether a good one or not)

The fun thing is that -e option is not documented in MacOS's man page while still usable. It is documented in the man page of Linux.

Solution 5 - Bash

Since I recommended printf in a comment, I should probably give some examples of its usage (although for printing a usage message, I'd be more likely to use Dennis' or Chris' answers). printf is a bit more complex to use than echo. Its first argument is a format string, in which escapes (like \n) are always interpreted; it can also contain format directives starting with %, which control where and how any additional arguments are included in it. Here are two different approaches to using it for a usage message:

First, you could include the entire message in the format string:

printf "usage: up [--level <n>| -n <levels>][--help][--version]\n\nReport bugs to: \nup home page: \n"

Note that unlike echo, you must include the final newline explicitly. Also, if the message happens to contain any % characters, they would have to be written as %%. If you wanted to include the bugreport and homepage addresses, they can be added quite naturally:

printf "usage: up [--level <n>| -n <levels>][--help][--version]\n\nReport bugs to: %s\nup home page: %s\n" "$bugreport" "$homepage"

Second, you could just use the format string to make it print each additional argument on a separate line:

printf "%s\n" "usage: up [--level <n>| -n <levels>][--help][--version]" "" "Report bugs to: " "up home page: "

With this option, adding the bugreport and homepage addresses is fairly obvious:

printf "%s\n" "usage: up [--level <n>| -n <levels>][--help][--version]" "" "Report bugs to: $bugreport" "up home page: $homepage"

Solution 6 - Bash

Also with indented source code you can use <<- (with a trailing dash) to ignore leading tabs (but not leading spaces).

For example this:

if [ some test ]; then
    cat <<- xx
        line1
        line2
xx
fi

Outputs indented text without the leading whitespace:

line1
line2

Solution 7 - Bash

I usually go with the builtin read command which I think is more flexible and intuitive. It reads the contents of a line into a variable, and allows for word splitting that is tied to the special shell variable IFS. Refer to this blog or even the man page for more details.

read -r -d '' usage <<-EOF
	usage: up [--level <n>| -n <levels>][--help][--version] 

	Report bugs to: $report server
	up home page: $HOME
EOF
echo "$usage"

Solution 8 - Bash

Use the -e argument and the escape character \n:

echo -e "This will generate a next line \nThis new line is the result"

Solution 9 - Bash

One more thing, using printf with predefined variable (here: msg) as template.

msg="First line %s
Second line %s
Third line %s
"

one='additional message for the first line'
two='2'
tri='this is the last one'

printf "$msg" "$one" "$two" "$tri"

This ^^^ will print whole message with additional vars inserted instead of %s in provided order.

Solution 10 - Bash

    You can write your
    text
        freely,
                   in a separate: 
                             ----file.

and then

echo "$(</pathto/your_multiline_file)"

Solution 11 - Bash

Do this:

dedent() {
    local -n reference="$1"
    reference="$(echo "$reference" | sed 's/^[[:space:]]*//')"
}

text="this is line one
      this is line two
      this is line three\n"

# `text` is passed by reference and gets dedented
dedent text

printf "$text"

Output withOUT calling dedent first:

this is line one
      this is line two
      this is line three

...and WITH calling dedent first (as shown above):

this is line one
this is line two
this is line three

For a full explanation, see where I've already written about this:

  1. Equivalent of python's textwrap dedent in bash
  2. Multi-line string with extra space (preserved indentation)

And of course, thanks to @Andreas Louv for showing me the sed part of that function here.

Solution 12 - Bash

Here is how I've done:

function help_text {
  printf "\n\
Usage: ./cpanel-to-cc.sh [arguments] ... \n\
Examples: \n\
\t ./cpanel-to-cc.sh --client-id 123123 --api-key abc123def456 --domain example.com \n\
\t ./cpanel-to-cc.sh --client-id 123123 --tmp-dir /home/user/cpanel-to-cc \n\
\t ./cpanel-to-cc.sh --resync --domain example.com \n\
\t ./cpanel-to-cc.sh --purge \n\
\n\
Arguments: \n\
Option \t\t\t Long option \t\t\t Function \n\
 -c <id> \t\t --client-id <id> \t\t Specify the SiteHost Client ID \n\
 -k <key> \t\t --api-key <key> \t\t Specify the SiteHost API key with access to Cloud, Job and Server modules \n\
 -d <domain> \t\t --domain <domain> \t\t The cPanel domain to migrate. If not specified we try migrate all \n\
 -t <directory> \t --tmp-dir <directory> \t\t Directory to store temporary files and logs. Default is: $TMP_DIR \n\
 -v \t\t\t --verbose \t\t\t Print debugging/verbose information \n\
 -y \t\t\t --assume-yes \t\t\t Automatic yes to prompts. Assume \"yes\" as answer to all prompts \n\
 -r \t\t\t --resync \t\t\t Use credentials stored and copy data into Container already created. \n\
 -p \t\t\t --purge \t\t\t Remove any metadata stored on the the server. This removes any files in: $TMP_DIR \n\
 -h \t\t\t --help \t\t\t Display this help and exit \n\
 \n"
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionhelpermethodView Question on Stackoverflow
Solution 1 - BashDennis WilliamsonView Answer on Stackoverflow
Solution 2 - BashChris MohrView Answer on Stackoverflow
Solution 3 - BashJorgeView Answer on Stackoverflow
Solution 4 - BashnhahtdhView Answer on Stackoverflow
Solution 5 - BashGordon DavissonView Answer on Stackoverflow
Solution 6 - BashElliptical viewView Answer on Stackoverflow
Solution 7 - BashZstackView Answer on Stackoverflow
Solution 8 - BashvictorkolisView Answer on Stackoverflow
Solution 9 - BashIvanView Answer on Stackoverflow
Solution 10 - Bashassayag.orgView Answer on Stackoverflow
Solution 11 - BashGabriel StaplesView Answer on Stackoverflow
Solution 12 - BashAndré Campos RodovalhoView Answer on Stackoverflow