How to make a bash function which can read from standard input?
It's a little tricky to write a function which can read standard input, but works properly when no standard input is given. If you simply try to read from standard input, it will block until it receives any, much like if you simply type cat
at the prompt.
In bash 4, you can work around this by using the -t
option to read
with an argument of 0. It succeeds if there is any input available, but does not consume any of it; otherwise, it fails.
Here's a simple function that works like cat
if it has anything from standard input, and echo
otherwise.
catecho () { if read -t 0; then cat else echo "$*" fi}$ catecho command line argumentscommand line arguments$ echo "foo bar" | catechofoo bar
This makes standard input take precedence over command-line arguments, i.e., echo foo | catecho bar
would output foo
. To make arguments take precedence over standard input (echo foo | catecho bar
outputs bar
), you can use the simpler function
catecho () { if [ $# -eq 0 ]; then cat else echo "$*" fi}
(which also has the advantage of working with any POSIX-compatible shell, not just certain versions of bash
).
You can use <<<
to get this behaviour. read <<< echo "text"
should make it.
Test with readly
(I prefer not using reserved words):
function readly(){ echo $* echo "this was a test"}$ readly <<< echo "hello"hellothis was a test
With pipes, based on this answer to "Bash script, read values from stdin pipe":
$ echo "hello bye" | { read a; echo $a; echo "this was a test"; }hello byethis was a test
To combine a number of other answers into what worked for me (this contrived example turns lowercase input to uppercase):
uppercase() { local COMMAND='tr [:lower:] [:upper:]' if [ -t 0 ]; then if [ $# -gt 0 ]; then echo "$*" | ${COMMAND} fi else cat - | ${COMMAND} fi }
Some examples (the first has no input, and therefore no output):
:; uppercase:; uppercase testTEST:; echo test | uppercase TEST:; uppercase <<< testTEST:; uppercase < <(echo test)TEST
Step by step:
test if file descriptor 0 (
/dev/stdin
) was opened by a terminalif [ -t 0 ]; then
tests for CLI invocation arguments
if [ $# -gt 0 ]; then
echo all CLI arguments to command
echo "$*" | ${COMMAND}
else if
stdin
is piped (i.e. not terminal input), outputstdin
to command (cat -
andcat
are shorthand forcat /dev/stdin
)else cat - | ${COMMAND}