Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

The issue:

  1. I need to assign a variable a value that is decently long.
  2. All the lines of my script must be under a certain number of columns.

So, I am trying to assign it using more than one line.

It's simple to do without indents:

VAR="This displays without \
any issues."
echo "${VAR}"

Result:

This displays without any issues.

However with indents:

    VAR="This displays with \
    extra spaces."
    echo "${VAR}"

Result:

This displays with      extra spaces.

How can I elegantly assign it without these spaces? Thanks!

share|improve this question

8 Answers 8

up vote 2 down vote accepted

Here the issue is that you are surrounding the variable with double quotes (""). Remove it and things will work fine.

    VAR="This displays with \
    extra spaces."
    echo ${VAR}

Output

 This displays with extra spaces.

Here the issue is that double quoting a variable preserves all white space characters. This can be used in case if you explicitly need it.

For example,

$ echo "Hello     World    ........ ...            ...."

will print

Hello     World    ........ ...            ....

And on removing quotes, its different

$ echo Hello     World    ........ ...            ....
Hello World ........ ... ....

Here the Bash removes extra spaces in the text because in the first case the entire text is taken as a "single" argument and thus preserving extra spaces. But in the second case echo command receives the text as 5 arguments.

Quoting a variable will also be helpful while passing arguments to commands.

In the below command, echo only gets single argument as "Hello World"

$ variable="Hello World"
$ echo "$variable"

But in case of the below scenario echo gets two arguments as Hello and World

$ variable="Hello World"
$ echo $variable
share|improve this answer
    
Most answers worked well, but this was the simplest. Thanks! –  Sman865 Oct 24 '14 at 19:05
    
@Sman865 - please believe me when I tell you that this is actually almost definitely the most complicated answer offered here, and it is also wrong in most respects - especially in its opening declaration. Any issue surrounding value assignment cannot be in any way related to its later expansion - that's just backwards. I'm sorry Kannan, but this answer is both wrong and wrong-headed. Field-splitting expansions on $IFS is a powerful and universal tool - but code written in this way can never produce reliable results of any kind. –  mikeserv Oct 25 '14 at 11:32
    
Failing to use quotes when expanding a variable causes problems sooner or later, starting with filename expansion (any of * ? []). –  mr.spuratic Oct 27 '14 at 11:09
    
I agree that variables need to be enclosed inside a double quotes to prevent it from bash expansion. But for special cases we can avoid it to prevent complication of code. –  Kannan Mohan Oct 27 '14 at 14:37

Let the shell eat the unwanted linefeeds and following spaces:

$ cat weird.sh 
#!/bin/sh

        var1="A weird(?) $(
             )multi line $(
             )text idea. $(
             )PID=$$"

        var2='You can '$(
            )'avoid expansion '$(
            )'too: PID=$$'

        var3='Or mix it: '$(
            )'To insert the PID use $$. '$(
            )"It expands to e.g. $$."

        echo "$var1"
        echo "$var2"
        echo "$var3"
$ sh weird.sh 
A weird(?) multi line text idea. PID=13960
You can avoid expansion too: PID=$$
Or mix it: To insert the PID use $$. It expands to e.g. 13960.

So it is possible... but sure it is a matter of taste to like or dislike this solution...

share|improve this answer
    
every single one of these is a fork. –  mikeserv Oct 25 '14 at 8:34

Maybe you can try this.

          echo "Test" \
               "Test2" \
               "Test3"
share|improve this answer

The solutions given by esuoxu and Mickaël Bucas are the common and more portable ways of doing this.

Here are a few bash solutions (some of which should also work in other shells, like zsh). Firstly with the += append operator (which works in a slightly different way for each of an integer variable, a regular variable and an array).

text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
text+="tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
text+="quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ..." 

If you want newlines (or other whitespace) in the text, use instead $'' quoting:

text=$'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\n'
text+=$'...'

Next, using printf -v to assign a formatted value to a variable

printf -v text "%s" "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed " \
                    "do eiusmod empor incididunt ut labore et dolore magna aliqua. "\
                    "Ut enim ad minim veniam ..."

The trick here is that there are more arguments than format specifiers, so unlike most printf functions, the bash one reuses the format string until it runs out. You can put a \n within the format string, or use $'', (or both) to deal with whitespace.

Next, using an array:

text=("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod "
      "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
      "quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ..." )

You can also use += to build the text up line by line. Here though, you must remember to "flatten" the array if you want the entire text content in one go

echo "$text"      # only outputs index [0], the first line
echo "${text[*]}" # output complete text (joined by first character of IFS)

(integer indexed arrays are implicitly sorted, unlike associative arrays) This gives you slightly more flexibility since you can manipulate lines if needed.

Finally, using read or readarray and a "here-document":

read -r -d '' text <<-"EOT"
        Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 
        quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea ...
EOT

readarray -t textarray <<-"EOT"
        Lorem [...]
EOT  

The here-document form of <<- means all leading hard tabs are removed from the input, so you must use tabs to indent your text. Quotes around "EOT" prevent shell expansion features, so the input is used verbatim. With read it uses NUL byte delimited input, so that it will read the newline delimited text in one go. With readarray (aka mapfile, available since bash-4.0) it read into an array, and -t strips newlines on each line.

