Categories
bash scripting shell split

How do I split a string on a delimiter in Bash?

2598

I have this string stored in a variable:

IN="[email protected];[email protected]"

Now I would like to split the strings by ; delimiter so that I have:

ADDR1="[email protected]"
ADDR2="[email protected]"

I don’t necessarily need the ADDR1 and ADDR2 variables. If they are elements of an array that’s even better.


After suggestions from the answers below, I ended up with the following which is what I was after:

#!/usr/bin/env bash

IN="[email protected];[email protected]"

mails=$(echo $IN | tr ";" "\n")

for addr in $mails
do
    echo "> [$addr]"
done

Output:

> [[email protected]]
> [[email protected]]

There was a solution involving setting Internal_field_separator (IFS) to ;. I am not sure what happened with that answer, how do you reset IFS back to default?

RE: IFS solution, I tried this and it works, I keep the old IFS and then restore it:

IN="[email protected];[email protected]"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo "> [$x]"
done

IFS=$OIFS

BTW, when I tried

mails2=($IN)

I only got the first string when printing it in loop, without brackets around $IN it works.

9

  • 25

    With regards to your “Edit2”: You can simply “unset IFS” and it will return to the default state. There’s no need to save and restore it explicitly unless you have some reason to expect that it’s already been set to a non-default value. Moreover, if you’re doing this inside a function (and, if you aren’t, why not?), you can set IFS as a local variable and it will return to its previous value once you exit the function.

    May 1, 2012 at 1:26


  • 24

    @BrooksMoses: (a) +1 for using local IFS=... where possible; (b) -1 for unset IFS, this doesn’t exactly reset IFS to its default value, though I believe an unset IFS behaves the same as the default value of IFS ($’ \t\n’), however it seems bad practice to be assuming blindly that your code will never be invoked with IFS set to a custom value; (c) another idea is to invoke a subshell: (IFS=$custom; ...) when the subshell exits IFS will return to whatever it was originally.

    May 31, 2012 at 5:21

  • I just want to have a quick look at the paths to decide where to throw an executable, so I resorted to run ruby -e "puts ENV.fetch('PATH').split(':')". If you want to stay pure bash won’t help but using any scripting language that has a built-in split is easier.

    – ichigolas

    Mar 7, 2016 at 15:32

  • 10

    for x in $(IFS=';';echo $IN); do echo "> [$x]"; done

    Apr 26, 2018 at 20:15


  • 3

    In order to save it as an array I had to place another set of parenthesis and change the \n for just a space. So the final line is mails=($(echo $IN | tr ";" " ")). So now I can check the elements of mails by using the array notation mails[index] or just iterating in a loop

    – afranques

    Jul 3, 2018 at 14:08


1543

You can set the internal field separator (IFS) variable, and then let it parse into an array. When this happens in a command, then the assignment to IFS only takes place to that single command’s environment (to read ). It then parses the input according to the IFS variable value into an array, which we can then iterate over.

This example will parse one line of items separated by ;, pushing it into an array:

IFS=';' read -ra ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
  # process "$i"
done

This other example is for processing the whole content of $IN, each time one line of input separated by ;:

while IFS=';' read -ra ADDR; do
  for i in "${ADDR[@]}"; do
    # process "$i"
  done
done <<< "$IN"

19

  • 29

    This is probably the best way. How long will IFS persist in it’s current value, can it mess up my code by being set when it shouldn’t be, and how can I reset it when I’m done with it?

    May 28, 2009 at 2:25

  • 12

    now after the fix applied, only within the duration of the read command 🙂

    May 28, 2009 at 3:04

  • 18

    You can read everything at once without using a while loop: read -r -d ” -a addr <<< “$in” # The -d ” is key here, it tells read not to stop at the first newline (which is the default -d) but to continue until EOF or a NULL byte (which only occur in binary data).

    – lhunath

    May 28, 2009 at 6:14

  • 73

    @LucaBorrione Setting IFS on the same line as the read with no semicolon or other separator, as opposed to in a separate command, scopes it to that command — so it’s always “restored”; you don’t need to do anything manually.

    Jul 6, 2013 at 14:39


  • 5

    @imagineerThis There is a bug involving herestrings and local changes to IFS that requires $IN to be quoted. The bug is fixed in bash 4.3.

    – chepner

    Oct 2, 2014 at 3:50

1385

Taken from Bash shell script split array:

IN="[email protected];[email protected]"
arrIN=(${IN//;/ })
echo ${arrIN[1]}                  # Output: [email protected]

Explanation:

This construction replaces all occurrences of ';' (the initial // means global replace) in the string IN with ' ' (a single space), then interprets the space-delimited string as an array (that’s what the surrounding parentheses do).

The syntax used inside of the curly braces to replace each ';' character with a ' ' character is called Parameter Expansion.

There are some common gotchas:

  1. If the original string has spaces, you will need to use IFS:
  • IFS=':'; arrIN=($IN); unset IFS;
  1. If the original string has spaces and the delimiter is a new line, you can set IFS with:
  • IFS=$'\n'; arrIN=($IN); unset IFS;

20

  • 113

    I just want to add: this is the simplest of all, you can access array elements with ${arrIN[1]} (starting from zeros of course)

    – oz123

    Mar 21, 2011 at 18:50

  • 32

    Found it: the technique of modifying a variable within a ${} is known as ‘parameter expansion’.

    Jan 5, 2012 at 15:13

  • 28

    No, I don’t think this works when there are also spaces present… it’s converting the ‘,’ to ‘ ‘ and then building a space-separated array.

    – Ethan

    Apr 12, 2013 at 22:47

  • 15

    Very concise, but there are caveats for general use: the shell applies word splitting and expansions to the string, which may be undesired; just try it with. IN="[email protected];[email protected];*;broken apart". In short: this approach will break, if your tokens contain embedded spaces and/or chars. such as * that happen to make a token match filenames in the current folder.

    – mklement0

    Apr 24, 2013 at 14:08

  • 63

    This is a bad approach for other reasons: For instance, if your string contains ;*;, then the * will be expanded to a list of filenames in the current directory. -1

    Jul 6, 2013 at 14:39

335

If you don’t mind processing them immediately, I like to do this:

for i in $(echo $IN | tr ";" "\n")
do
  # process
done

You could use this kind of loop to initialize an array, but there’s probably an easier way to do it.

6

  • You should have kept the IFS answer. It taught me something I didn’t know, and it definitely made an array, whereas this just makes a cheap substitute.

    May 28, 2009 at 2:42

  • I see. Yeah i find doing these silly experiments, i’m going to learn new things each time i’m trying to answer things. I’ve edited stuff based on #bash IRC feedback and undeleted 🙂

    May 28, 2009 at 2:59

  • 4

    You could change it to echo “$IN” | tr ‘;’ ‘\n’ | while read -r ADDY; do # process “$ADDY”; done to make him lucky, i think 🙂 Note that this will fork, and you can’t change outer variables from within the loop (that’s why i used the <<< “$IN” syntax) then

    May 28, 2009 at 17:00

  • 15

    To summarize the debate in the comments: Caveats for general use: the shell applies word splitting and expansions to the string, which may be undesired; just try it with. IN="[email protected];[email protected];*;broken apart". In short: this approach will break, if your tokens contain embedded spaces and/or chars. such as * that happen to make a token match filenames in the current folder.

    – mklement0

    Apr 24, 2013 at 14:13

  • This is very helpful answer. e.g. IN=abc;def;123. How can we also print the index number? echo $count $i ?

    – user8864088

    Oct 10, 2018 at 18:50