204

How can i split my long string constant over multiple lines?

I realize that you can do this:

echo "continuation \
lines"
>continuation lines

However, if you have indented code, it doesn't work out so well:

    echo "continuation \
    lines"
>continuation     lines
  • 8
    Google's Bash Shell Style Guide [recommends "here" documents](https://google.github.io/styleguide/shell.xml#Line_Length_and_Long_Strings) for "If you have to write strings that are longer than 80 characters". See [@tripleee's answer](http://stackoverflow.com/questions/7316107/bash-continuation-lines/7316685#7316685). – Trevor Boyd Smith Mar 11 '16 at 12:58
  • 1
    https://google.github.io/styleguide/shellguide.html#line-length-and-long-strings -- this is the direct link in the new document – jcollum Mar 26 '20 at 18:47
  • 1
    The answers clearly demonstrate how fun the bash language is :-) – matanster Dec 10 '20 at 10:18

12 Answers12

205

This is what you may want

$       echo "continuation"\
>       "lines"
continuation lines

If this creates two arguments to echo and you only want one, then let's look at string concatenation. In bash, placing two strings next to each other concatenate:

$ echo "continuation""lines"
continuationlines

So a continuation line without an indent is one way to break up a string:

$ echo "continuation"\
> "lines"
continuationlines

But when an indent is used:

$       echo "continuation"\
>       "lines"
continuation lines

You get two arguments because this is no longer a concatenation.

If you would like a single string which crosses lines, while indenting but not getting all those spaces, one approach you can try is to ditch the continuation line and use variables:

$ a="continuation"
$ b="lines"
$ echo $a$b
continuationlines

This will allow you to have cleanly indented code at the expense of additional variables. If you make the variables local it should not be too bad.

Ray Toal
  • 86,166
  • 18
  • 182
  • 232
  • 1
    Thanks for your help, but while this does remove the spaces, they are now separate parameters (Bash is interpreting the spaces on the second line as a parameter separator) and are now only printed correctly because of the echo command. –  Sep 06 '11 at 07:19
  • 7
    Solution with one variable: `s="string no. 1" s+="string no. 2" s+=" string no. 3" echo "$s"` – Johnny Thunderman Apr 15 '16 at 09:11
40

Here documents with the <<-HERE terminator work well for indented multi-line text strings. It will remove any leading tabs from the here document. (Line terminators will still remain, though.)

cat <<-____HERE
    continuation
    lines
____HERE

See also http://ss64.com/bash/syntax-here.html

If you need to preserve some, but not all, leading whitespace, you might use something like

sed 's/^  //' <<____HERE
    This has four leading spaces.
    Two of them will be removed by sed.
____HERE

or maybe use tr to get rid of newlines:

tr -d '\012' <<-____
    continuation
     lines
____

(The second line has a tab and a space up front; the tab will be removed by the dash operator before the heredoc terminator, whereas the space will be preserved.)

For wrapping long complex strings over many lines, I like printf:

printf '%s' \
    "This will all be printed on a " \
    "single line (because the format string " \
    "doesn't specify any newline)"

It also works well in contexts where you want to embed nontrivial pieces of shell script in another language where the host language's syntax won't let you use a here document, such as in a Makefile or Dockerfile.

printf '%s\n' >./myscript \
    '#!/bin/sh` \
    "echo \"G'day, World\"" \
    'date +%F\ %T' && \
chmod a+x ./myscript && \
./myscript
tripleee
  • 175,061
  • 34
  • 275
  • 318
  • 1
    Doesn't work for me. Ubuntu 16.04. I get two lines instead of expected concatenated one line. – Penghe Geng Jul 20 '18 at 15:40
  • 1
    @PengheGeng Indeed, this solves the problem of getting rid of indentation, not the one of joining lines together. You can still backslash the newline at the end of a line to join two lines together. – tripleee Jul 20 '18 at 15:43
  • (But see the first `printf` example also now.) – tripleee Aug 28 '19 at 03:24
  • For anyone seeing this in the future, this other answer to a related question about splitting multi-line strings at a certain number of characters https://stackoverflow.com/a/49261098/1585486. It also recommends here document syntax and it might reinforce its use to address this need in bash. I recommend testing for each command using them separately (v.g. echo, cat, printf) – Pablo Adames Aug 12 '23 at 01:32
17

You can use bash arrays

$ str_array=("continuation"
             "lines")

then

$ echo "${str_array[*]}"
continuation lines

there is an extra space, because (after bash manual):

If the word is double-quoted, ${name[*]} expands to a single word with the value of each array member separated by the first character of the IFS variable

So set IFS='' to get rid of extra space

$ IFS=''
$ echo "${str_array[*]}"
continuationlines
tworec
  • 4,409
  • 2
  • 29
  • 34
  • 1
    You should probably use `"${str_array[@]}"`, though. – tripleee May 02 '21 at 10:55
  • 1
    Good catch! Thx. For `echo` it does not matter, but for other cases it might make huge difference https://stackoverflow.com/questions/3348443/a-confusion-about-array-versus-array-in-the-context-of-a-bash-comple – tworec May 04 '21 at 12:54
