Bash – Function with For Loop and Nested If Statement Giving Peculiar Result

bashbash-scripting

I have the following bash function that has me boondoggled. If I enter the following in the zenity boxes…

employeeid = 2
categoryid = 3

I get the following: 2 3

if…

employeeid =
categoryid = 3

After a second zenity window opens I enter 2 and I get the following: 2 3

However when I enter

employeeid = 2
categoryid =

No additional Zenity window opens and I get the following: 2

What I really want to end up with is 2,3 after the tests have run.

Does anyone know what is wrong here?

#!/bin/bash


num(){
        emp=$(echo "$1" | awk -F, -v  OFS=, '{print $1 "," $2}')

        IFS=, read -ra array1 <<<"$emp"
        
        p=$(for i in "${array1[@]}"
        
        do
                
                if [[ "${i}" =~ ^[0-9]+$ ]]; then

                        out="${i}"
                elif
                        [[ "${i}" = NULL ]]; then

                        out="${i}"


                else   local var2

                        until [[ ${var2} =~ ^[0-9]+$ ]] || [[ ${var2} = NULL ]]; do

                                var2="$(zenity --forms --title="table salaries_wages" --text "Add a number"  --separator="," \
                                --add-entry="WARNING! You either forgot to enter or didn't enter a number. Please enter a valid number: ")"

                         done

                                out="${var2}"

        fi

        echo "$out"
        
done)
      
        echo "$p"
}

input="$(zenity --forms --title="table salaries_wages" --text="Add a new salaries_wages entry" --separator="," \
        --add-entry="ENTER employeeid: " \
        --add-entry="ENTER categoryid: ")"

num "$input"

Best Answer

what is wrong here?

Your assumption of how read works is different than how read actually works. Run this code in Bash:

how_many () { IFS=, read -ra array1 <<<"$1"; echo "${#array1[@]}"; }
how_many "2,3"
how_many ",3"
how_many "2,"

You will get 2, 2, 1. The last number stands out. It means that trailing separator (, in this case) is treated by read more like a terminator: the empty field after it is not read into the array and the array ends up being one element too short. If this happens in your code then for i in "${array1[@]}" will run the loop for just the first field.

The fix may be to introduce an extra , as the trailing terminator on purpose. Then read will never read the third field, but it will always read two fields (even if the second one is empty). See the difference when I add an extra ,:

how_many () { IFS=, read -ra array1 <<<"$1,"; echo "${#array1[@]}"; }
how_many "2,3"
how_many ",3"
how_many "2,"
how_many ","

The output is 2 each time.

To fix your code this way use <<<"$emp," instead of <<<"$emp".


If you fix the code then it will misbehave when the else block is run more than once (i.e. when both fields are initially invalid); because you're reusing the var2 variable again.

I guess you used local var2 to avoid this, but local makes the variable local in a function, not in an else block or in a single iteration of a for loop. You're reusing var2 in the same instance of the num function.

You're calling the function once. Inside it var2 is always the same var2, local only makes it distinct from any var2 outside this very invocation of the function. If you used var2 outside of the function, the one in the function would be distinct. If you invoked num more than once, each invocation would use its own distinct var2. Neither of these happens. You're calling the function once and reusing the variable there. When two fields are invalid the variable is used for the first field and then reused for the second field.

But if you rebuild your code so some function (e.g. validate) is called from within the loop:

for i in "${array1[@]}"; do validate "$i"; …

and use local var2 in the validate function, then var2 in each invocation of the function will be distinct. This is how local can help. In each loop validate will be called anew, its local variable(s) will be initialized fresh without any connection to other variable(s) with the same name(s). However you will still be able to reuse a local variable inside the function in a way that breaks something (similarly to how you're currently reusing var2 in the num function).

After I wrote the above, a added an example to the already linked answer.


Note p=$(stuff); echo "$p" is almost equivalent to echo "$(stuff)", which almost always should be just stuff. Please read this answer where it elaborates on var=$(stuff); echo "$var".

Related Question