281

I have a variable which contains a space-delimited string:

line="1 1.50 string"

I want to split that string with space as a delimiter and store the result in an array, so that the following:

echo ${arr[0]}
echo ${arr[1]}
echo ${arr[2]}

outputs

1
1.50
string

Somewhere I found a solution which doesn't work:

arr=$(echo ${line})

If I run the echo statements above after this, I get:

1 1.50 string
[empty line]
[empty line]

I also tried

IFS=" "
arr=$(echo ${line})

with the same result. Can someone help, please?

Nikola Novak
  • 4,091
  • 5
  • 24
  • 33
  • See this answer on Unix & Linux Stack Exchange: [Using sed with herestring (<<<) and read -a](https://unix.stackexchange.com/a/153335/201820). `set -f; arr=($string); set +f` seems to be faster than `read -r -a <<< $string`. – codeforester Mar 20 '18 at 02:23
  • Related: how to convert a _newline-delimited_ string to a bash array: [Convert multiline string to array](https://stackoverflow.com/q/24628076/4561887) – Gabriel Staples Dec 17 '21 at 20:51
  • See also these demonstrations and examples here: [`shellcheck` SC2206: Quote to prevent word splitting/globbing, or split robustly with `mapfile` or `read -a`.](https://github.com/koalaman/shellcheck/wiki/SC2206) – Gabriel Staples Feb 25 '22 at 04:10

6 Answers6

429

In order to convert a string into an array, create an array from the string, letting the string get split naturally according to the IFS (Internal Field Separator) variable, which is the space char by default:

arr=($line)

or pass the string to the stdin of the read command using the herestring (<<<) operator:

read -a arr <<< "$line"

For the first example, it is crucial not to use quotes around $line since that is what allows the string to get split into multiple elements.

See also: https://github.com/koalaman/shellcheck/wiki/SC2206

Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
kev
  • 155,172
  • 47
  • 273
  • 272
  • 14
    and to do a sanity check of your beautiful new array: `for i in ${arr[@]}; do echo $i; done` – Banjer Oct 11 '13 at 15:00
  • 9
    or just `echo ${arr[@]}` – Banjer Oct 11 '13 at 15:29
  • 13
    Both ways may fail if `$line` has globbing characters in it. `mkdir x && cd x && touch A B C && line="*" arr=($line); echo ${#arr[@]}` gives 3 – Tino Nov 26 '14 at 00:25
  • 4
    `declare -a "arr=($line)"` will ignore `IFS` delimiters inside quoted strings – Dave Oct 21 '15 at 20:13
  • 2
    Both solutions work for me on the cmd-line. `arr=($line)` doesn't work for me within a bash script whereas `read -a arr <<<$line` does. – georg Feb 26 '16 at 14:43
  • What need to be done if the file was delimited eg :"1,1.50,string" . Can delimite be specified before feeding to array. – Awknewbie May 30 '16 at 08:40
  • @Awknewbie try this command: `arr=($(echo '1,1.50,string' | tr ',' ' '))` – kev May 30 '16 at 09:51
  • Works also with ksh. Beware that declaring `arr=("a b")` won't work, you need to explicitly declare the intermediary variable "line". `line="a b"` then `arr=($line)` – Manuel Rozier Oct 18 '16 at 15:10
  • 1
    You can avoid wildcard expansion with: set -f && arr=($line) && set +f – Tim Bird Jan 06 '17 at 19:07
  • 5
    @Tino No. When `line='*'`, `read -a arr <<<$line` always work, but only `arr=($line)` fails. – Johnny Wong Aug 10 '17 at 10:11
  • @JohnnyWong Good find! Sorry that I mixed that with `ksh` where `a='*'; read -A b <<<$a` fails while `a='*'; read -A b <<<"$a"` works. Note that `bash` uses `read -a` while `ksh` uses `read -A`. – Tino Aug 13 '17 at 18:46
  • Shellcheck SC2207 argues against `arr=($line)`. I like how concise this is, but maybe it's not worth using? – TonyH Apr 27 '21 at 16:12
61

In: arr=( $line ). The "split" comes associated with "glob".
Wildcards (*,? and []) will be expanded to matching filenames.

The correct solution is only slightly more complex:

IFS=' ' read -a arr <<< "$line"

No globbing problem; the split character is set in $IFS, variables quoted.

codeforester
  • 39,467
  • 16
  • 112
  • 140
  • 8
    This should be the accepted answer. The statement `arr=($line)` in the [accepted answer](https://stackoverflow.com/a/9294015/6862601) suffers from globbing issues. For example, try: `line="twinkling *"; arr=($line); declare -p arr`. – codeforester Mar 20 '18 at 01:59
  • 1
    Quoting is optional for herestring, `<<<` but it may be a good idea to still use double quotes for consistency and readability. – codeforester Mar 20 '18 at 02:10
  • See also: [`shellcheck` SC2206: Quote to prevent word splitting/globbing, or split robustly with `mapfile` or `read -a`.](https://github.com/koalaman/shellcheck/wiki/SC2206) – Gabriel Staples Feb 19 '22 at 00:08
  • I get `func:read:5: bad option: -a` – payne Dec 19 '22 at 18:22
58

Try this:

arr=(`echo ${line}`);
gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
Randy
  • 589
  • 4
  • 2
  • 12
    Nice -- This solution also works in Z shell where some of the other approaches above fail. – Keith Hughitt Sep 27 '14 at 12:31
  • Its does the work, could you please explain why it works? – vr3C Sep 19 '16 at 03:49
  • 2
    Remark: this doesn't work either when the line have '*' in it, like `line='*'` – Johnny Wong Oct 09 '17 at 02:11
  • Looking for a `GNU Make 4.2.1` solution, but it doesn't did the job – artu-hnrq Feb 16 '21 at 21:57
  • @artu-hnrq Make use the `sh` shell. That shell has no arrays. The question is about arrays. Can't give you an answer compatible with both requirements. Unless you claim that the positional arguments are the only array in `sh` and, then, this: `set -- $line; printf '%s\n' "$@"` would work. Note that glob characters are still a problem in this case. –  Feb 16 '21 at 23:15
  • You are right @Isaac! I made my [own question](https://stackoverflow.com/q/66233239/2989289) – artu-hnrq Feb 16 '21 at 23:29
  • This solution worked for me, thanks a lot. – elulcao Jul 15 '21 at 19:42
  • Very simple solution for CentOS8 – Togomi May 24 '22 at 04:19
15

If you need parameter expansion, then try:

eval "arr=($line)"

For example, take the following code.

line='a b "c d" "*" *'
eval "arr=($line)"
for s in "${arr[@]}"; do 
    echo "$s"
done

If the current directory contained the files a.txt, b.txt and c.txt, then executing the code would produce the following output.

a
b
c d
*
a.txt
b.txt
c.txt
David Anderson
  • 1,108
  • 9
  • 17
0

Yet another solution, but using readarray:

readarray -d ' ' arr <<< "argelia china denmark colombia"

Indeed, storing the input in an array of 4 positions:

$ echo ${#arr[@]} ${arr[@]:1:2}
4 china  denmark 
ivanleoncz
  • 9,070
  • 7
  • 57
  • 49
-9
line="1 1.50 string"

arr=$( $line | tr " " "\n")

for x in $arr
do
echo "> [$x]"
done
codemania
  • 1,098
  • 1
  • 9
  • 26
Hitesh
  • 9
  • The looping is wrong, it splits the array fine and the pipe into `tr` is superfluous but it should loop over `"${arr[@]}"` instead, not `$arr` – Zorf Mar 07 '15 at 12:44