| Tested on | RHEL 10.2 (Coughlan) |
|---|---|
| Package | bash-5.2.26-6.el10.x86_64 |
| Applies to | Linux distributions with Bash installed |
| Privilege | Normal user |
| Scope | Splitting one string into a Bash indexed array with IFS and read -ra: whitespace and single-character delimiters, empty fields, unsafe arr=($string), multi-character delimiter limits, and a CSV counterexample. Does not cover array CRUD, full IFS theory, or mapfile for files and multiline streams. |
| Related guides | Bash shell script tutorial Shell scripting course hub |
You split a string into a Bash array when one variable holds several values separated by spaces, commas, colons, or another delimiter. The usual pattern is IFS=... read -ra array <<< "$string", which fills indexed array elements without pathname expansion.
IFS names a set of delimiter characters, not a multi-character token. That distinction matters for version numbers, PATH-style lists, and any input where commas sit inside quoted CSV fields. This guide focuses on splitting one string into an indexed array. For array iteration, slicing, and modification, see the Bash arrays guide.
| Need | Pattern |
|---|---|
| Whitespace fields | read -ra arr <<< "$string" |
| One delimiter | IFS=',' read -ra arr <<< "$string" |
| Several delimiter chars | `IFS=',; |
| Preserve spaces in fields | choose a delimiter that does not appear inside values (often |) |
| Multiline or file input | use mapfile / line-reading patterns — not this article |
Quick Answer: Split a String into a Bash Array
For most single-line strings, assign the delimiter to IFS and read into an array with -a:
string="red,green,blue"
IFS=',' read -ra colors <<< "$string"
printf '%s\n' "${colors[@]}"red
green
blueread -ra reads one input record (here from a here-string), splits it using IFS, and stores the fields in colors. This is also the usual way to convert a delimited Bash string to an indexed array. The -r flag prevents backslashes in the input from being treated as escape characters, so read -ra is the safe default when splitting arbitrary strings. Use "${colors[@]}" when passing elements to another command so spaces inside a field stay intact.
Split on Whitespace
When fields are separated by spaces or tabs, read uses the default IFS whitespace rules:
string="apple banana orange"
read -ra fruits <<< "$string"
printf '%s\n' "${fruits[@]}"apple
banana
orangeThis matches a simple word list. It is not the right tool when a field itself contains spaces unless you switch to a non-whitespace delimiter.
Split on a Single Delimiter
Set IFS to one character before read. The assignment applies only to that read call when written on the same line:
string="red,green,blue"
IFS=',' read -ra colors <<< "$string"
printf '%s\n' "${colors[@]}"red
green
blueThe Bash read builtin accepts -a to fill an array from one line of input. Always pair it with -r when the string may contain backslashes:
string='C:\Temp\file,backup'
IFS=, read -a arr <<< "$string"
printf '%s\n' "${arr[@]}"C:Tempfile
backupWithout -r, read treats backslash as an escape and alters the field text — the first element is C:Tempfile, not C:\Temp\file. read -ra preserves backslashes:
IFS=, read -ra arr <<< "$string"
printf '%s\n' "${arr[@]}"C:\Temp\file
backupSplit Comma-, Colon- and Pipe-Separated Values
The same pattern works for other single-character separators.
Colon-separated paths behave like PATH entries:
path="/usr/local/bin:/usr/bin:/bin"
IFS=: read -ra dirs <<< "$path"
printf '%s\n' "${dirs[@]}"/usr/local/bin
/usr/bin
/binVersion strings split on dots:
ver="1.2.3"
IFS=. read -ra parts <<< "$ver"
printf 'major=%s minor=%s patch=%s\n' "${parts[0]}" "${parts[1]}" "${parts[2]}"major=1 minor=2 patch=3Pipe-separated records can carry spaces inside a field:
record="nginx|active|/var/log/nginx/access.log"
IFS='|' read -ra fields <<< "$record"
printf 'service=%s status=%s log=%s\n' "${fields[0]}" "${fields[1]}" "${fields[2]}"service=nginx status=active log=/var/log/nginx/access.logIf the record must contain exactly three fields, check [[ ${#fields[@]} -eq 3 ]] before using the values.
What IFS Really Means
IFS is a set of delimiter characters, not a multi-character separator string. Each character in IFS can end a field. This section covers what you need for one-line string-to-array splits; for word splitting on unquoted expansions, empty IFS, and local IFS in functions, see Bash IFS.
Bash treats IFS whitespace differently from delimiters such as , or :. Runs of spaces, tabs, and newlines are normally collapsed during field splitting, while adjacent non-whitespace IFS characters can produce empty fields:
string=$(printf 'a\x20\x20\x20b')
read -ra arr <<< "$string"
printf 'count=%s\n' "${#arr[@]}"
printf '%s' "$string" | od -An -tx1count=2
61 20 20 20 62The printf line builds a, three ordinary ASCII spaces (0x20), and b — equivalent to string='a b' with three normal spaces. Those spaces collapse to one delimiter, so you get two elements. That is why a,,,b behaves differently.
Setting IFS='::' does not mean “split only on the two-character sequence ::.” Bash still treats : as the delimiter:
text="a::b::c"
IFS="::" read -ra arr <<< "$text"
printf 'count=%s\n' "${#arr[@]}"
printf '<%s>\n' "${arr[@]}"count=5
<a>
<>
<b>
<>
<c>Each <> line is an empty array element. You get empty fields between colons, not clean a, b, c tokens. For a true multi-character separator, transform the string first or use a purpose-built tool — see Multi-Character Delimiters below.
You can list several delimiter characters at once:
string="apple,banana;orange|grape"
IFS=',;|' read -ra arr <<< "$string"
printf '%s\n' "${arr[@]}"apple
banana
orange
grapeAny comma, semicolon, or pipe ends a field.
Preserve Spaces Inside Fields
Whitespace splitting breaks when a value contains spaces. Pick a delimiter that does not appear inside the data — often | for simple service records:
string="apple|red banana|orange"
IFS='|' read -ra arr <<< "$string"
printf '%s\n' "${arr[@]}"apple
red banana
orangeThe middle element keeps its internal space because IFS was | only, not space.
Empty Fields and Adjacent Delimiters
With a non-whitespace delimiter such as comma, adjacent separators can yield empty array elements:
text="a,,b,c"
IFS=, read -ra arr <<< "$text"
printf 'count=%s\n' "${#arr[@]}"
printf '[%s]\n' "${arr[@]}"count=4
[a]
[]
[b]
[c]The empty field between the two commas becomes arr[1]="". Whether you want to keep or drop empty fields depends on your input format — filter them in a for loop if the protocol does not use empty slots.
read -a does not preserve every trailing empty field. A string that ends with the delimiter drops the final empty element:
text='a,b,'
IFS=, read -ra arr <<< "$text"
declare -p arrdeclare -a arr=([0]="a" [1]="b")You get a and b, not a third empty element after the trailing comma. Adjacent non-whitespace delimiters can create empty fields in the middle of the array, but read -a is not lossless for trailing empties. If trailing empty fields carry meaning, use a parser designed for that format or handle the trailing delimiter explicitly.
Why arr=($string) Is Unsafe
Unquoted arr=($string) runs word splitting and pathname expansion. A literal * in the string can expand to filenames in the current directory. Create a throwaway directory with a few files so the glob has something to match:
tmpdir=$(mktemp -d) && cd "$tmpdir" && touch a.txt b.txtWith a.txt and b.txt in the working directory, run the unsafe form:
string="keep * end"
arr=($string)
printf 'unsafe: %s\n' "${arr[@]}"unsafe: keep
unsafe: a.txt
unsafe: b.txt
unsafe: endThe * was meant as text, but Bash replaced it with matching files. read -ra does not glob:
read -ra arr <<< "$string"
printf 'safe: %s\n' "${arr[@]}"safe: keep
safe: *
safe: endAvoid arr=($string) for general string splitting. It performs both word splitting and pathname expansion; use IFS=... read -ra when you want delimiter-based splitting into an array. The form can be intentional in tightly controlled shell code, but it is a poor default for parsing data.
Multi-Character Delimiters
Do not simulate a multi-character delimiter with IFS alone. A common workaround for a fixed token such as :: is to replace it with a single-character separator before read:
text="host::port::db"
IFS=: read -ra tmp <<< "${text//::/:}"
printf '%s\n' "${tmp[@]}"host
port
dbThis works only when the replacement character cannot occur independently in the data. Otherwise the replacement makes original delimiters and existing characters indistinguishable. For arbitrary multi-character delimiters, use a parser that explicitly matches the full delimiter instead of relying on IFS.
Why IFS Is Not a CSV Parser
Comma splitting is not CSV parsing. Quoted fields contain commas that must stay inside one value:
csv='"Smith, John",42'
IFS=, read -ra arr <<< "$csv"
printf '%s\n' "${arr[@]}""Smith
John"
42IFS=, saw the comma inside the name and split there. For real CSV with quoting and escaping, use a CSV-aware library or tool — not IFS alone.
Split One Line vs Read Multiple Lines
read -ra handles one string / one line of input. When the source is a multiline variable, a here-document, or a file with one record per line, use line-oriented tools instead:
- Bash
mapfile/readarraywhen each input line should become one array element while IFS= read -r linefor streaming — see read a file line by line
This article stops at turning one delimited string into array elements. Building, looping, and deleting elements belong in the Bash arrays guide near the start of this page.
Common Mistakes
| Symptom | Likely cause | Fix |
|---|---|---|
| Extra filenames in the array | arr=($string) globbed * or ? |
Use read -ra or quote expansions |
a::b splits into many empty pieces |
IFS treats each : separately |
Replace :: or use a real multi-char parser |
| Field with spaces breaks apart | Default whitespace IFS |
Use |, comma, or another delimiter |
| CSV name column splits on comma | IFS=, ignores quotes |
Use a CSV parser, not IFS |
| Later commands split oddly | IFS left changed globally |
Keep IFS=... on the same line as read |
| Trailing comma loses final empty field | read -a drops trailing empties |
Handle the trailing delimiter explicitly or use another parser |
| Backslashes disappear after split | read -a without -r |
Use read -ra |
| Need one element per file line | Used read -ra on multiline text |
Use mapfile or a while read loop |
Summary
Splitting one Bash string into an array — converting a delimited string to an indexed array — usually means IFS=delimiter read -ra array <<< "$string". Use -ra so backslashes are not eaten during the read. Whitespace, comma, colon, pipe, and dot each work when IFS is set to that single character (or to a set of characters). read fills the array for one input record; expand with "${array[@]}" when passing elements to other commands.
Remember that IFS lists delimiter characters, not a multi-character separator, and that whitespace IFS collapses runs while non-whitespace delimiters can leave empty middle fields — but not trailing empty fields after read -a. Avoid arr=($string) for parsing; it word-splits and glob-expands. Quoted CSV needs a real parser, not IFS=,.
For multiline input or entire files, switch to line-reading tools instead of stretching this pattern. Once the string is split, loop, slice, and modify the elements with normal array syntax.
References
- GNU Bash manual —
read(Bourne Shell Builtins) - GNU Bash manual — Word Splitting
- GNU Bash manual — Arrays
Frequently Asked Questions
How do I split a string by comma in Bash?
Set IFS=',' and use read -ra: IFS=',' read -ra arr <<< "$string". Each comma-separated segment becomes one array element.
Can IFS use a multi-character delimiter?
No. IFS is a set of single delimiter characters. IFS='::' still splits on each colon individually. Replace the token in the string or use another tool for true multi-character separators.
Why did * in my string turn into filenames?
Unquoted arr=($string) performs pathname expansion. A * in the string matches files in the current directory. Use read -ra instead.
Can I split CSV with IFS=,?
Not reliably. Commas inside quoted fields are still delimiters to IFS. Use a CSV-aware parser when quoting and escaping matter.
When should I use mapfile instead of read -ra?
Use mapfile (or readarray) when each line of a file or multiline string should become one array element. Use read -ra when one delimited string on a single line should become multiple elements.

