Search code examples
bashcoproc

bash coproc - unexpected behavior


Followup to

Given that the obvious use of coproc does not work as I expected, as seen in:

$ cat test.sh
coproc cat auto/etc/build.cfg
while read -u ${COPROC[0]} BRANCH TARGET SVNSRC SVNTAG BUILDTYPE DISTTYPE DISTARGS
do
    echo hello
done

$ bash -x test.sh
+ read -u 63 BRANCH TARGET SVNSRC SVNTAG BUILDTYPE DISTTYPE DISTARGS
+ cat auto/etc/build.cfg
+ echo hello
hello
+ read -u BRANCH TARGET SVNSRC SVNTAG BUILDTYPE DISTTYPE DISTARGS
test.sh: line 2: read: BRANCH: invalid file descriptor specification

Question: Why does the coproc go away after the script reads one line of output?


Solution

  • I cannot reproduce:

    bash-4.1 $ cat infile 
    one
    two
    three
    four
    five
    
    bash-4.1 $ cat s.sh 
    coproc cat infile
    while read -u ${COPROC[0]} v; do
      echo "$v"
    done
    
    bash-4.1 $ bash -x s.sh 
    + read -u 63 v
    + cat infile
    + echo one
    one
    + read -u 63 v
    + echo two
    two
    + read -u 63 v
    + echo three
    three
    + read -u 63 v
    + echo four
    four
    + read -u 63 v
    + echo five
    five
    + read -u 63 v
    + echo ''
    
    + read -u 63 v
    

    Edit: I did reproduce it like this:

    bash-4.1 $ cat s.sh 
    coproc cat infile
    
    sleep 1
    
    while read -u ${COPROC[0]} v; do
      echo "$v"
    done
    
    bash-4.1 $ bash  -x s.sh 
    + sleep 1
    + cat infile
    + read -u v
    s.sh: line 5: read: v: invalid file descriptor specification
    

    Edit: See comments below.


    It seems that the co-process times out quickly ... May be your system is slow :)

    No, the command executed as co-process is too quick, if you slow it down, it works:


    bash-4.1 $ cat s.sh 
    coproc while read -r; do
      printf '%s\n' "$REPLY"
      sleep 1
    done < infile
    
    sleep 1
    
    while read -u ${COPROC[0]} v; do
      echo "$v"
    done
    
    bash-4.1 $ bash s.sh 
    one
    two
    three
    four
    five
    

    Anyway, I believe that this test case is not appropriate. You need a co-process when you need a two-way pipe (i.e. you need to chat with the co-process). You can use a single database connection (the database connections are resource expensive) and go back and forth with your queries and shell code.

    Edit (see comments below). The issues related to the stdin buffering could be worked around with some non standard tools (in this case stdbuf is used (part of recent versions of the GNU coreutils, I believe):

    ~/t$ cat s
    coproc stdbuf -oL -i0 mysql
    
    printf '%s;\n' 'show databases' >&${COPROC[1]}
    
    printf '\n\nshowing databases, fisrt time ...\n\n\n'
    
    while read -t3 -u${COPROC[0]}; do
      printf '%s\n' "$REPLY"
      [[ $REPLY == test ]] && {
        printf '%s\n' 'test found, dropping it ...'
        printf '%s;\n' 'drop database test' >&${COPROC[1]}
        }
    done
    
    printf '\n\nshowing databases, second time ...\n\n\n'
    
    
    printf '%s;\n' 'show databases' >&${COPROC[1]}
    
    while read -t3 -u${COPROC[0]}; do
      printf '%s\n' "$REPLY"
    done
    
    
    printf '%s\n' quit >&${COPROC[1]}
    

    Output:

    ~/t$ bash s
    
    
    showing databases, fisrt time ...
    
    
    Database
    information_schema
    mysql
    sakila
    test
    test found, dropping it ...
    world
    
    
    showing databases, second time ...
    
    
    Database
    information_schema
    mysql
    sakila
    world
    

    I realize this approach has many drawbacks ...