0

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"

Christian Hick
  • 331
  • 4
  • 12

1 Answers1

2

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".

Kamil Maciorowski
  • 69,815
  • 22
  • 136
  • 202
  • Thanks Kamil, so I tried the following. I changed to `<<<"$emp,"` and rebuilt the code to `for i in "${array1[@]}"; do validate "$i"; …`. Then I wrote a function `validate(){ local "$1" }` and placed it at the top of the script. (I tried this with and without quotation. This however only works if I provide two fields.There is still a problem with this. The output is also on two separate lines and not the same line. What I really want is 2,3 on the same line. – Christian Hick Jul 17 '20 at 06:19
  • @ChristianHick Of course there's still a problem. A function that only calls `local` and exits is a no-op. (Besides `validate(){ local "$1" }` is syntactically invalid; and `local "$1"` is not `local var2` you need.) The problem is some piece of code reuses `var2`. If we think `local` can help then we need to *move this piece of code to a function* and use `local` *there*. I named the function `validate` because it should contain your code that *validates*. Maybe I will provide an example in few hours; no time now. – Kamil Maciorowski Jul 17 '20 at 07:28
  • @ChristianHick I added an example to the linked answer. My intention is to let it deal with the issue of a variable being reused. Then let the above answer deal mainly with the issue of `read` reading fewer fields than expected. If you can't get the output to be in the same line, then it's *yet another* issue. This site works best in "one issue – one question" regime. If I were you I would ask another question. I would make it as MCVE as I can: hopefully without `zenity`, `awk`, `read`. What have you tried? Here you `echo "$out"` without even trying to put `,` after it and to suppress newline. – Kamil Maciorowski Jul 17 '20 at 21:58
  • Thanks Kamil for taking the time to elaborate in the linked answer. I really much appreciate your patience. The solution that I will be going with from your recommendation is to change to ```<<<"$emp,"``` remove ```validate(){ local "$1" }``` and replace ```local var2``` with ```unset var2``` after ```else```. This gives the desired result in all scenarios. I will post an additional question on how to get 2,3 in one line rather than 2 3 in two lines. – Christian Hick Jul 18 '20 at 03:38