share|improve this answer

There is a special heredoc syntax that removes tabs at the beginning of all lines : "<<-" (notice the dash added)

http://tldp.org/LDP/abs/html/here-docs.html

Example 19-4. Multi-line message, with tabs suppressed

You can use it like this :

v="$(cat <<-EOF
    A
        B
    C
EOF
)"
echo "$v"

Result :

A
B
C

It works only with tabs, not spaces.

share|improve this answer

You may want to try :

echo $VAR | tr -s " "

or

myArr=($VAL)
VAL=${myArr[@]}
echo "$VAL"

and also you can check this out.

share|improve this answer

Use a Bash Substitution Expansion

If you're using Bash, you can use a substitution expansion. For example:

$ echo "${VAR//  /}"
This displays with extra spaces.
share|improve this answer

This how I suggest you should do it, and I will explain why, but first I want to talk about something else...

set -- 'Arg 1: Line 1.' \
       'Arg 2: Line 2.' \
       'and so on for'  \
       'as long as you might like.'
var="$*"

A lot of the other proffered solutions here seem to suggest that you can somehow affect a shell variable's contents by altering your methods of expanding it. I can assure you this is not the case.

    string="some stuff here \
            some more stuff here."
    echo $string ${#string} 
    echo "$string" "${#string}"

OUTPUT

some stuff here some more stuff here. 53
some stuff here                 some more stuff here. 53

What you see above is first a field-split expansion, then a report on the byte-count for the expansion's source variable, then a quote-delimited expansion, and the same byte-count. While the output may differ the contents of the shell variable $string never changes at all except upon assignment.

What's more, if you do not understand why this is, you're bound to encounter some very nasty surprises sooner than later. Let's try that again, but in slightly different conditions.

    IFS=sf
    echo $string ${#string} 
    echo "$string" "${#string}"

Same $string - different environment.

OUTPUT

 ome  tu   here                  ome more  tu   here. 53
some stuff here                 some more stuff here. 53

Field splitting occurs based on the field delimiters defined in $IFS. There are two kinds of delimiters - $IFS whitespace and $IFS anything else. By default $IFS is assigned the value space tab newline - which are the three possible $IFS whitespace values. It is easily changed, though, as you can see above, and can have drastic effects on field-split expansions.

$IFS whitespace will elide by sequence to a single field - and this is why echoing an expansion containing any sequence of spaces when $IFS contains a space will evaluate to only a single space - because echo concatenates its arguments on spaces. But any non-whitespace values will not elide in the same way, and each occurring delimiter always gets a field unto itself - as can be seen in the stuff expansion above.

This is not the worst of it. Consider this other $string.

IFS=$space$tab$newline
cd emptydir
    string=" * * * \
             * * * "
    echo $string ${#string}
    echo "$string" "${#string}"    

OUTPUT

* * * * * * 30
 * * *                  * * *  30

Looks ok, right? Well, let's alter the environment again.

    touch file1 file2 file3 file4 file5
    echo $string ${#string}
    echo "$string" "${#string}"    

OUTPUT

file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 file1 file2 file3 file4 file5 30
 * * *                  * * *  30

Woah.

By default the shell will expand filename globs if it can match them. This occurs after parameter expansion and field-splitting in its parse-order and so any unquoted string is vulnerable in this way. You can toggle this behavior off with set -f if you like, but any POSIX-compatible shell will always glob by default.

This is the kind of stuff you're up against when you drop quotes on expansions to suit your indentation preferences. And even so, in every case, regardless of its expansion behavior, the actual value for $string is always still whatever it was when you last assigned it. So let's get back to the first thing.

set -- 'Arg 1: Line 1.' \
       'Arg 2: Line 2.' \
       'and so on for'  \
       'as long as you might like.'
var="$*"
echo "$var" "${#var}"

OUTPUT

Arg 1: Line 1. Arg 2: Line 2. and so on for as long as you might like. 70

I believe this is a far saner way to adapt shell syntax to your indentation preferences. What I'm doing above is assigning each individual string to a positional parameter - which can each be referenced by number like $1 or ${33} - and then assigning their concatenated values to $var using the special shell parameter $*.

This approach is not immune to $IFS, even so. Still, I consider its relationship to $IFS an added benefit in this respect. Consider:

IFS=\ ;space_split="$*"
IFS=/; slash_split="$*";IFS='
';new_line_split="$*"

echo "$space_split"
echo "$slash_split"
echo "$new_line_split"

OUTPUT

Arg 1: Line 1. Arg 2: Line 2. and so on for as long as you might like.
Arg 1: Line 1./Arg 2: Line 2./and so on for/as long as you might like.
Arg 1: Line 1.
Arg 2: Line 2.
and so on for
as long as you might like.

As you can see, $* concatenates each arg in "$@" on the first byte in $IFS. So saving its value while $IFS is differently assigned gets different field delimiters for each saved value. What you see above is the literal value for each variable, by the way. If you wanted no delimiter at all you would do:

IFS=;delimitless="$*"
echo "$delimitless" "${#delimitless}"

OUTPUT

Arg 1: Line 1.Arg 2: Line 2.and so on foras long as you might like. 67
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.