11

In certain scenarios utilizing Bash's concatenation ability might be appropriate.

Example:

temp='this string is very long '
temp+='so I will separate it onto multiple lines'
echo $temp
this string is very long so I will separate it onto multiple lines

From the PARAMETERS section of the Bash Man page:

name=[value]...

...In the context where an assignment statement is assigning a value to a shell variable or array index, the += operator can be used to append to or add to the variable's previous value. When += is applied to a variable for which the integer attribute has been set, value is evaluated as an arithmetic expression and added to the variable's current value, which is also evaluated. When += is applied to an array variable using compound assignment (see Arrays below), the variable's value is not unset (as it is when using =), and new values are appended to the array beginning at one greater than the array's maximum index (for indexed arrays) or added as additional key-value pairs in an associative array. When applied to a string-valued variable, value is expanded and appended to the variable's value.

Cybernaut
  • 127
  • 2
  • 9
  • Probably the best advice on this page. Using a HEREDOC and piping into a translate for s is super unintuitive and fails when the string actually needs to have discretely placed line separators. – ingyhere Jan 13 '20 at 06:58
2

I came across a situation in which I had to send a long message as part of a command argument and had to adhere to the line length limitation. The commands looks something like this:

somecommand --message="I am a long message" args

The way I solved this is to move the message out as a here document (like @tripleee suggested). But a here document becomes a stdin, so it needs to be read back in, I went with the below approach:

message=$(
    tr "\n" " " <<-END
        This is a
        long message
END
)
somecommand --message="$message" args

This has the advantage that $message can be used exactly as the string constant with no extra whitespace or line breaks.

Note that the actual message lines above are prefixed with a tab character each, which is stripped by here document itself (because of the use of <<-). There are still line breaks at the end, which are then replaced by tr with spaces.

Note also that if you don't remove newlines, they will appear as is when "$message" is expanded. In some cases, you may be able to workaround by removing the double-quotes around $message, but the message will no longer be a single argument.

MestreLion
  • 12,698
  • 8
  • 66
  • 57
haridsv
  • 9,065
  • 4
  • 62
  • 65
2

You could simply separate it with newlines (without using backslash) as required within the indentation as follows and just strip of new lines.

Example:

echo "continuation
of 
lines" | tr '\n' ' '

Or if it is a variable definition newlines gets automatically converted to spaces. So, strip of extra spaces only if applicable.

x="continuation
of multiple
lines"
y="red|blue|
green|yellow"

echo $x # This will do as the converted space actually is meaningful
echo $y | tr -d ' ' # Stripping of space may be preferable in this case
rjni
  • 471
  • 7
  • 7
2

This isn't exactly what the user asked, but another way to create a long string that spans multiple lines is by incrementally building it up, like so:

$ greeting="Hello"
$ greeting="$greeting, World"
$ echo $greeting
Hello, World

Obviously in this case it would have been simpler to build it one go, but this style can be very lightweight and understandable when dealing with longer strings.

Jon McClung
  • 1,619
  • 20
  • 28
2

Line continuations also can be achieved through clever use of syntax.

In the case of echo:

# echo '-n' flag prevents trailing <CR> 
echo -n "This is my one-line statement" ;
echo -n " that I would like to make."
This is my one-line statement that I would like to make.

In the case of vars:

outp="This is my one-line statement" ; 
outp+=" that I would like to make." ; 
echo -n "${outp}"
This is my one-line statement that I would like to make.

Another approach in the case of vars:

outp="This is my one-line statement" ; 
outp="${outp} that I would like to make." ; 
echo -n "${outp}"
This is my one-line statement that I would like to make.

Voila!

