3

Simple script that does not work as expected. I have spent hours of hairpulling trying discover the right combination of escaping and quoting to make it work, but I have not yet succeeded.

I have an array in K that I want to loop through from an embedded expect script.

send_user '${list[4]}'

works as expected, printing the fourth element of the array on the console.

Looping the statements:

incr i 1
send_user "\$i"

work as expected, printing an incrementing interger value 12345... etc.

But, here's the trick.

send_user "${list[$i]}"

does NOT work. No matter what the value of $i, it always returns the first array element, I cannot loop thru the array.

Yes, I know about escaping the $ with a backslash, and the difference between the effects of single quotes and double quotes, etc. I have tried every combination. Like I said, hours and hours. I either get an error, nothing, or the first element of the array (as in the example) without regard to the actual value of $i.

I also tried moving the array directly into the expect script portion, both as an associative array and as a list. Nada. The problem is not reading the data in the array, it is retrieving that array or list data inside the embedded expect code using a variable as opposed to a discrete number.

Before abandoning this approach and rewriting a long and complicated test tool in another way, I thought I would appeal to the assembled multitudes.

I am embedding a demonstrative test script below.

Thanks to all for any suggestions.

Nathan

$ cat test24.sh
list=(zero one two three four)

expect <<- EXPECT_DONE

exp_internal 1

send_user '${list[4]}'

set i 0
send_user "\$i"

send_user "${list[$i]}"
incr i 1
send_user "\$i"
send_user "$list[$i]"
incr i 1
send_user "\$i"
send_user "${list[ "$i" ]}"
incr i 1
send_user "\$i"
send_user ${list[$i]}
incr i 1
send_user "\$i"
send_user "${list[$i]}"
send_user ${list[4]}
send_user "\n"

EXPECT_DONE

echo "Script done."


$ ./test24.sh
'four'0zero1zero2zero3zero4zerofour
Script done.
4
  • Add a shebang and then paste your script there: shellcheck.net Commented Feb 17, 2019 at 16:16
  • 1
    @Cyrus Most of this "shell" script is just an embedded Tcl script. Commented Feb 17, 2019 at 17:39
  • @Nathan You have a typo; you use $list[i], not ${list[i]}, at one point. This is equivalent to ${list[0]}[i], that is, the first element of the array followed by the literal string [i]. Commented Feb 17, 2019 at 17:40
  • (Not that the typo matters, because you are trying to index the shell array with a Tcl variable.) Commented Feb 17, 2019 at 17:48

2 Answers 2

2

The problem is that any expansions of the shell array have to occur before the Tcl code starts executing, and you are trying to index the shell array with a Tcl variable. It's better to pass the values in the array as arguments to the Tcl script, rather than dynamically generating the script with embedded references to the array. This lets you single-quote the here document to avoid any additional escaping inside the script as well.

expect -f - "${list[@]}" <<- 'EXPECT_DONE'

exp_internal 1

send_user [lindex argv 4]

set i 0
send_user "$i"

send_user [lindex argv $i]
incr i 1
send_user $i
send_user [lindex argv $i]
incr i 1
send_user $i
send_user [lindex argv $i]
incr i 1
send_user $i
send_user [lindex argv $i]
incr i 1
send_user $i
send_user [lindex argv $i]
send_user [lindex argv 4]
send_user "\n"

EXPECT_DONE

(Apologies for any errors in the Tcl script; it's been a few decades since I've written any Tcl code.)

Sign up to request clarification or add additional context in comments.

Comments

2

You are misunderstanding when/where expansion happens.

incr i 1

tells expect to add 1 to its own variable i. However the shell variable i never sees this.

Similarly:

send_user "${list[$i]}"

is expanded by the shell before expect sees the command. The shell has no variable named i so $i expands to 0 (technically to nothing, but when used as a list subscript it will be treated as 0) and ${list[0]} becomes zero. Expect receives:

send_user "zero"

One possible way to restructure your code might be to move all the processing inside the expect. Note the single-quoting of 'EXPECT_DONE' so that the shell doesn't change any of the input:

#!/bin/bash

expect <<- 'EXPECT_DONE'

    set list [list zero one two three four]

    exp_internal 1

    for {set i 0} {$i < [llength $list]} {incr i} {
        send_user "$i"
        send_user [lindex $list $i]
    }

    send_user "\n"

EXPECT_DONE

echo "Script done."

Another is to move all the processing into bash:

#!/bin/bash

{
    list=(zero one two three four)

    echo "exp_internal 1"

    i=0
    while [[ $i -le ${#list} ]]; do
        echo "send_user \"$i\""
        echo "send_user \"${list[$i]}\""
        : $((i++))
    done

    echo "send_user \"\\n\""

} | expect

echo "Script done."

4 Comments

There is no shell variable i. i only exists in the expect portion. This is a very long and complex bash script with just a little bit of expect, which I distilled down to a bare essential example for posting. Perhaps I made mistakes in doing so, but other than the array, the expect makes no use of anything from bash, and the array is read-only, it passes nothing back.
The first sample above does seem at first blush to accomplish what I want, although translating the sample code back into the big script may yet reveal problems. I had previously tried this, moving the array into a list inside expect and ran into other problems. Thanks for the assist, I will work on this. It looks promising.
Per chepner's answer, you could invoke expect with the bash list as arguments, then the tcl list argv will contain the elements. Or you could { echo "set list [list ${list[@]}]"; cat <<- 'EOD' ... } | expect to turn the bash array into a tcl list. (although watch out for special characters in the elements)
Thank you very much!!! Second part of code: run expect code through bash script is very useful to work with arrays.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.