Loop through a comma-separated shell variable

ShellLoopsUnixFor LoopCut

Shell Problem Overview


Suppose I have a Unix shell variable as below

variable=abc,def,ghij

I want to extract all the values (abc, def and ghij) using a for loop and pass each value into a procedure.

The script should allow extracting arbitrary number of comma-separated values from $variable.

Shell Solutions


Solution 1 - Shell

Not messing with IFS
Not calling external command

variable=abc,def,ghij
for i in ${variable//,/ }
do
    # call your procedure/other scripts here below
    echo "$i"
done

Using bash string manipulation http://www.tldp.org/LDP/abs/html/string-manipulation.html

Solution 2 - Shell

You can use the following script to dynamically traverse through your variable, no matter how many fields it has as long as it is only comma separated.

variable=abc,def,ghij
for i in $(echo $variable | sed "s/,/ /g")
do
    # call your procedure/other scripts here below
    echo "$i"
done

Instead of the echo "$i" call above, between the do and done inside the for loop, you can invoke your procedure proc "$i".


Update: The above snippet works if the value of variable does not contain spaces. If you have such a requirement, please use one of the solutions that can change IFS and then parse your variable.


Hope this helps.

Solution 3 - Shell

If you set a different field separator, you can directly use a for loop:

IFS=","
for v in $variable
do
   # things with "$v" ...
done

You can also store the values in an array and then loop through it as indicated in How do I split a string on a delimiter in Bash?:

IFS=, read -ra values <<< "$variable"
for v in "${values[@]}"
do
   # things with "$v"
done
Test
$ variable="abc,def,ghij"
$ IFS=","
$ for v in $variable
> do
> echo "var is $v"
> done
var is abc
var is def
var is ghij

You can find a broader approach in this solution to How to iterate through a comma-separated list and execute a command for each entry.

Examples on the second approach:

$ IFS=, read -ra vals <<< "abc,def,ghij"
$ printf "%s\n" "${vals[@]}"
abc
def
ghij
$ for v in "${vals[@]}"; do echo "$v --"; done
abc --
def --
ghij --

Solution 4 - Shell

#/bin/bash   
TESTSTR="abc,def,ghij"

for i in $(echo $TESTSTR | tr ',' '\n')
do
echo $i
done

I prefer to use tr instead of sed, becouse sed have problems with special chars like \r \n in some cases.

other solution is to set IFS to certain separator

Solution 5 - Shell

I think syntactically this is cleaner and also passes shell-check linting

variable=abc,def,ghij
for i in ${variable//,/ }
do
    # call your procedure/other scripts here below
    echo "$i"
done

Solution 6 - Shell

Another solution not using IFS and still preserving the spaces:

$ var="a bc,def,ghij"
$ while read line; do echo line="$line"; done < <(echo "$var" | tr ',' '\n')
line=a bc
line=def
line=ghij

Solution 7 - Shell

Here is an alternative tr based solution that doesn't use echo, expressed as a one-liner.

for v in $(tr ',' '\n' <<< "$var") ; do something_with "$v" ; done

It feels tidier without echo but that is just my personal preference.

Solution 8 - Shell

Here's my pure bash solution that doesn't change IFS, and can take in a custom regex delimiter.

loop_custom_delimited() {
    local list=$1
    local delimiter=$2
    local item
    if [[ $delimiter != ' ' ]]; then
        list=$(echo $list | sed 's/ /'`echo -e "\010"`'/g' | sed -E "s/$delimiter/ /g")
    fi
    for item in $list; do
        item=$(echo $item | sed 's/'`echo -e "\010"`'/ /g')
        echo "$item"
    done
}

Solution 9 - Shell

The following solution:

  • doesn't need to mess with IFS
  • doesn't need helper variables (like i in a for-loop)
  • should be easily extensible to work for multiple separators (with a bracket expression like [:,] in the patterns)
  • really splits only on the specified separator(s) and not - like some other solutions presented here on e.g. spaces too.
  • is POSIX compatible
  • doesn't suffer from any subtle issues that might arise when bash’s nocasematch is on and a separator that has lower/upper case versions is used in a match like with ${parameter/pattern/string} or case

beware that:

  • it does however work on the variable itself and pop each element from it - if that is not desired, a helper variable is needed
  • it assumes var to be set and would fail if it's not and set -u is in effect
while true; do
        x="${var%%,*}"
        echo $x
        #x is not really needed here, one can of course directly use "${var%%:*}"

        if [ -z "${var##*,*}" ]  &&  [ -n "${var}" ]; then
                var="${var#*,}"
        else
                break
        fi
done

Beware that separators that would be special characters in patterns (e.g. a literal *) would need to be quoted accordingly.

Solution 10 - Shell

Try this one.

#/bin/bash   
testpid="abc,def,ghij" 
count=`echo $testpid | grep -o ',' | wc -l` # this is not a good way
count=`expr $count + 1` 
while [ $count -gt 0 ]  ; do
     echo $testpid | cut -d ',' -f $i
     count=`expr $count - 1 `
done

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRamanathan KView Question on Stackoverflow
Solution 1 - ShellnericView Answer on Stackoverflow
Solution 2 - ShellanacronView Answer on Stackoverflow
Solution 3 - ShellfedorquiView Answer on Stackoverflow
Solution 4 - ShellMarek LisieckiView Answer on Stackoverflow
Solution 5 - Shellsman0307View Answer on Stackoverflow
Solution 6 - Shelluser3071170View Answer on Stackoverflow
Solution 7 - Shell6EQUJ5View Answer on Stackoverflow
Solution 8 - ShellplacidwolverineView Answer on Stackoverflow
Solution 9 - ShellcalestyoView Answer on Stackoverflow
Solution 10 - ShellKarthikeyan.R.SView Answer on Stackoverflow