Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

Is there a way to have the output of a subprocess (something other than 'sleep' below) be dovetailed with the output from a foreground command-loop? For example:

while true
do
    echo "updating screen..."
    sleep 3 & # Replace with command which takes some time and updates the screen
    read -s -n 1 -p "Input: " input
    case "$input" in
    q)
        exit
    ;;
    f)
        echo 'foo'
    ;;
    esac
done
share|improve this question
    
You just need to background the sleep. Running sleep in the background is obviously pointless, and running a command that outputs to the terminal will interfere with the prompt (but not preventing read from receiving the input). From my interpretation of what you are doing, the user should just be using Ctrl+c (SIGINT), which you can also trap if you need to. –  jordanm Dec 31 '11 at 21:02
    
No, because I don't want it to just quit on inputting q. I also want it to do other stuff based on the character. –  Tyilo Jan 1 '12 at 13:29
    
@jordanm When put into the background, the process will be stopped on a SIGSTTOU when it attempts to write to the terminal. Need to avoid that. –  Arcege Jan 1 '12 at 15:30

1 Answer 1

The shell makes assumptions about multiprocessing; the first and most important is: one program should control the terminal at a time, otherwise input (and output) will get muddled. What if the program that you put in place of 'sleep' wants to get input from the terminal? Where does the keyboard input get sent? To the subprocess ('sleep') or to the read statement?

From this you need to assume that the subprocess ('sleep') will not get input. You also have to wait until both the command loop (processing 'q' or 'f') and the subprocess has finished. I would suggest writing this is something other than a shell script, e.g. python, to get around the shell's assumptions; but as I show, it can be done in Bourne (or ksh or bash or zsh) shell too.

#!/usr/bin/python
import os, select, subprocess, sys
devnull = open('/dev/null', 'a')
# this is the same as "sleep 3 </dev/null > pipefile &" but will handle
# processing output at the same time
p = subprocess.Popen(
    ['/bin/sh', '-c',
 'i=0; while [ $i -lt 30 ]; do echo $i; sleep 2; i=`expr $i + 1`; done' ],
    stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
command_done = False
try:
    # endless loop until both input and output are done
    while True:
        inputs = []
        # only send input to our command loop
        if not command_done:
            sys.stdout.write('Input: ')
            sys.stdout.flush()
            inputs.append(sys.stdin)
        if p.returncode is None: # still not finished
            p.poll()
            inputs.append(p.stdout)
        outputs = []
        if not inputs and not outputs: # both are done
            break # exit while loop
        #print inputs, outputs
        r, w, x = select.select(inputs, outputs, [])
        #print 'r=', r, 'w=', w, 'x=', x
        # input from the user is ready
        for file in r:
            if file is sys.stdin:
                input = file.read(1)
                if input == 'q':
                    command_done = True
                    if p.returncode is None:
                        os.kill(p.pid, 15)
                elif input == 'f':
                    sys.stdout.write('foo\n')
            # the subprocess wants to write to the terminal too
            else:
                input = file.readline()
                sys.stdout.write(input)
finally:
    if p.poll():
        try:
            print
            os.kill(p.pid, 15)
        except OSError:
            pass

You could do this in a shell script, but the input/output wouldn't be as nicely integrated.

#!/bin/bash
mkfifo /tmp/mergedout.p
( i=0; while [ $i -lt 30 ]; do echo $i; sleep `expr 30 - $i`; i=`expr $i + 1`; done ) </dev/null >/tmp/mergedout.p 2>&1 &
pid=$!
exec 3</tmp/mergedout.p
done=false
trap 'rm /tmp/mergedout.p' 0
while [ -n "$pid" -a $done = false ]; do
    if [ $done = false ]; then
        echo -n "Input: "
        read -t 0.1 -s -r -n 1
        case $REPLY in
            q) done=true; if [ -n "$pid" ]; then kill $pid; fi;;
            f) echo foo;;
        esac
    fi
    if [ -n "$pid" ]; then
        kill -0 $pid 2>&-
        if [ $? -ne 0 ]; then
            echo "$pid terminated"
            wait $pid
            pid=""
            exec 3<&-
        else
            read -t 0.1 -u 3 -r
            echo "reading from fd3: X${REPLY}X $?"
            if [ -n "$REPLY" ]; then
                echo "$REPLY"
            fi
        fi
    fi
    sleep 0.5
done

Myself, the python is a bit more clear and more 'dovetailed', but for the most part, these can be done either way.

share|improve this answer
    
Bash output: mknod: node type must be 'b' or 'c' or 'w'. –  Tyilo Jan 1 '12 at 18:42
    
What OS are you using, @Tyilo? I edited to change to use mkfifo which I guess is the 'new standard'. –  Arcege Jan 1 '12 at 19:09
    
done should probably also be set to false at the top. –  Tyilo Jan 1 '12 at 19:47
    
Also you need to replace [ -n $pid -a $done = false ] with [[ -n $pid && $done = false ]] as my [ can't handle so many arguments and there is also something wrong with line 12: test.sh: line 12: kill: (16810) - No such process. My OS is Mac OS X Lion. –  Tyilo Jan 1 '12 at 19:53
    
You can use bash syntax if you like. I fixed the quoting. The kill 'error' is a red herring; kill -0 is a test to see if the process is still there - it's not, so the error gets sent to stderr - the return code is what we are looking for. Can just close stderr for the kill statement. –  Arcege Jan 1 '12 at 20:17

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.