106

I found this.

And I am trying this:

x='some
   thing'

y=(${x//\n/})

And I had no luck, I thought it could work with double backslash:

y=(${x//\\n/})

But it did not.

To test I am not getting what I want I am doing:

echo ${y[1]}

Getting:

some
thing

Which I want to be:

some

I want y to be an array [some, thing]. How can I do this?

Community
  • 1
  • 1
sites
  • 21,417
  • 17
  • 87
  • 146
  • 4
    Note that `some` would be in `${y[0]}`, not `${y[1]}`, once you solve the newline issue. – chepner Nov 04 '13 at 16:46
  • 1
    @juanpastas FYI the linked to solution has been updated to include **how to split on a newline character when your input has spaces**. https://stackoverflow.com/a/5257398/52074 – Trevor Boyd Smith Oct 03 '18 at 17:30

2 Answers2

173

Another way:

x=$'Some\nstring'
readarray -t y <<<"$x"

Or, if you don't have bash 4, the bash 3.2 equivalent:

IFS=$'\n' read -rd '' -a y <<<"$x"

You can also do it the way you were initially trying to use:

y=(${x//$'\n'/ })

This, however, will not function correctly if your string already contains spaces, such as 'line 1\nline 2'. To make it work, you need to restrict the word separator before parsing it:

IFS=$'\n' y=(${x//$'\n'/ })

...and then, since you are changing the separator, you don't need to convert the \n to space anymore, so you can simplify it to:

IFS=$'\n' y=($x)

This approach will function unless $x contains a matching globbing pattern (such as "*") - in which case it will be replaced by the matched file name(s). The read/readarray methods require newer bash versions, but work in all cases.

Sir Athos
  • 9,403
  • 2
  • 22
  • 23
  • 1
    Matching instances of `\n` with `\\n` in the expansion pattern doesn't work; instead, you have to splice in a `\n` *literal*: `echo ${x//$'\n'/ }`. – mklement0 Nov 04 '13 at 16:50
  • 3
    The `readarray` approach is actually the *most robust* one, because `($x)` is *still subject to globbing*: if any line happens to be a valid, *single-token* globbing pattern (e.g., `*`), it WILL be expanded, if files happen to match. `readarray` requires bash *4*, however; here's the bash 3.2 equivalent (for those on OS X): `IFS=$'\n' read -rd '' -a y <<<"$x"`. Since your post is gaining traction, would you mind adding these findings? – mklement0 Nov 04 '13 at 17:48
  • In OS-X/Macland, you have to use bash 3.2 (or at least without updating BASH). Thus the mysterious read -rd ' ' must be used (and works!) the online manual page I found is pretty cryptic about this (http://ss64.com/bash/read.html)...it's pretty mind-bending...does it mean "turn off \n, and then use emptiness as the delimiter?" – Mark Gerolimatos Mar 08 '16 at 03:07
  • Please remove the horrible `IFS=$'\n' y=($x)` parts from your answer! it's broken when `$x` contains globs (like `*`, `[...]`, etc.) — I know it can be “fixed” with `set -f`, but at this point we're fighting against the shell. The semantically correct ways (and bugfree ways) are the `readarray`/`mapfile` way and the `read` way. – gniourf_gniourf Jun 17 '16 at 16:16
  • 5
    Also, note that the return code of your `read` command is `1` (e.g., a failure), since the delimiter is not seen at the end of the stream read. The proper way is: `IFS=$'\n' read -d '' -ra y < <(printf '%s;\0' "$x")`, as mentioned in [this answer](http://stackoverflow.com/questions/918886/how-do-i-split-a-string-on-a-delimiter-in-bash/24426608#24426608). Note that in the case of a space-like character (i.e., space, newline or tab character), consecutive delimiters will be considered as a unique one. So the `read` method is subtly different from the `readarray`/`mapfile` method for newlines. – gniourf_gniourf Jun 17 '16 at 16:26
  • To fix the space-like character artifact described in my comment above, you have to use a `while`/`read` loop. – gniourf_gniourf Jun 17 '16 at 16:26
  • @Jayen: it does work, but the `readarray` is executed in a subshell, and hence the value of `y` is lost as soon as the subshell exits. See [BashFAQ/024](http://mywiki.wooledge.org/BashFAQ/024). – gniourf_gniourf Jun 17 '16 at 16:28
  • I had to remove quotes from around `$x` in order for the first solution to work. – Buttle Butkus Dec 09 '18 at 07:10
8

There is another way if all you want is the text up to the first line feed:

x='some
thing'

y=${x%$'\n'*}

After that y will contain some and nothing else (no line feed).

What is happening here?

We perform a parameter expansion substring removal (${PARAMETER%PATTERN}) for the shortest match up to the first ANSI C line feed ($'\n') and drop everything that follows (*).

Fleshgrinder
  • 15,703
  • 4
  • 47
  • 56