Skip to main content
added 546 characters in body
Source Link
user79743
user79743
delim_to_array() {
    local IFS="${2:-$' :|'}"
    # eval $1\=\(${!1}\);
    read -ra "$1" <<<"${!1}"
}

test="fail-test"; a="fail-test"

animal_list='anacondaanimal_list='bison, bisona space, {1..3},~/,${a},$a,$((2+2)),$(echo "fail"),./*,*,*'

delim_to_array "animal_list" ","
printf "<%s>" "${animal_list[@]}"; echo

$ so-setvar.sh
<bison>< a space>< {1..3}><~/><${a}><$a><$((2+2))><$(echo "fail")><./*><*><*>
#!/bin/bash

delim_to_array() {
        local IFS="${2:-$' :|'}"
        # printf "inside  IFS=<%s>\n" "$IFS"
        # eval $1\=\(${!1}\);
        read -ra "$1" <<<"${!1}";
}

animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "NAME: %s\n"%s\t " "${animal_list[@]}"

"; echo

people_list="alvin|baron|caleb|doug"
delim_to_array "people_list"
printf "NAME: %s\n"%s\t " "${people_list[@]}""; echo

$ ./so-setvar.sh
NAME: anaconda   NAME:  bison    NAME:  cougar   NAME:  dingo    
NAME: alvin      NAME: baron     NAME: caleb     NAME: doug      

As the (eval) function was constructed, there is one place in which the var is exposed unquoted to the shell parsing. That allows us to get the "word splitting" done using the IFS value. But that also expose the values of the vars (unless some quoting prevent that) to: "brace expansion", "tilde expansion", "parameter, variable and arithmetic expansion", "command substitution", and "pathname expansion", In that order. And process substitution <() >() in systems that support it.

 a=failed; echo {1..3} ~/ ${a} $a $((2+2)) $(ls) ./*

If you are sure that the variables do not contain such problematic values, thethen you are safe. If there is the potential to have such values, the ways to answer your question are more complex and need more (even longer) descriptions and explanations. Using read is an alternative.

  • Always use the -r option, it is very hard for me to think of a condition where it is not needed.
  • The read command could get only one line. No multiple lines valuesMulti-lines, even by setting the (ok in 99% of cases)-d option, need special care. Or the whole input will be assigned to one variable.
  • If IFS value contains an space, leading and trailing spaces will be removed. Well, the complete description should include some detail about the tab, but I'll skip it.
  • Do not pipe | data to read. If you do, itread will be in a sub-shell. All variables set in a sub-shell do not workpersist upon returning to the parent shell. Well, there are some workarounds, but, again, I'll skip the detail.
delim_to_array() {
    local IFS="${2:-$' :|'}"
    # eval $1\=\(${!1}\);
    read -ra "$1" <<<"${!1}"
}

test="fail-test"; a="fail-test"

animal_list='anaconda, bison, {1..3},~/,${a},$a,$((2+2)),$(echo "fail"),./*,*,*'

delim_to_array "animal_list" ","
printf "<%s>" "${animal_list[@]}"; echo
#!/bin/bash

delim_to_array() {
        local IFS="${2:-$' :|'}"
        # printf "inside  IFS=<%s>\n" "$IFS"
        # eval $1\=\(${!1}\);
        read -ra "$1" <<<"${!1}";
}

animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "NAME: %s\n" "${animal_list[@]}"

echo

people_list="alvin|baron|caleb|doug"
delim_to_array "people_list"
printf "NAME: %s\n" "${people_list[@]}"

As the function was constructed, there is one place in which the var is exposed unquoted to the shell parsing. That allows us to get the "word splitting" done using the IFS value. But that also expose the values of the vars to: "brace expansion", "tilde expansion", "parameter, variable and arithmetic expansion", "command substitution", and "pathname expansion", In that order. And process substitution <() >() in systems that support it.

 echo {1..3} ~/ ${a} $a $((2+2)) $(ls) ./*

If you are sure that the variables do not contain such problematic values, the you are safe. If there is the potential to have such values, the ways to answer your question are more complex and need more (even longer) descriptions and explanations.

  • Always use the -r option, it is very hard for me to think of a condition where it is not needed.
  • The read command could get only one line. No multiple lines values (ok in 99% of cases).
  • If IFS value contains an space, leading and trailing spaces will be removed. Well, the complete description should include some detail about the tab, but I'll skip it.
  • Do not pipe | data to read, it will not work. Well, there are some workarounds, but, again, I'll skip the detail.
delim_to_array() {
    local IFS="${2:-$' :|'}"
    # eval $1\=\(${!1}\);
    read -ra "$1" <<<"${!1}"
}

test="fail-test"; a="fail-test"

animal_list='bison, a space, {1..3},~/,${a},$a,$((2+2)),$(echo "fail"),./*,*,*'

delim_to_array "animal_list" ","
printf "<%s>" "${animal_list[@]}"; echo

$ so-setvar.sh
<bison>< a space>< {1..3}><~/><${a}><$a><$((2+2))><$(echo "fail")><./*><*><*>
#!/bin/bash

delim_to_array() {
        local IFS="${2:-$' :|'}"
        # printf "inside  IFS=<%s>\n" "$IFS"
        # eval $1\=\(${!1}\);
        read -ra "$1" <<<"${!1}";
}

animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "NAME: %s\t " "${animal_list[@]}"; echo

people_list="alvin|baron|caleb|doug"
delim_to_array "people_list"
printf "NAME: %s\t " "${people_list[@]}"; echo

$ ./so-setvar.sh
NAME: anaconda   NAME:  bison    NAME:  cougar   NAME:  dingo    
NAME: alvin      NAME: baron     NAME: caleb     NAME: doug      

As the (eval) function was constructed, there is one place in which the var is exposed unquoted to the shell parsing. That allows us to get the "word splitting" done using the IFS value. But that also expose the values of the vars (unless some quoting prevent that) to: "brace expansion", "tilde expansion", "parameter, variable and arithmetic expansion", "command substitution", and "pathname expansion", In that order. And process substitution <() >() in systems that support it.

 a=failed; echo {1..3} ~/ ${a} $a $((2+2)) $(ls) ./*

If you are sure that the variables do not contain such problematic values, then you are safe. If there is the potential to have such values, the ways to answer your question are more complex and need more (even longer) descriptions and explanations. Using read is an alternative.

  • Always use the -r option, it is very hard for me to think of a condition where it is not needed.
  • The read command could get only one line. Multi-lines, even by setting the -d option, need special care. Or the whole input will be assigned to one variable.
  • If IFS value contains an space, leading and trailing spaces will be removed. Well, the complete description should include some detail about the tab, but I'll skip it.
  • Do not pipe | data to read. If you do, read will be in a sub-shell. All variables set in a sub-shell do not persist upon returning to the parent shell. Well, there are some workarounds, but, again, I'll skip the detail.
Added a read solution.
Source Link
user79743
user79743

If we remove all the un-neededunneeded lines to leave only the core eval, only create a new variable for IFS, we reduce the function to its minimal expresionexpression:

Setting the value of IFS as a local variable, allows us to also set a "default" value for the function. Whenever the value needed for IFS is not sent to the function as the second argument, the local IFS takes the "default" value. I felt that the default should be space ( ) (which is always an useful splitting value), the colon (:), and the vertical line (:|). Any of those three will split the values. Of course, the default could be set to any other values that fit your needs.

Edit to use read:

It is scary how much change is expressedTo reduce the risk of unquoted values in so little codeeval, we can use:

delim_to_array() {
    local IFS="${2:-$' :|'}"
    # eval $1\=\(${!1}\);
    read -ra "$1" <<<"${!1}"
}

test="fail-test"; a="fail-test"

animal_list='anaconda, bison, {1..3},~/,${a},$a,$((2+2)),$(echo "fail"),./*,*,*'

delim_to_array "animal_list" ","
printf "<%s>" "${animal_list[@]}"; echo

Most of the values set above for the var animal_list do fail with eval.
But pass the read without problems.

  • Note: It is perfectly safe to try the eval option in this code as the values of the vars have been set to plain text values just before calling the function. Even if really executed, they are just text. Not even a problem with ill-named files, as pathname expansion is the last expansion, there will be no variable expansion re-executed over the pathname expansion. Again, with the code as is, this is in no way a validation for general use of eval.
#!/bin/bash

delim_to_array() {
        local IFS="${2:-$' :|'}"
        # printf "inside  IFS=<%s>\n" "$IFS"
        # eval $1\=\(${!1}\);
        read -ra "$1" <<<"${!1}";
}

animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "NAME: %s\n" "${animal_list[@]}"

echo

people_list="alvin|baron|caleb|doug"
delim_to_array "people_list"
printf "NAME: %s\n" "${people_list[@]}"

As you can see, the IFS is set only inside the function, it is not changed permanently, and therefore it does not need to be re-set to its old value. AditionallyAdditionally, the second call "people_list" to the function takes advantage of the default value of IFS, there is no need to set a second argument.

Warnings 01:

Warnings 02:

I feel that the level of complexity of the solution proposed is already enoughYes, and I will leave other issues as "outside the rangeread comes with it's own share of this answer"«dragons». 

  • Always use the -r option, it is very hard for me to think of a condition where it is not needed.
  • The read command could get only one line. No multiple lines values (ok in 99% of cases).
  • If IFS value contains an space, leading and trailing spaces will be removed. Well, the complete description should include some detail about the tab, but I'll skip it.
  • Do not pipe | data to read, it will not work. Well, there are some workarounds, but, again, I'll skip the detail.

Of courseI didn't mean to include the warnings and problems of read, any new question could ask about deeperbut by popular request, or more specific issuesI had to include them, sorry.

If we remove all the un-needed lines to leave only the core eval, only create a new variable for IFS, we reduce the function to its minimal expresion:

Setting the value of IFS as a local variable, allows us to also set a "default" value for the function. Whenever the value needed for IFS is not sent to the function as the second argument, the local IFS takes the "default" value. I felt that the default should be space ( ) (which is always an useful splitting value), the colon (:), and the vertical line (:). Any of those three will split the values. Of course, the default could be set to any other values that fit your needs.

It is scary how much change is expressed in so little code.

#!/bin/bash

delim_to_array() {
        local IFS="${2:-$' :|'}"
        # printf "inside  IFS=<%s>\n" "$IFS"
        eval $1\=\(${!1}\);
}

animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "NAME: %s\n" "${animal_list[@]}"

echo

people_list="alvin|baron|caleb|doug"
delim_to_array "people_list"
printf "NAME: %s\n" "${people_list[@]}"

As you can see, the IFS is set only inside the function, it is not changed permanently, and therefore it does not need to be re-set to its old value. Aditionally, the second call "people_list" to the function takes advantage of the default value of IFS, there is no need to set a second argument.

Warnings:

I feel that the level of complexity of the solution proposed is already enough, and I will leave other issues as "outside the range of this answer".

Of course, any new question could ask about deeper, or more specific issues.

If we remove all the unneeded lines to leave only the core eval, only create a new variable for IFS, we reduce the function to its minimal expression:

Setting the value of IFS as a local variable, allows us to also set a "default" value for the function. Whenever the value needed for IFS is not sent to the function as the second argument, the local IFS takes the "default" value. I felt that the default should be space ( ) (which is always an useful splitting value), the colon (:), and the vertical line (|). Any of those three will split the values. Of course, the default could be set to any other values that fit your needs.

Edit to use read:

To reduce the risk of unquoted values in eval, we can use:

delim_to_array() {
    local IFS="${2:-$' :|'}"
    # eval $1\=\(${!1}\);
    read -ra "$1" <<<"${!1}"
}

test="fail-test"; a="fail-test"

animal_list='anaconda, bison, {1..3},~/,${a},$a,$((2+2)),$(echo "fail"),./*,*,*'

delim_to_array "animal_list" ","
printf "<%s>" "${animal_list[@]}"; echo

Most of the values set above for the var animal_list do fail with eval.
But pass the read without problems.

  • Note: It is perfectly safe to try the eval option in this code as the values of the vars have been set to plain text values just before calling the function. Even if really executed, they are just text. Not even a problem with ill-named files, as pathname expansion is the last expansion, there will be no variable expansion re-executed over the pathname expansion. Again, with the code as is, this is in no way a validation for general use of eval.
#!/bin/bash

delim_to_array() {
        local IFS="${2:-$' :|'}"
        # printf "inside  IFS=<%s>\n" "$IFS"
        # eval $1\=\(${!1}\);
        read -ra "$1" <<<"${!1}";
}

animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "NAME: %s\n" "${animal_list[@]}"

echo

people_list="alvin|baron|caleb|doug"
delim_to_array "people_list"
printf "NAME: %s\n" "${people_list[@]}"

As you can see, the IFS is set only inside the function, it is not changed permanently, and therefore it does not need to be re-set to its old value. Additionally, the second call "people_list" to the function takes advantage of the default value of IFS, there is no need to set a second argument.

Warnings 01:

Warnings 02:

Yes, read comes with it's own share of «dragons». 

  • Always use the -r option, it is very hard for me to think of a condition where it is not needed.
  • The read command could get only one line. No multiple lines values (ok in 99% of cases).
  • If IFS value contains an space, leading and trailing spaces will be removed. Well, the complete description should include some detail about the tab, but I'll skip it.
  • Do not pipe | data to read, it will not work. Well, there are some workarounds, but, again, I'll skip the detail.

I didn't mean to include the warnings and problems of read, but by popular request, I had to include them, sorry.

Including a better setting of var IFS .
Source Link
user79743
user79743
#!/bin/bash

function delim_to_array() {
    local VarName=$1 

 delim=$2 oifs=$IFS;
  local IFS="$2";
    IFS="$delim";printf "inside  IFS=<%s>\n" "$IFS"

    echo "inside  var    $VarName"
    echo "inside  list = ${!VarName}"

    echo a\=\(${!VarName}\)
    eval a\=\(${!VarName}\)
    printf "in  <%s> " "${a[@]}"; echo

    eval $VarName\=\(${!VarName}\)
    IFS=$oifs;
}

animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","

printf "out <%s> " "${animal_list[@]}"; echo
printf "outside IFS=<%s>\n" "$IFS"

# Now we can use animal_name as an array
for animal in "${animal_list[@]}"; do
    echo "NAME: $animal"
done
$ ./so-setvar.sh
inside  IFS=<,>
inside  var    animal_list
inside  list = anaconda, bison, cougar, dingo
a=(anaconda  bison  cougar  dingo)
in  <anaconda> in  <bison> in  <cougar> in  <dingo> 
out <anaconda> out <bison> out <cougar> out <dingo> 
outside IFS=< 
>
NAME: anaconda
NAME: bison
NAME: cougar
NAME: dingo

Note that the real change was executed in the last eval of the function. 
That's it, done. The var now has an array of values.

Also, the value of IFS is set as local to the function which makes it return to the value it had before executing the function without any additional work. Thanks to Peter Cordes for the comment on the original idea.

That ends the explanation, hope its clear.

If we remove all the otherun-needed lines to leave only onethe core eval, do notonly create a new variables inside the function and we make the change ofvariable for IFS outside of the function, we reduce the function to its minimal expresion:

delim_to_array() {
    local IFS="${2:-$' :|'}"
    eval $1\=\(${!1}\);  
}

Setting the value of IFS as a local variable, allows us to also set a "default" value for the function. Whenever the value needed for IFS is not sent to the function as the second argument, the local IFS takes the "default" value. I felt that the default should be space ( ) (which is always an useful splitting value), the colon (:), and the vertical line (:). Any of those three will split the values. Of course, the default could be set to any other values that fit your needs.

It is scary how much change is expressed in so little code.

 
#!/bin/bash

delim_to_array() {
        local IFS="${2:-$' :|'}"
        # printf "inside  IFS=<%s>\n" "$IFS"
        eval $1\=\(${!1}\);  
}

animal_list="anaconda, bison, cougar, dingo"
IFS="," delim_to_array animal_list
"animal_list" ","
forprintf animal"NAME: in%s\n" "${animal_list[@]}"; do"
    echo "NAME: $animal"
doneecho

# And reuse this in several places to converted lists to arrays
people_list="alvin|baron|caleb|doug"
IFS="|" delim_to_array people_list

# Now I want to treat animal_name as an array"people_list"
forprintf person"NAME: in%s\n" "${people_list[@]}"; do
    echo "NAME: $person"
done"

As you can see, the IFS is set only to callinside the function, it is not changed permanently, and therefore it does not need to be re-set to its old value. Of courseAditionally, setting and re-setting IFS inside the second call "people_list" to the function takes advantage of the default value of IFS, there is also possibleno need to set a second argument.


« Here be Dragons » ¯\(ツ)


#!/bin/bash

function delim_to_array() {
    local VarName=$1 delim=$2 oifs=$IFS;

    IFS="$delim";
    echo "inside var    $VarName"
    echo "inside list = ${!VarName}"

    echo a\=\(${!VarName}\)
    eval a\=\(${!VarName}\)
    printf "in  <%s> " "${a[@]}"; echo

    eval $VarName\=\(${!VarName}\)
    IFS=$oifs;
}

animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","

printf "out <%s> " "${animal_list[@]}"; echo

# Now we can use animal_name as an array
for animal in "${animal_list[@]}"; do
    echo "NAME: $animal"
done
$ ./so-setvar.sh
inside var    animal_list
inside list = anaconda, bison, cougar, dingo
a=(anaconda  bison  cougar  dingo)
in  <anaconda> in  <bison> in  <cougar> in  <dingo>
out <anaconda> out <bison> out <cougar> out <dingo>
NAME: anaconda
NAME: bison
NAME: cougar
NAME: dingo

Note that the real change was executed in the last eval of the function. That's it, done. The var now has an array of values.

That ends the explanation, hope its clear.

If we remove all the other lines to leave only one eval, do not create new variables inside the function and we make the change of IFS outside of the function, we reduce the function to its minimal expresion:

delim_to_array() { eval $1\=\(${!1}\); }

It is scary how much change is expressed in so little code.

#!/bin/bash

delim_to_array() { eval $1\=\(${!1}\); }

animal_list="anaconda, bison, cougar, dingo"
IFS="," delim_to_array animal_list

for animal in "${animal_list[@]}"; do
    echo "NAME: $animal"
done

# And reuse this in several places to converted lists to arrays
people_list="alvin|baron|caleb|doug"
IFS="|" delim_to_array people_list

# Now I want to treat animal_name as an array
for person in "${people_list[@]}"; do
    echo "NAME: $person"
done

As you can see, the IFS is set only to call the function, it is not changed permanently, and therefore it does not need to be re-set to its old value. Of course, setting and re-setting IFS inside the function is also possible.

#!/bin/bash

function delim_to_array() {
    local VarName=$1 

    local IFS="$2";
    printf "inside  IFS=<%s>\n" "$IFS"

    echo "inside  var    $VarName"
    echo "inside  list = ${!VarName}"

    echo a\=\(${!VarName}\)
    eval a\=\(${!VarName}\)
    printf "in  <%s> " "${a[@]}"; echo

    eval $VarName\=\(${!VarName}\)
}

animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","

printf "out <%s> " "${animal_list[@]}"; echo
printf "outside IFS=<%s>\n" "$IFS"

# Now we can use animal_name as an array
for animal in "${animal_list[@]}"; do
    echo "NAME: $animal"
done
$ ./so-setvar.sh
inside  IFS=<,>
inside  var    animal_list
inside  list = anaconda, bison, cougar, dingo
a=(anaconda  bison  cougar  dingo)
in  <anaconda> in  <bison> in  <cougar> in  <dingo> 
out <anaconda> out <bison> out <cougar> out <dingo> 
outside IFS=< 
>
NAME: anaconda
NAME: bison
NAME: cougar
NAME: dingo

Note that the real change was executed in the last eval of the function. 
That's it, done. The var now has an array of values.

Also, the value of IFS is set as local to the function which makes it return to the value it had before executing the function without any additional work. Thanks to Peter Cordes for the comment on the original idea.

That ends the explanation, hope its clear.

If we remove all the un-needed lines to leave only the core eval, only create a new variable for IFS, we reduce the function to its minimal expresion:

delim_to_array() {
    local IFS="${2:-$' :|'}"
    eval $1\=\(${!1}\); 
}

Setting the value of IFS as a local variable, allows us to also set a "default" value for the function. Whenever the value needed for IFS is not sent to the function as the second argument, the local IFS takes the "default" value. I felt that the default should be space ( ) (which is always an useful splitting value), the colon (:), and the vertical line (:). Any of those three will split the values. Of course, the default could be set to any other values that fit your needs.

It is scary how much change is expressed in so little code.

 
#!/bin/bash

delim_to_array() {
        local IFS="${2:-$' :|'}"
        # printf "inside  IFS=<%s>\n" "$IFS"
        eval $1\=\(${!1}\); 
}

animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "NAME: %s\n" "${animal_list[@]}"

echo

people_list="alvin|baron|caleb|doug"
delim_to_array "people_list"
printf "NAME: %s\n" "${people_list[@]}"

As you can see, the IFS is set only inside the function, it is not changed permanently, and therefore it does not need to be re-set to its old value. Aditionally, the second call "people_list" to the function takes advantage of the default value of IFS, there is no need to set a second argument.


« Here be Dragons » ¯\(ツ)


Source Link
user79743
user79743
Loading