I can not find a way to correctly split a simple input string by a configurable separator
Script:
#!/bin/bash
while IFS='|' read -r name; do
echo "$name";
done <<< "one|two|three"
Actual output :
one|two|three
Expected output:
one
two
three
I'm using `GNU bash, version 4.4.20(1)-release (x86_64-redhat-linux-gnu)`
IFS='|' read -r name
will split the input using |
as the delimiter but then you only give it one variable (name
) in which to store the 3 components. Consider the following:
$ IFS='|' read -r var1 <<< "one|two|three"
$ typeset -p var1
declare -- var1="one|two|three" # "one" plus leftover "two|three"
$ IFS='|' read -r var1 var2 <<< "one|two|three"
$ typeset -p var1 var2
declare -- var1="one"
declare -- var2="two|three" # "two" plus leftover "|three"
$ IFS='|' read -r var1 var2 var3 <<< "one|two|three"
$ typeset -p var1 var2 var3
declare -- var1="one"
declare -- var2="two"
declare -- var3="three" # "three" plus *no leftover*
$ IFS='|' read -r var1 var2 var3 var4 <<< "one|two|three"
$ typeset -p var1 var2 var3 var4
declare -- var1="one"
declare -- var2="two"
declare -- var3="three"
declare -- var4="" # not enough delimited items to populate "var4"
In your case, assuming you know there will always be 3 items, is to provide 3 variables for read
to populate, eg:
while IFS='|' read -r name1 name2 name3; do # modify to use 3 distinct variable names as you see fit
echo "name1:$name1:"
echo "name2:$name2:"
echo "name2:$name3:"
done <<< "one|two|three"
This will generate:
name1:one:
name2:two:
name2:three:
Keep in mind this solution will only loop once, with all 3 items available in said single loop.
If the intent is to loop 3 times, with each loop processing just 1 item, then we need to (in essence) break the input into 3 separate items - one per line.
One option would be convert the |
delimiters into linefeeds (\n
).
One idea using tr
, eg:
$ tr '|' '\n' <<< "one|two|three"
one
two
three
Modifying your current code to utilize the tr
output:
cnt=0
while read -r name; do
((cnt++))
echo "loop #${cnt} : name=$name"
done < <(tr '|' '\n' <<< "one|two|three")
This generates:
loop #1 : name=one
loop #2 : name=two
loop #3 : name=three
An alternative approach would use read
to split the input into array elements (See Diego's answer) and then loop through the array elements, eg:
IFS='|' read -ra names <<< "one|two|three"
cnt=0
for name in "${names[@]}"; do
((cnt++))
echo "loop #${cnt} : name=$name"
done
This also generates:
loop #1 : name=one
loop #2 : name=two
loop #3 : name=three