[string] Multi-line string with extra space (preserved indentation)

I want to write some pre-defined texts to a file with the following:

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

echo -e $text > filename

I'm expecting something like this:

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

But got this:

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

I'm positive that there is no space after each \n, but how does the extra space come out?

This question is related to string bash shell echo

The answer is


it will work if you put it as below:

AA='first line
\nsecond line 
\nthird line'
echo $AA
output:
first line
second line
third line

I came hear looking for this answer but also wanted to pipe it to another command. The given answer is correct but if anyone wants to pipe it, you need to pipe it before the multi-line string like this

echo | tee /tmp/pipetest << EndOfMessage
This is line 1.
This is line 2.
Line 3.
EndOfMessage

This will allow you to have a multi line string but also put it in the stdin of a subsequent command.


echo adds spaces between the arguments passed to it. $text is subject to variable expansion and word splitting, so your echo command is equivalent to:

echo -e "this" "is" "line" "one\n" "this" "is" "line" "two\n"  ...

You can see that a space will be added before "this". You can either remove the newline characters, and quote $text to preserve the newlines:

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

echo "$text" > filename

Or you could use printf, which is more robust and portable than echo:

printf "%s\n" "this is line one" "this is line two" "this is line three" > filename

In bash, which supports brace expansion, you could even do:

printf "%s\n" "this is line "{one,two,three} > filename

If you're trying to get the string into a variable, another easy way is something like this:

USAGE=$(cat <<-END
    This is line one.
    This is line two.
    This is line three.
END
)

If you indent your string with tabs (i.e., '\t'), the indentation will be stripped out. If you indent with spaces, the indentation will be left in.

NOTE: It is significant that the last closing parenthesis is on another line. The END text must appear on a line by itself.


Just to mention a simple one-line concatenation as it can be useful sometimes.

# for bash

v=" guga "$'\n'"   puga "

# Just for an example.
v2="bar "$'\n'"   foo "$'\n'"$v"

# Let's simplify the previous version of $v2.
n=$'\n'
v3="bar ${n}   foo ${n}$v"

echo "$v3" 

You'll get something like this

bar 
   foo 
 guga 
   puga 

All leading and ending white spaces will be preserved right for

echo "$v3" > filename

There are many ways to do it. For me, piping the indented string into sed works nicely.

printf_strip_indent() {
   printf "%s" "$1" | sed "s/^\s*//g" 
}

printf_strip_indent "this is line one
this is line two
this is line three" > "file.txt"

This answer was based on Mateusz Piotrowski's answer but refined a bit.


I've found more solutions since I wanted to have every line properly indented:

  1. You may use echo:

    echo    "this is line one"   \
        "\n""this is line two"   \
        "\n""this is line three" \
        > filename
    

    It does not work if you put "\n" just before \ on the end of a line.

  2. Alternatively, you can use printf for better portability (I happened to have a lot of problems with echo):

    printf '%s\n' \
        "this is line one"   \
        "this is line two"   \
        "this is line three" \
        > filename
    
  3. Yet another solution might be:

    text=''
    text="${text}this is line one\n"
    text="${text}this is line two\n"
    text="${text}this is line three\n"
    printf "%b" "$text" > filename
    

    or

    text=''
    text+="this is line one\n"
    text+="this is line two\n"
    text+="this is line three\n"
    printf "%b" "$text" > filename
    
  4. Another solution is achieved by mixing printf and sed.

    if something
    then
        printf '%s' '
        this is line one
        this is line two
        this is line three
        ' | sed '1d;$d;s/^    //g'
    fi
    

    It is not easy to refactor code formatted like this as you hardcode the indentation level into the code.

  5. It is possible to use a helper function and some variable substitution tricks:

    unset text
    _() { text="${text}${text+
    }${*}"; }
    # That's an empty line which demonstrates the reasoning behind 
    # the usage of "+" instead of ":+" in the variable substitution 
    # above.
    _ ""
    _ "this is line one"
    _ "this is line two"
    _ "this is line three"
    unset -f _
    printf '%s' "$text"
    

in a bash script the following works:

#!/bin/sh

text="this is line one\nthis is line two\nthis is line three"
echo -e $text > filename

alternatively:

text="this is line one
this is line two
this is line three"
echo "$text" > filename

cat filename gives:

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

The following is my preferred way to assign a multi-line string to a variable (I think it looks nice).

read -r -d '' my_variable << \
_______________________________________________________________________________

String1
String2
String3
...
StringN
_______________________________________________________________________________

The number of underscores is the same (here 80) in both cases.


Examples related to string

How to split a string in two and store it in a field String method cannot be found in a main class method Kotlin - How to correctly concatenate a String Replacing a character from a certain index Remove quotes from String in Python Detect whether a Python string is a number or a letter How does String substring work in Swift How does String.Index work in Swift swift 3.0 Data to String? How to parse JSON string in Typescript

Examples related to bash

Comparing a variable with a string python not working when redirecting from bash script Zipping a file in bash fails How do I prevent Conda from activating the base environment by default? Get first line of a shell command's output Fixing a systemd service 203/EXEC failure (no such file or directory) /bin/sh: apt-get: not found VSCode Change Default Terminal Run bash command on jenkins pipeline How to check if the docker engine and a docker container are running? How to switch Python versions in Terminal?

Examples related to shell

Comparing a variable with a string python not working when redirecting from bash script Get first line of a shell command's output How to run shell script file using nodejs? Run bash command on jenkins pipeline Way to create multiline comments in Bash? How to do multiline shell script in Ansible How to check if a file exists in a shell script How to check if an environment variable exists and get its value? Curl to return http status code along with the response docker entrypoint running bash script gets "permission denied"

Examples related to echo

Multi-line string with extra space (preserved indentation) Bash: Echoing a echo command with a variable in bash Echo a blank (empty) line to the console from a Windows batch file Add php variable inside echo statement as href link address? How to format font style and color in echo How do I make a Windows batch script completely silent? Is there a php echo/print equivalent in javascript How to use css style in php How can I align the columns of tables in Bash? Double quotes within php script echo