How to read stdin when no arguments are passed?

纵然是瞬间 提交于 2019-12-04 18:01:36

问题


Script doesn't work when I want to use standard input when there are no arguments (files) passed. Is there any way how to use stdin instead of a file in this code?

I tried this:

if [ ! -n $1 ] # check if argument exists
   then
   $1=$(</dev/stdin)  # if not use stdin as an argument
   fi

var="$1"
while read line
   do
   ...                # find the longest line
   done <"$var"

回答1:


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.)




回答2:


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



回答3:


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"



回答4:


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.




回答5:


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



来源:https://stackoverflow.com/questions/19619490/how-to-read-stdin-when-no-arguments-are-passed

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