How to read from a file or standard input in Bash

The following Perl script (my.pl) can read from either the file in the command line arguments or from standard input (STDIN):

while (<>) {
   print($_);
}

perl my.pl will read from standard input, while perl my.pl a.txt will read from a.txt. This is very handy.

Is there an equivalent in Bash?


Solution 1:

The following solution reads from a file if the script is called with a file name as the first parameter $1 and otherwise from standard input.

while read line
do
  echo "$line"
done < "${1:-/dev/stdin}"

The substitution ${1:-...} takes $1 if defined. Otherwise, the file name of the standard input of the own process is used.

Solution 2:

Perhaps the simplest solution is to redirect standard input with a merging redirect operator:

#!/bin/bash
less <&0

Standard input is file descriptor zero. The above sends the input piped to your bash script into less's standard input.

Read more about file descriptor redirection.

Solution 3:

Here is the simplest way:

#!/bin/sh
cat -

Usage:

$ echo test | sh my_script.sh
test

To assign stdin to the variable, you may use: STDIN=$(cat -) or just simply STDIN=$(cat) as operator is not necessary (as per @mklement0 comment).


To parse each line from the standard input, try the following script:

#!/bin/bash
while IFS= read -r line; do
  printf '%s\n' "$line"
done

To read from the file or stdin (if argument is not present), you can extend it to:

#!/bin/bash
file=${1--} # POSIX-compliant; ${1:--} can be used either.
while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")

Notes:

- read -r - Do not treat a backslash character in any special way. Consider each backslash to be part of the input line.

- Without setting IFS, by default the sequences of Space and Tab at the beginning and end of the lines are ignored (trimmed).

- Use printf instead of echo to avoid printing empty lines when the line consists of a single -e, -n or -E. However there is a workaround by using env POSIXLY_CORRECT=1 echo "$line" which executes your external GNU echo which supports it. See: How do I echo "-e"?

See: How to read stdin when no arguments are passed? at stackoverflow SE

Solution 4:

I think this is the straightforward way:

$ cat reader.sh
#!/bin/bash
while read line; do
  echo "reading: ${line}"
done < /dev/stdin

--

$ cat writer.sh
#!/bin/bash
for i in {0..5}; do
  echo "line ${i}"
done

--

$ ./writer.sh | ./reader.sh
reading: line 0
reading: line 1
reading: line 2
reading: line 3
reading: line 4
reading: line 5

Solution 5:

The echo solution adds new lines whenever IFS breaks the input stream. @fgm's answer can be modified a bit:

cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"