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 ofecho
to avoid printing empty lines when the line consists of a single-e
,-n
or-E
. However there is a workaround by usingenv POSIXLY_CORRECT=1 echo "$line"
which executes your external GNUecho
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}"