8

How to check, in a Bash script, that no command line arguments or STDIN was provided ?

I mean if I run:

#> ./myscript.sh
... Show message "No data provided..." and exit

Or:

#> ./myscript.sh filename.txt
... Read from filename.txt

Or:

#> ./myscript.sh < filename.txt**
... Read from STDIN
perror
  • 3,239
  • 7
  • 33
  • 45

4 Answers4

20

Does this fit your requirements ?

#!/bin/sh

if test -n "$1"; then
    echo "Read from $1";
elif test ! -t 0; then
    echo "Read from stdin"
else
    echo "No data provided..."
fi

The major tricks are as follow:

  • Detecting that you have an argument is done through the test -n $1 which is checking if a first argument exists.

  • Then, checking if stdin is not open on the terminal (because it is piped to a file) is done with test ! -t 0 (check if the file descriptor zero (aka stdin) is not open).

  • And, finally, everything else fall in the last case (No data provided...).

perror
  • 3,239
  • 7
  • 33
  • 45
  • Yes, sorry for my erroneous edit. I misread your code and thought for a while that you were using read with a timeout... – Kusalananda Nov 28 '18 at 12:14
  • No problem! But, my code works with POSIX shell (I tried it before sending, just to be sure). :-) – perror Nov 28 '18 at 12:15
  • Would it work with stdin from a pipe though? Hmmm... probably. But it would fail if stdin had been explicitly closed... The -t test is true if the fd is open and associated with a terminal. If its false, either its closed or not associated with a terminal. – Kusalananda Nov 28 '18 at 12:17
  • @Kusalananda: You are totally right! If stdin has been closed for some other reason, it will wrongly deduce that it reads from a file given on stdin. But, I do not see how to do better than that... If you can come with a better solution, I would be delighted to see it! – perror Nov 28 '18 at 12:30
  • @perror I don't think it matters too much as read would return (with a "bad filediscriptor" error) immediately, and that could be handled as a data error, i.e. the data was supposed to come from standard input, but there was something wrong with it. – Kusalananda Nov 28 '18 at 12:45
  • I wrote https://github.com/NightMachinary/possiblycat to add a timeout to the test ! -t 0 case. It works more reliably that way. – HappyFace Sep 20 '20 at 18:55
  • While OP accepted this - it does not answer the question title, i.e. it checks for STDIN being a terminal. – einpoklum Mar 20 '24 at 15:47
5

I searched far and wide to no avail, and finally managed to put this together through much trial and error. It has worked flawlessly for me in numerous use-cases ever since.

#!/bin/bash
### LayinPipe.sh
## Recreate "${@}" as "${Args[@]}"; appending piped input.
## Offers usable positional parameters regardless of where the input came from.
##
## You could choose to create the array with "${@}" instead following
##  any piped arguments by simply swapping the order
##   of the following two 'if' statements.

# First, check for normal positional parameters.
if [[ ${@} ]]; then
    while read line; do
        Args[${#Args[@]}]="${line}"
    done < <(printf '%s\n' "${@}")
fi

# Then, check for piped input.
if [[ ! -t 0 ]]; then
    while read line; do
        Args[${#Args[@]}]="${line}"
    done < <(cat -)
fi

# Behold the glory.
for ((a=0;a<${#Args[@]};a++)); do
    echo "${a}: ${Args[a]}"
done
  • Example: (knowing full well that using output of 'ls' as input is discouraged in order to show the flexibility of this solution.)
$ ls
: TestFile.txt 'Filename with spaces'

$ ls -1 | LayinPipe.sh "$(ls -1)"
> 0: Filename with spaces
> 1: TestFile.txt 
> 2: Filename with spaces
> 3: TestFile.txt 

$ LayinPipe.sh "$(ls -1)"
> 0: Filename with spaces
> 1: TestFile.txt 

$ ls -1 | LayinPipe.sh
> 0: Filename with spaces
> 1: TestFile.txt 

aPerson
  • 51
  • 1
  • 1
  • Amazing, thanks! Spent quite a bit of time before finding your answer. – Asu Aug 19 '20 at 16:45
  • found an edge case. if you printf "foo" | LayInPipe.sh you'll get no output. Requires a trailing newline to work. – masukomi Nov 26 '22 at 20:49
2

[SOLVED] in bash shell ...

... read -t 0 : see 'help read'

$ function read_if_stdin_not_empty {
if read -t 0 ; then
  while read ; do
    echo "stdin receive : $REPLY"
    read_if_stdin_not_empty
  done
else 
  echo "stdin is empty .."
fi
}

# TESTs:

$ read_if_stdin_not_empty
stdin is empty ..

$ echo '123' | read_if_stdin_not_empty
stdin receive : 123

$s='123                        
456'
$ echo "$s" | read_if_stdin_not_empty
stdin receive : 123
stdin receive : 456
Paulo Tomé
  • 3,782
fw13
  • 21
  • 1
1

I propose a function that synthesizes the previous very interesting answers:

File test.sh:

#!/bin/bash
function init_STDIN() {
  ## Version 1.0.0
  ## Creates the global variable array indexed STDIN
  ## which contains the possible lines sent in the
  ## file descriptor /dev/stdin of the script
  declare -ga STDIN
  read -t0 || return 1
  while read LINE; do
    STDIN[${#STDIN[@]}]="$LINE"
  done < <(cat -)
  test ${#STDIN[@]} -ne 0
}

if init_STDIN; then echo "Feed provided on /dev/stdin. Processing..." echo "For this example, /dev/stdin is:" for ((I=0; I<${#STDIN[@]}; I++)); do echo ${STDIN[$I]} done else echo "Working without any feed on /dev/stdin..." fi

echo echo "For this example, the ${#@} parameter(s) provided on the command line is(are) :" echo $@

TESTS:

$ ./test.sh some args ...
Working without any feed on /dev/stdin...

For this example, the 3 parameter(s) provided on the command line is(are) : some args ...

$ seq 1 10 | sed 's/ /\n/g' | ./test.sh some args ... Feed provided on /dev/stdin. Processing... For this example, /dev/stdin is: 1 2 3 4 5 6 7 8 9 10

For this example, the 3 parameter(s) provided on the command line is(are) : some args ...

$ seq 1 10 | sed 's/ /\n/g' | (exec 0<&-; ./test.sh some args ...) Working without any feed on /dev/stdin...

For this example, the 3 parameter(s) provided on the command line is(are) : some args ...