5

I usually use if [ -t 0 ] to test stdin and if [ -t 1 ] to see if stdin and stdout are TTY's, and if they aren't I assume that they're pipes. I recently learned that that is a bad assumption:

function context()
{
    if [ -t 0 ]
    then
        if [ -t 1 ]
        then
            echo "no pipes"
        else
            echo "pipe out only"
        fi
    else
        if [ -t 1 ]
        then
            echo "pipe in only"
        else
            echo "pipe in and out"
        fi
    fi
}

echo "No Loop:"        # these cases work as desired
context
echo 'f' | context
echo 'f' | context | cat
context | cat

echo
echo "Loop:"
echo $'foo
bar' | while read x
do
    context             # I want this to say "no pipes"
    echo 'f' | context
done

The output of the above bash is this:

No Loop:
no pipes
pipe in only
pipe in and out
pipe out only

Loop:
pipe in only
pipe in only
pipe in only
pipe in only

How can I change the definition of context to so that the 5th and 7th calls print "no pipes"?

That is, rather than testing whether stdin is not a terminal (which causes the problem) I'd like to test whether stdin for this particular function happens to be a pipe. I see that -p checks to see if it's a named pipe, but I want this for anonymous pipes.

Edit

The actual use case involves a function we'll call theDb in the place of context. Ideally the two cases below would be equivalent regardless of context:

cat file_with_some_sql | theDb
theDb "sql goes here"
6
  • 1
    why should that say "no pipes" while its stdin is, in fact, a pipe? but, yes, it's a silly assumption that everything not a tty is a pipe -- what about regular files? character devices? (eg. context <<<'' >/dev/null). Commented Sep 9, 2019 at 22:33
  • 1
    Is this a purely academic question or is there a real problem you are trying to solve by checking this? The academic question is interesting, but the broad answer is "no, there is no easy way." While there may be an academic solution that is of interest for learning purposes, it's not likely to have practical value in writing a simple shell script. Commented Sep 9, 2019 at 22:37
  • @Wildcard there's a real problem. My .bashrc contains functions that wrap calls to mysql (they supply hostnames, creds, etc), sometimes I I have queries in a file, so I pipe them to the desired function. Other times I type the queries on the command line. This works great, until I include it in a loop. Commented Sep 9, 2019 at 22:40
  • 2
    You seem to assume that in eg. cat ~/.bashrc | while read p; do tr a e; done the output of cat is not piped to tr. That assumption is false. Commented Sep 9, 2019 at 22:44
  • 2
    But fwiw, in linux test -p /dev/stdin works with "anonymous" pipes: echo yup | if [ -p /dev/stdin ]; then echo yes; fi Commented Sep 9, 2019 at 22:45

1 Answer 1

5

The reason "pipe in only" appears four times is in fact because the context function is working correctly, and the echo $'foo bar' | while read x etc. is a pipe -- everything in the while loop is getting input from a pipe. To get the desired output, change the while loop to a for loop, and don't pipe anything to that for loop:

for x in foo bar; do  context ;  echo 'f' | context; done

Output:

no pipes
pipe in only
no pipes
pipe in only

To show where the OP code went wrong, just pipe something into the for loop, and see what happens:

echo | for x in foo bar; do  context ;  echo 'f' | context; done

Output:

pipe in only
pipe in only
pipe in only
pipe in only

A novice might (incorrectly) suppose that while read x consumes all of standard input, but that is not necessarily so. Consider this example, with a read inside a while loop:

printf '%s\n' foo bar baz buzz | 
while read x; do echo $x; read y; echo ${y^^} ;done

Output:

foo
BAR
baz
BUZZ

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.