This is a classic X-Y problem. The goal is to write a utility in which
utility file1 file2
and
utility file1 < file2
have the same behaviour. It seems tempting to find a way to somehow translate the second invocation into the first one by (somehow) figuring out the "name" of stdin, and then using that name the same way as the second argument would be used. Unfortunately, that's not possible. The redirection happens before the utility is invoked, and there is no portable way to get the "name" of an open file descriptor. (Indeed, it might not even have a name, in the case of other_cmd | utility file1
.)
So the solution is to focus on what is being asked for: make the two behaviours consistent. This is the case with most standard utilities (grep
, cat
, sort
, etc.): if the input file is not specified, the utility uses stdin
.
In many unix implementations, stdin
does actually have a name: /dev/stdin
. In such systems, the above can be achieved trivially:
utility() {
utility_implementation "$1" "${2:-/dev/stdin}"
}
where utility_implementation
actually does whatever is required to be done. The syntax of the second argument is normal default parameter expansion; it represents the value of $2
if $2
is present and non-empty, and otherwise the string /dev/stdin
. (If you leave out the -
so that it is "${2:/dev/stdin}", then it won't do the substitution if $2
is present and empty, which might be better.)
Another way to solve the problem is to ensure that the first syntax becomes the same as the second syntax, so that the input is always coming from stdin
even with a named file. The obvious simple approach:
utility() {
if (( $# < 2 )); then
utility_implementation "$1"
else
utility_implementation "$1" < "$2"
fi
}
Another way to do this uses the exec
command with just a redirection to redirect the shell's own stdin
. Note that we have to do this inside a subshell ((
...)
instead of {
...}
) so that the redirection does not apply to the shell which invokes the function:
utility() (
if (( $# > 1 )) then; exec < "$2"; fi
# implementation goes here. $1 is file1 and stdin
# is now redirected to $2 if $2 was provided.
# ...
)