Help with Bash piped while-read and a read user input at the same time

Hi
I am new to writing script and want to use a Bash Piped while-read and read from user input.

if something happens on server.log then do while loop or if something happend on user input then do while loop.

Pseudocode something like:
tail -n 3 -f server.log | while read serverline || read -p # -e promptline
do
if something happend on promptline then do this
if something happend serverline then do this

I appreciate all help.
Thanks

Read user input from /dev/tty, to avoid accidentally reading input from the pipe or file instead. /dev/tty will always be the current terminal, if any.

... | while read LINE
do
        printf "prompt: "
        read USERINPUT </dev/tty
done
2 Likes

Thanks, will this lock the while loop and wait for user input? I want the script to react and run the wile loop if something happens in the server.log file as well.

Yes, it will block.

Since it's bash, you could have it time out eventually if the user doesn't type anything ( read -t 1 userinput < /dev/tty )

1 Like

I can not get it to work as it builds on that it comes data on file 'server.log' for the program to get to the prompt and there is no steady stream of data on that file.

I don't think I really understand what you're trying to do, then. Could you give a little pseudocode?

1 Like

No worries, I explain in greater detail. It might very well be that I do not understand you.
I have a process A that depends on two processes.
Process B is the one assigned to the file server.log
Process C is if a user writes something

If data comes from B or C should process A run
Pseudocode something like:

tail -n 3 -f server.log | while read serverline || read userinput
do
if something happend on userinput then do this
if something happend serverline then do this

I do not know how to write something like this:

tail -n 3 -f server.log | while read serverline | while read -t 1 userinput < /dev/tty

A problem with another solution like this is that it is locked on the tail line

tail -n 3 -f server.log | while read serverline
do
        printf "prompt: "
        read -t 1 USERINPUT </dev/tty

And the same problem with a solution like this is that it is locked on the tail line

do
        printf "prompt: "
        read -t 1 USERINPUT </dev/tty
        tail -n 3 -f server.log | while read serverline
        do

I hope I explained my problem better and if not do not hesitate to ask again.

If this were C, you'd set the file descriptors non-blocking and use the select() system call to tell when one file descriptor or the other has data ready to be read at any particular time.

The closest shell has to non-blocking I/O is terminal timeouts, which really isn't the same thing.

You could read both and cram them into a fifo I suppose... A bit of a kludge but fifos can combine two writers into one reader. As long as you write entire lines to the fifo, they ought to come out in the order you wrote them.

(That's why I didn't use cat to read from the terminal. It might not care about line boundaries. It might even block until it has x lines input or EOF...)

#!/bin/sh

# Create fifo
mkfifo /tmp/$$.fifo

# Read the logfile in the background, cram into fifo
( tail -f server.log > /tmp/$$.fifo ) &
P1="$!"

# Read from the fifo and use it here, in the background
(       while [ -e /tmp/$$.fifo ] && IFS="" read LINE
        do
                echo "got line $LINE"

                [ "$LINE" = "USER quit" ] && break
        done < /tmp/$$.fifo

        echo "Quitting" >&2

        rm /tmp/$$.fifo
        kill "$P1"
        kill "$$"       ) &

# Save its PID in case we need to kill it
P2="$!"

# Guarantee we kill all our background processes on exit
trap "rm -f /tmp/$$.fifo ; kill $P1 $P2 ; wait" EXIT

# Read from terminal, write to fifo.
# this MUST happen in the foreground!
while [ -e "/tmp/$$.fifo" ] && IFS="" read LINE
do
        echo "USER $LINE"
done > /tmp/$$.fifo

This will spam error messages when you quit about killing nonexistent processes, and things dying uncleanly... but if it didn't at least try to kill them there's situation it could leave things hanging around.

---------- Post updated at 12:46 PM ---------- Previous update was at 12:23 PM ----------

Here's a less kludgy version which avoids the spammy messages, uses a pipe instead of a fifo, and puts the combined read at the bottom:

#!/bin/bash

(       tail -f server.log &
        trap "" SIGPIPE

        while read LINE && echo "USER $LINE"
        do
                true
        done

        echo "SIGPIPE happened I guess, quitting" >&2

        kill $!
        wait 
) | while read INPUT
do
        [ "$INPUT" = "USER quit" ] && break
        echo "got $INPUT"
done

'tail -f' runs in the background, writing to standard output -- i.e. the pipe -- by itself.

I trap SIGPIPE so that the subshell doesn't get killed instantly when the 'while read INPUT' loop quits. That might prevent it from killing the background 'tail -f', or at the very least, print warning messages about the subshell being killed abnormally.

Meanwhile the other while loop reads from the terminal and echoes lines straight back out to standard output -- i.e. the pipe again, the same pipe.

The subshell should detect when the main reading loop quits by detecting when 'echo' fails, allowing it to kill the 'tail -f' cleanly before terminating normally.

1 Like

Thanks, just what I needed to know!