179

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.

fedorqui
  • 275,237
  • 103
  • 548
  • 598
Ramanathan K
  • 1,829
  • 3
  • 13
  • 8

10 Answers10

235

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

neric
  • 3,927
  • 3
  • 21
  • 25
181

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.

starball
  • 20,030
  • 7
  • 43
  • 238
anacron
  • 6,443
  • 2
  • 26
  • 31
  • 16
    This doesn't work if `$variable` contains whitespace, e.g. `variable=a b,c,d` => prints 4 lines (a | b | c | d) rather than 3 (a b | c | d) – Dan Jun 11 '15 at 09:53
  • As well as it is getting added new quotes like ** `"value `**. could you please update if there is any regex to remove that .. – thar45 Mar 20 '17 at 03:56
  • i am getting shellcheck warnings with this script – anandhu Apr 06 '22 at 13:22
  • Shellcheck was released in 2020. This answer is from 2014 and was answered to address the original poster's question. If possible, you can edit the answer to improve it. :) – anacron Apr 07 '22 at 17:00
72

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 --
Community
  • 1
  • 1
fedorqui
  • 275,237
  • 103
  • 548
  • 598
  • Also see http://stackoverflow.com/questions/918886/how-do-i-split-a-string-on-a-delimiter-in-bash :-) Good luck to all. – shellter Dec 30 '14 at 11:55
  • Yep! I also thought about that, only that it then required to steps: a `while` to read into an array and another one to loop through the results. – fedorqui Dec 30 '14 at 12:00
  • 4
    Makes me happy when someone actually knows how to use the shell, rather than pipe together a bunch of binutils. – PeterT Feb 22 '18 at 19:47
  • 2
    do you have to set `IFS` back to whatever it was before ? – pstanton Sep 06 '18 at 01:56
  • @pstanton no, because you are setting it just for the scope of this specific command. It is different if you would do `IFS=,` and then `read -ra vals <<< "a,b,c"` in another line. See [Setting IFS for a single statement](https://unix.stackexchange.com/a/92190/40596) – fedorqui Sep 06 '18 at 07:00
  • I was trying to use an environment variable from a docker-compose value that used the block chomping indicator (`>-`), I used `IFS=", "` – GammaGames Jul 12 '19 at 15:38
  • 1
    @fedorqui It did! That's the only variable I wanted to loop through and it made my yaml file easier to read (instead of one LONG environment variable it has a bunch of rows) – GammaGames Jul 15 '19 at 14:14
12

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
sman0307
  • 198
  • 1
  • 9
8
#/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

Marek Lisiecki
  • 199
  • 3
  • 4
3

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
user3071170
  • 435
  • 7
  • 14
2

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.

6EQUJ5
  • 3,142
  • 1
  • 21
  • 29
  • Nice solution, but unfortunately doesn't work. Can be fixed for example by setting IFS to \n. – aver Nov 03 '22 at 11:02
2

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.

calestyo
  • 327
  • 2
  • 7
0

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
}
-1

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
Karthikeyan.R.S
  • 3,991
  • 1
  • 19
  • 31
  • but what if i am not sure about the number of fields in the variable testpid? – Ramanathan K Dec 30 '14 at 09:28
  • Also, i need to pass each filed of the above variable as an argument to a procedure. And i want to do this until the length of the variable becomes zero. (ie; all the fields should be passed once to the procedure for processing) – Ramanathan K Dec 30 '14 at 09:33
  • I don't know about the procedure. From this link You can get the count of the fields. `http://stackoverflow.com/questions/8629330/unix-count-of-columns-in-file`. After that Make that for loop into while loop. You can get that one. – Karthikeyan.R.S Dec 30 '14 at 09:38
  • This is to count the fields from a file i guess. What if i need to count the fields in a variable (suppose the variable is testpid=abc,def,ghij) – Ramanathan K Dec 30 '14 at 09:48
  • echo the variable and pipe the output to the awk. Or else see my updated answer. May be it is useful. – Karthikeyan.R.S Dec 30 '14 at 09:49
  • grep: Not a recognized flag: o Usage: grep [-r] [-R] [-H] [-L] [-E|-F] [-c|-l|-q] [-insvxbhwyu] [-p[parasep]] -e pattern_list... [-f pattern_file...] [file...] Usage: grep [-r] [-R] [-H] [-L] [-E|-F] [-c|-l|-q] [-insvxbhwyu] [-p[parasep]] [-e pattern_list...] -f pattern_file... [file...] Usage: grep [-r] [-R] [-H] [-L] [-E|-F] [-c|-l|-q] [-insvxbhwyu] [-p[parasep]] pattern_list [file...] cut: A flag requires a parameter: f Usage: cut -b List [-n] [File...] or: cut -c List [File...] or: cut -f List [-d Character] [-s] [File...] – Ramanathan K Dec 30 '14 at 09:58
  • Have you tried the awk command.?? That one is easy for you!. Because I am using ubuntu. – Karthikeyan.R.S Dec 30 '14 at 09:59
  • Can you please help me with the command as i am new to shell scripting – Ramanathan K Dec 30 '14 at 11:19
  • too many line of code for just a simple task! – Xao Apr 27 '21 at 09:42