Search code examples
rubystdin

Ruby treats first command-line args as stdin


Running a Ruby script with command line args make $stdin read from the first command-line arg instead of from a tty.

echo "puts gets" > myscript.rb
ruby myscript.rb foo
# myscript.rb:1:in `gets': No such file or directory @ rb_sysopen - foo (Errno::ENOENT)

In this example, I'd like to script the ask me for input on the tty, then echo back whatever input I give, but instead, Ruby looks for a file named foo and tries to read from it.

How can I supply command-line args but still have Ruby prompt me for input on the tty?


Solution

  • To quote from the documentation of Kernel#gets (emphasis mine):

    Returns (and assigns to $_) the next line from the list of files in ARGV (or $*), or from standard input if no files are present on the command line. Returns nil at end of file. The optional argument specifies the record separator. The separator is included with the contents of each record. A separator of nil reads the entire contents, and a zero-length separator reads the input one paragraph at a time, where paragraphs are divided by two consecutive newlines. If the first argument is an integer, or optional second argument is given, the returning string would not be longer than the given value in bytes. If multiple filenames are present in ARGV, gets(nil) will read the contents one file at a time.

    Thus, gets returns the lines from the file names passes as arguments to the current program. It only uses STDIN if no further command line arguments are present in ARGV.

    You can overwrite this behavior by not using Kernel#gets but IO#gets:

    echo 'puts $stdin.gets' > myscript.rb
    ruby myscript.rb foo