merlin2011
merlin2011

Reputation: 75649

Is there a way to use IFS to read delimited strings in a loop?

Consider the following script:

#!/bin/bash

while true; do
  IFS=';' read -r
  echo "Read: $REPLY"
done

If I run this script and type in 1;2;3, I expect and desire to see the output:

Read: 1
Read: 2
Read: 3

However, the output I get is

Read: 1;2;3

It seems like IFS is being ignored.

What is happening here, and is it possible to get the desired output?

Note that I am already aware of the array option (read -a and then loop through the array), and I am specifically interested in the single read case.

Upvotes: 0

Views: 172

Answers (2)

markp-fuso
markp-fuso

Reputation: 35516

Assuming you will always have the same number (3 in this case) of delimited fields:

while true
do
    IFS=';' read -r -p "enter string: " a b c

    echo "#############"

    echo "Read: ${a}"
    echo "Read: ${b}"
    echo "Read: ${c}"

    echo "#############"
done

NOTE: if there are more than 3 delimited fields c will be populated with fields 3-n with the delimiter included between the fields

Taking for a test drive:

enter string: 1;2;3
#############
Read: 1
Read: 2
Read: 3
#############
enter string: 1;2;3;4
#############
Read: 1
Read: 2
Read: 3;4                             # 3rd/4th fields stored in c
#############
enter string: a b c;d e f;g h i
#############
Read: a b c
Read: d e f
Read: g h i
#############
enter string:

If you don't know in advance the number of fields we can modify the read to parse and store the fields into an array then loop over the array:

while true
do
    IFS=';' read -r -p "enter string: " -a arr        # split input on delimiter ; and place results in array arr[]
#   typeset -p arr                                    # uncomment to display the contents of the array

    echo "#############"

    for ((i=0; i<${#arr[@]}; i++))
    do
        echo "Read: ${arr[i]}"
    done

    echo "#############"
done

Taking for a test drive (with the typeset -p uncommented):

enter string: 1;2;3
declare -a arr=([0]="1" [1]="2" [2]="3")
#############
Read: 1
Read: 2
Read: 3
#############
enter string: 1;2;3;4
declare -a arr=([0]="1" [1]="2" [2]="3" [3]="4")
#############
Read: 1
Read: 2
Read: 3
Read: 4                               # 4th field stored in a separate array location
#############
enter string: a b c;d e f;g h i
declare -a arr=([0]="a b c" [1]="d e f" [2]="g h i")
#############
Read: a b c
Read: d e f
Read: g h i
#############
enter string:

Upvotes: 0

chepner
chepner

Reputation: 532508

read reads one line at a time. The value of IFS controls word splitting, so that if you wrote

IFS=';' read -r a b c

then your line would be split on ; into at most 3 fields. You could then print each field separately.

IFS=';' read -r a b c <<< "1;2;3"
echo "Read: $a"
echo "Read: $b"
echo "Read: $c"

If you don't know how many fields there will be, you can populate an array by splitting as many times as possible and collecting the results. You can then iterate over the array.

IFS=';' read -ra arr <<< "1;2;3"
for x in "${arr[@]}"; do
    echo "Read: $x"
done

Upvotes: 2

Related Questions