9

Given the following syntax:

x=(-a 2);echo "${x[@]}";x=(-e 2 -e); echo "${x[@]}"

Output:

-a 2
2 -e

Desired output

-a 2
-e 2 -e

Why is this happening? How do I fix?

Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
  • `echo "${x[*]}"` (`${x[*]}` returns all the items as a single word, whereas `${x[@]}` returns each item as a separate word) – Wrikken Jan 16 '14 at 00:01

3 Answers3

11

tl;dr

printf "%s\n" "${x[*]}"

Explanation

echo takes 3 options:

$ help echo
[…]
Options:
  -n    do not append a newline
  -e    enable interpretation of the following backslash escapes
  -E    explicitly suppress interpretation of backslash escapes

So if you run:

$ echo -n
$ echo -n -e
$ echo -n -e -E

You get nothing. Even if you put each option in quotes, it still looks the same to bash:

$ echo "-n"
$ echo "-n" "-e"

The last command runs echo with two arguments: -n and -e. Now contrast that with:

$ echo "-n -e"
-n -e

What we did was run echo with a single argument: -n -e. Since bash does not recognize the (combined) option -n -e, it finally echoes the single argument to the terminal like we want.

Applied to Arrays

In the second case, the array x begins with the element -e. After bash expands the array ${x[@]}, you are effectively running:

$ echo "-e" "2" "-e"
2 -e

Since the first argument is -e, it is interpreted as an option (instead of echoed to the terminal), as we already saw.

Now contrast that with the other style of array expansion ${x[*]}, which effectively does the following:

$ echo "-e 2 -e"
-e 2 -e

bash sees the single argument -e 2 -e — and since it does not recognize that as an option — it echoes the argument to the terminal.

Note that ${x[*]} style expansion is not safe in general. Take the following example:

$ x=(-e)
$ echo "${x[*]}"

Nothing is printed even though we expected -e to be echoed. If you've been paying attention, you already know why this is the case.

Escaping

The solution is to escape any arguments to the echo command. Unfortunately, unlike other commands which offer some way to say, “hey! the following argument is not to be interpreted as an option” (typically a -- argument), bash provides no such escaping mechanism for echo.

Fortunately there is the printf command, which provides a superset of the functionality that echo offers. Hence we arrive at the solution:

printf "%s\n" "${x[*]}"
Michael Kropat
  • 14,557
  • 12
  • 70
  • 91
  • +1. Too bad `echo` doesn't take `--` – glenn jackman Jan 16 '14 at 03:20
  • 1
    +1 although it's actually even worse than this, because different implementations of the `echo` command behave differently. Some interpret `-option`s, some ignore `-option`s but interpret escape sequences in the string, some do both... if you want predictable results, just avoid `echo` and use `printf` instead. – Gordon Davisson Jan 16 '14 at 07:09
2

Nice one! What is happening is the first -e is being interpreted as an option for echo (to enable escape sequences' Usually, you'd do something like echo -- "-e", and it should print simply -e, but echo is happy to behave differently, and simply prints out -- -e as a whole string.

echo does not interpret -- to mean the end of options.

The solution to the problem could also be found in the man pages:

Due to shell aliases and built-in echo command, using an unadorned echo interactively or in a script may get you different functionality than that described here. Invoke it via env (i.e., env echo ...) to avoid interference from the shell.

So something like this should work:

x=(-a 2);echo "${x[@]}";x=(-e 2 -e); env echo "${x[@]}"
kenorb
  • 155,785
  • 88
  • 678
  • 743
stellarhopper
  • 660
  • 5
  • 10
2

@MichaelKropat's answer gives sufficient explanation.

As an alternative to echo (and printf), cat and a here-string can be used:

$ x=(-a 2);cat <<< "${x[@]}";x=(-e 2 -e); cat <<< "${x[@]}"
-a 2
-e 2 -e
$ 
Community
  • 1
  • 1
Digital Trauma
  • 15,475
  • 3
  • 51
  • 83