How to read stdin when no arguments are passed?

ⅰ亾dé卋堺 提交于 2019-12-03 12:05:00
pilcrow

Just substitute bash's specially interpreted /dev/stdin as the filename:

VAR=$1
while read blah; do
  ...
done < "${VAR:-/dev/stdin}"

(Note that bash will actually use that special file /dev/stdin if built for an OS that offers it, but since bash 2.04 will work around that file's absence on systems that do not support it.)

For a general case of wanting to read a value from stdin when a parameter is missing, this will work.

$ echo param | script.sh
$ script.sh param

script.sh

#!/bin/bash

set -- "${1:-$(</dev/stdin)}" "${@:2}"

echo $1
mklement0

pilcrow's answer provides an elegant solution; this is an explanation of why the OP's approach didn't work.

The main problem with the OP's approach was the attempt to assign to positional parameter $1 with $1=..., which won't work.

The LHS is expanded by the shell to the value of $1, and the result is interpreted as the name of the variable to assign to - clearly, not the intent.

The only way to assign to $1 in bash is via the set builtin. The caveat is that set invariably sets all positional parameters, so you have to include the other ones as well, if any.

set -- "${1:-/dev/stdin}" "${@:2}"     # "${@:2}" expands to all remaining parameters

(If you expect only at most 1 argument, set -- "${1:-/dev/stdin}" will do.)

The above also corrects a secondary problem with the OP's approach: the attempt to store the contents rather than the filename of stdin in $1, since < is used.

${1:-/dev/stdin} is an application of bash parameter expansion that says: return the value of $1, unless $1 is undefined (no argument was passed) or its value is the empty string (""or '' was passed). The variation ${1-/dev/stdin} (no :) would only return /dev/stdin if $1 is undefined (if it contains any value, even the empty string, it would be returned).

If we put it all together:

# Default to filename '/dev/stdin' (stdin), if none was specified.
set -- "${1:-/dev/stdin}" "${@:2}"

while read -r line; do
   ...                # find the longest line
done < "$1"

But, of course, the much simpler approach would be to use ${1:-/dev/stdin} as the filename directly:

while read -r line; do
   ...                # find the longest line
done < "${1:-/dev/stdin}"

or, via an intermediate variable:

filename=${1:-/dev/stdin}
while read -r line; do
   ...                # find the longest line
done < "$filename"
Oswald

Variables are assigned a value by Var=Value and that variable is used by e.g. echo $Var. In your case, that would amount to

1=$(</dev/stdin)

when assigning the standard input. However, I do not think that variable names are allowed to start with a digit character. See the question bash read from file or stdin for ways to solve this.

kenorb

Here is my version of script:

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

If file is not present in the argument, read the from standard input.

See more examples: How to read from file or stdin in bash? at stackoverflow SE

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!