views:

2094

answers:

3

I want my ksh script to have different behaviors depending on whether there is something incoming through stdin or not:

    (1) cat file.txt | ./script.ksh  (then do "cat <&0 >./tmp.dat" and process tmp.dat)
vs. (2) ./script.ksh (then process $1 which must be a readable regular file)

Checking for stdin to see if it is a terminal[ -t 0 ] is not helpful, because my script is called from an other script.

Doing "cat <&0 >./tmp.dat" to check tmp.dat's size hangs up waiting for an EOF from stdin if stdin is "empty" (2nd case).

How to just check if stdin is "empty" or not?!

+2  A: 

EDIT: You are running on HP-UX

Tested [ -t 0 ] on HP-UX and it appears to be working for me. I have used the following setup:

/tmp/x.ksh:

#!/bin/ksh
/tmp/y.ksh

/tmp/y.ksh:

#!/bin/ksh
test -t 0 && echo "terminal!"

Running /tmp/x.ksh prints: terminal!

Could you confirm the above on your platform, and/or provide an alternate test setup more closely reflecting your situation? Is your script ultimately spawned by cron?


EDIT 2

If desperate, and if Perl is available, define:

stdin_ready() {
  TIMEOUT=$1; shift
  perl -e '
    my $rin = "";
    vec($rin,fileno(STDIN),1) = 1;
    select($rout=$rin, undef, undef, '$TIMEOUT') < 1 && exit 1;
  '
}

stdin_ready 1 || 'stdin not ready in 1 second, assuming terminal'


EDIT 3

Please note that the timeout may need to be significant if your input comes from sort, ssh etc. (all these programs can spawn and establish the pipe with your script seconds or minutes before producing any data over it.) Also, using a hefty timeout may dramatically penalize your script when there is nothing on the input to begin with (e.g. terminal.)

If potentially large timeouts are a problem, and if you can influence the way in which your script is called, then you may want to force the callers to explicitly instruct your program whether stdin should be used, via a custom option or in the standard GNU or tar manner (e.g. script [options [--]] FILE ..., where FILE can be a file name, a - to denote standard input, or a combination thereof, and your script would only read from standard input if - were passed in as a parameter.)

Cheers, V.

vladr
I'm on HP-UX :-(Is there a mean of displaying existing pipes under HP-UX?
Hmmm, yet 'test -t 0' correctly identifies the fd as a terminal on my hpux, even when the test is performed from within a script called by another script (HP-UX HP-A500 B.11.11 U 9000/800 538790518 unlimited-user license)
vladr
Well, test -t 0 works in my tests too, but my script is called by tens of other, some must be doing black magic with file descriptors..My script is to replace an existing one on many servers worldwide, so it must be bullet proof. Your perl solution is great, I'm testing it now. Thanks !
Yes; hoever, if perl is not present, the above will be equivalent to stdin not being ready; in the long run it may still be better to adopt a `cat`-like strategy, i.e. (1) if 0 file arguments are provided assume automatically stdin (even if terminal); (2) if >0 file arguments provided, ignore stdin
vladr
Up to you to pick the solution that best fits your requirements. :) I would personally prefer the `cat`-like approach as more predictable and more robust.
vladr
I must handle one more situation: n>=1 files and stdin present! In this case, no file should be processed, only stdin input. The problem with cat is that I would never be able to detect when no stdin and no files! Doing cat on stdin to check hangs up! Besides, perl is always present and works great!
Sorry, by `cat`-like approach I did not actually mean using `cat`, but behaving like `cat`. Based on your latest comment this is obviously not an option, so yeah, the `select` approach (Perl or C) is a good, fool-proof heuristic provided you can afford the timeout.
vladr
As I said, I'm replacing an exiting script which relied on un-manageable technology and can't control how the script is called (tens of prod "chains"). Your solution worked great in test for a week. I released it for production this morning! Timeout is no issue .Only stdin-scenario is: cat | script.
So thank you very much Vlad! This question is now answered!
A: 

Why not solve this in a more traditional way, and use the command line argument to indicate that the data will be coming from stdin?

For an example, consider the difference between:

echo foo | cat -

and

echo foo > /tmp/test.txt

cat /tmp/test.txt

Mike
+2  A: 

This strategy works for bash, and would likely work for ksh. Poll 'tty':

#!/bin/bash
set -a

if [ "$( tty )" == 'not a tty' ]
then
    STDIN_DATA_PRESENT=1
else
    STDIN_DATA_PRESENT=0
fi

if [ ${STDIN_DATA_PRESENT} -eq 1 ]
then
    echo "Input was found."
else
    echo "Input was not found."
fi
Also works with ksh.
Boune
Please not that on non-english systems you will get the localized version of "not a tty". One possiblity to avoid this is adding the line "LANG=C" at the top of the script.
chiborg