1

I would like to know how can I use nested variables in a coprocess.For example, I can use a nested variable in the following way normally.

$ a=b
$ b=lol
$ echo ${!a}
lol

But I can't do this for a coprocess, at least in shell script:

$ coproc a { while :;do echo lol;done; }
[1] 15827
$ b=a
$ read test <&${!b[0]}
$ echo $test
lol

This works, but this

#!/bin/bash

send_message() { echo "$2">$1; }
question() {
    TARGET="$1"
    echo "Why hello there.
Would you like some tea (y/n)?"
    read answer
    [[ $answer =~ ^([yY][eE][sS]|[yY])$ ]] && echo "OK then, here you go: http://www.rivertea.com/blog/wp-content/uploads/2013/12/Green-Tea.jpg" || echo "OK then."
    until [ "$SUCCESS" = "y" ] ;do
        send_keyboard "$TARGET" "Do you like Music?" "Yass!" "No"
        read answer
        case $answer in
            'Yass!') echo "Goody!";SUCCESS=y;;
            'No') echo "Well that's weird";SUCCESS=y;;
            *) SUCCESS=n;;
        esac
    done
}
startproc() {
    local copname="$1"
    local TARGET="$2"
    coproc $copname { question "$TARGET" 2>&1; }
    outproc "$copname" "$TARGET"
}
inproc() {
    local coproc="$1"
    shift
    echo "$@" >&"${!coproc[1]}"
}

outproc() {
    local coproc="$1"
    local TARGET="$2"
    while read -t 1 -u "${!coproc[0]}" line; do send_message "$TARGET" "$line"; done
}
startproc a test
inproc a y

Does not:

~ $ bash -vx t.sh
#!/bin/bash
send_message() { echo "$2">$1; }
question() {
        TARGET="$1"
        echo "Why hello there.
Would you like some tea (y/n)?"
        read answer
        [[ $answer =~ ^([yY][eE][sS]|[yY])$ ]] && echo "OK then, here you go: http://www.rivertea.com/blog/wp-content/uploads/2013/12/Green-Tea.jpg" || echo "OK then."
        until [ "$SUCCESS" = "y" ] ;do
                send_keyboard "$TARGET" "Do you like Music?" "Yass!" "No"
                read answer
                case $answer in
                        'Yass!') echo "Goody!";SUCCESS=y;;
                        'No') echo "Well that's weird";SUCCESS=y;;
                        *) SUCCESS=n;;
                esac
        done
}
startproc() {
        local copname="$1"
        local TARGET="$2"
        coproc $copname { question "$TARGET" 2>&1; }
        outproc "$copname" "$TARGET"
}
inproc() {
        local coproc="$1"
        shift
        echo "$@" >&"${!coproc[1]}"
}

outproc() {
        local coproc="$1"
        local TARGET="$2"
        while read -t 1 -u "${!coproc[0]}" line; do send_message "$TARGET" "$line"; done
}
startproc a test
+ startproc a test
+ local copname=a
+ local TARGET=test
+ outproc a test
+ local coproc=a
+ local TARGET=test
+ read -t 1 -u '' line
t.sh: line 34: read: : invalid file descriptor specificationinproc a y
+ inproc a y
+ local coproc=a
+ shift
+ echo y
t.sh: line 28: "${!coproc[1]}": Bad file descriptor
~ $ + question test

~ $

Thanks in advance.

Danogentili
  • 664
  • 7
  • 13

1 Answers1

1

I can't find any documentation which would support the behaviour, so I'm inclined to think it is a bug.

The coproc command does not expand $name in

coproc $name COMMAND

Consequently, it ends up creating a coprocess array called, literally, $name. That's not a legal array name, but since coproc is working at a relatively low level, it succeeds in creating the array.

Eg:

$ echo $BASH_VERSION
4.3.11(1)-release
$ coproc $name { echo hello; }
[1] 23424
$ declare -p '$name'
declare -a $name='([0]="63" [1]="60")'
[1]+  Done                    coproc $name { echo hello; }

So the indirect reference doesn't work because the coproc array is not called what you think it is.

I suppose you could work around by using eval, but you'd need to get the quoting right on the command to be coproc'd. I'd suggest defining a function to make it easier.


By the way, in ${!coproc[1]}, the subscript [1] is applied before the !, so it means "the variable whose name is ${coproc[1]}, rather than "element 1 of the array whose name is $coproc. That works with 0 because ${x} and ${x[0]} mean exactly the same thing, regardless of whether x is a scalar or a (non-associative) array, but that is really a misleading coincidence. You need to include the subscript in the variable through which you are indirecting:

cp1=$coproc[1]   # *Not* an array lookup, just a simple substitution
cmd >&${!cp1}
rici
  • 234,347
  • 28
  • 237
  • 341
  • Thanks! This really helped me. Now all I have to do is use some DHHs (Dangerous and Horrible Hacks, such as eval ;) – Danogentili Jan 03 '16 at 17:01
  • @Danogentili: Yeah, I just added a suggestion about that, since I don't see any other obvious work-around – rici Jan 03 '16 at 17:02
  • I also noticed another weird behavior: I can't redirect the output of a program to a nested file descriptor (but I can read it) – Danogentili Jan 03 '16 at 17:14
  • @Danogentili When you say "nested", do you mean "using an indirect reference"? – rici Jan 03 '16 at 17:15
  • Yes. I mean when I execute the following command I get an ambiguous redirect error: coproc a { bash; }; b=a; echo "echo lol">&${!b[1]} – Danogentili Jan 03 '16 at 17:18
  • @Danogentili That's a separate question. Short answer: ${!b[1]} does not mean element one of ${!b}. It means "the value of the variable whose name is ${b[1]}". Use a nameref (declare -n) – rici Jan 03 '16 at 17:20
  • Oh, OK. So I guess I have to use eval again. – Danogentili Jan 03 '16 at 17:21
  • 1
    Or simply: coproc a { bash; }; b="a[1]"; echo "echo lol">&${!b} – Danogentili Jan 03 '16 at 17:31
  • @Danogentili: added that comment to the answer. – rici Jan 03 '16 at 18:11