2

I want to make sure my script will work when the user uses a syntax like this:

script.sh firstVariable < SecondVariable

For some reason I can't get this to work.

I want $1=firstVariable And $2=SecondVariable

But for some reason my script thinks only firstVariable exists?

5
  • 3
    < is the redirection operator. It connects the stdin of your script to file SecondVariable. Commented Jan 5, 2016 at 11:47
  • Why is the <? parameters are separated by space when passed to an executable on the command line. Commented Jan 5, 2016 at 11:48
  • So how do i make stdin of SecondVariable equal to $2? Commented Jan 5, 2016 at 11:51
  • @marekful, the space is allowed. I don't consider it good form, but it's entirely valid syntax. Commented Jan 5, 2016 at 18:00
  • @Joey, what does "stdin of SecondVariable equal to $2" even mean? "SecondVariable" is, in the context of this command line, a filename; you can of course access the file's contents, by reading from your script's stdin. Commented Jan 5, 2016 at 18:34

2 Answers 2

5

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.
  # ...
)
Sign up to request clarification or add additional context in comments.

Comments

1

To make the stdin of the second variable the final argument to the script(so if you have one arg then < second arg, it will be the second), you can use the below

#!/bin/bash

##read loop to read in stdin
while read -r line
do

  ## This just checks if the variable is empty, so a newline isn't appended on the front
  [[ -z $Vars ]] && Vars="$line" && continue

  ## Appends every line read to variable
  Vars="$Vars"$'\n'"$line"

  ## While read loop using stdin
done < /dev/stdin

 ##Set re-sets the arguments to the script to the original arguments and then the new argument we derived from stdin
set - "$@" "$Vars"

## Echo the new arguments
echo "$@"

10 Comments

wow this is great but echo "$@" of course echos every line of $2 instead of just the file name (it kind off works like cat now)
@joey if you want it to just read as the filename why do you want to use stdin ?
@Joey You can't (well you can but not portably I don't think) get the name of what's attached to stdin. Nor would doing that be of any real use. What's the actual question that you think requires this? What does the script need to do?
BTW, what do you expect < /dev/stdin to do that isn't already the case by default?
@CharlesDuffy: One might also be tempted to write lines=$(cat) although cat is not builtin. Or lines=$(</dev/stdin).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.