ingyhere
  • 11,818
  • 3
  • 38
  • 52
  • `echo -n` is dubious; you should use `printf` instead. – tripleee May 02 '21 at 10:47
  • @tripleee How exactly is using a [GNU coreutil](https://www.gnu.org/software/coreutils/manual/html_node/echo-invocation.html#echo-invocation) dubious, praytell? In the case of this simple case, it does exactly what's expected, even allowing the terminal to properly wrap the output. Even the [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html) uses it similarly in simple cases (advice for HEREDOC notwithstanding). This is not a solution for special formatting. – ingyhere May 03 '21 at 16:28
  • The problem with `echo -n` is that it doesn't *reliably* work the way you describe. A number of non-GNU platforms have different `echo` implementations with different features. As long as you are using the Bash built-in, it's merely a bad smell, though; but why would you prefer to make your solutions less portable when the portable and reliable solution is actually more elegant, too? – tripleee May 03 '21 at 16:37
  • I could not find any examples of `echo -n` in the Google style guide. – tripleee May 03 '21 at 16:41
  • @tripleee One problem is `printf` has its own fairly sizable portability concerns. It's a very fine-toothed comb in 2021 to complain about the `-n` flag which removes the trailing 's in `echo`. I'd even argue it offers more control instead of spitting out whatever line ending. Personally, I'd prefer `echo -ne "${outp}"` which forces interpretation of escape chars, also. Recommendation for strict compatibility over decades is to use `cat` with a HEREDOC. But really, the original poster is asking about using `echo` and the question is tagged with `Bash`. – ingyhere May 04 '21 at 03:46
1

Depending on what sort of risks you will accept and how well you know and trust the data, you can use simplistic variable interpolation.

$: x="
    this
    is
       variably indented
    stuff
   "
$: echo "$x" # preserves the newlines and spacing

    this
    is
       variably indented
    stuff

$: echo $x # no quotes, stacks it "neatly" with minimal spacing
this is variably indented stuff
Paul Hodges
  • 13,382
  • 1
  • 17
  • 36
  • So then just do `MY_VAR = $(echo x$)` and there you have it. IMO this is was best, easiest answer to keep my code readable each line being a variable holding a string itself. I need to export this value and you can't export arrays. – DKebler Nov 15 '20 at 22:59
  • @DKebler Your comment contains multiple syntax errors. It's hard to guess what you mean, or take the surrounding advice too seriously. See also [useless use of `echo`.](http://www.iki.fi/era/unix/award.html#echo) – tripleee May 02 '21 at 10:50
1

Following @tripleee 's printf example (+1):

LONG_STRING=$( printf '%s' \
    'This is the string that never ends.' \
    ' Yes, it goes on and on, my friends.' \
    ' My brother started typing it not knowing what it was;' \
    " and he'll continue typing it forever just because..." \
    ' (REPEAT)' )

echo $LONG_STRING

This is the string that never ends. Yes, it goes on and on, my friends. My brother started typing it not knowing what it was; and he'll continue typing it forever just because... (REPEAT)

And we have included explicit spaces between the sentences, e.g. "' Yes...". Also, if we can do without the variable:

echo "$( printf '%s' \
    'This is the string that never ends.' \
    ' Yes, it goes on and on, my friends.' \
    ' My brother started typing it not knowing what it was;' \
    " and he'll continue typing it forever just because..." \
    ' (REPEAT)' )"

This is the string that never ends. Yes, it goes on and on, my friends. My brother started typing it not knowing what it was; and he'll continue typing it forever just because... (REPEAT)

Acknowledgement for the song that never ends

Ana Nimbus
  • 635
  • 3
  • 16
0

However, if you have indented code, it doesn't work out so well:

    echo "continuation \
    lines"
>continuation     lines

Try with single quotes and concatenating the strings:

    echo 'continuation' \
    'lines'
>continuation lines

Note: the concatenation includes a whitespace.

mMontu
  • 8,983
  • 4
  • 38
  • 53
  • 2
    It works with echo and string arguments, but it doesn't work with other things, like variable assignment. Though the question wasn't about variables, using echo was only an example. Instead of `echo ` if you had `x=`, you'd get the error: `lines: command not found`. – Mr. Lance E Sloan Aug 05 '16 at 15:39
  • 1
    @Mr.LanceESloan That's because of the unquoted space, though, so it's really a [separate issue.](https://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-shell-variable) If you remove the space before the backslash, this works with assigments, too. – tripleee May 04 '21 at 13:05
-2

This probably doesn't really answer your question but you might find it useful anyway.

The first command creates the script that's displayed by the second command.

The third command makes that script executable.

The fourth command provides a usage example.

john@malkovich:~/tmp/so$ echo $'#!/usr/bin/env python\nimport textwrap, sys\n\ndef bash_dedent(text):\n    """Dedent all but the first line in the passed `text`."""\n    try:\n        first, rest = text.split("\\n", 1)\n        return "\\n".join([first, textwrap.dedent(rest)])\n    except ValueError:\n        return text  # single-line string\n\nprint bash_dedent(sys.argv[1])'  > bash_dedent
john@malkovich:~/tmp/so$ cat bash_dedent 
#!/usr/bin/env python
import textwrap, sys

def bash_dedent(text):
    """Dedent all but the first line in the passed `text`."""
    try:
        first, rest = text.split("\n", 1)
        return "\n".join([first, textwrap.dedent(rest)])
    except ValueError:
        return text  # single-line string

print bash_dedent(sys.argv[1])
john@malkovich:~/tmp/so$ chmod a+x bash_dedent
john@malkovich:~/tmp/so$ echo "$(./bash_dedent "first line
>     second line
>     third line")"
first line
second line
third line

Note that if you really want to use this script, it makes more sense to move the executable script into ~/bin so that it will be in your path.

Check the python reference for details on how textwrap.dedent works.

If the usage of $'...' or "$(...)" is confusing to you, ask another question (one per construct) if there's not already one up. It might be nice to provide a link to the question you find/ask so that other people will have a linked reference.

intuited
  • 23,174
  • 7
  • 66
  • 88
  • 6
    Well-intentioned- and possibly even useful- though this may be, the OP asked for advice on basic bash syntax, and you gave him a python function definition which uses OO paradigms, exceptional flow control, and imports. Further, you called an executable as part of a string interpolation- something that a person asking this kind of question would definitely not have seen yet in bash. – Parthian Shot Jul 21 '14 at 21:10