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?
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
Related
I am modifying a pre-receive hook of gitlab
the code which returns branch name is
print ARGF.read
print "\n"
refs = ARGF.read
print refs
first time when i do print or puts i get the branch name but again second time whenever i use the same string it returns nothing. nil or empty string.
Any clue why is this happening. I am new to ruby, so unable to figure out.
ARGF is a stream designed for use in scripts that process files given as command-line arguments or passed in via STDIN.
The manual states that ARGF::read
Reads _length_ bytes from ARGF. The files named on the command line
are concatenated and treated as a single file by this method, so when
called without arguments the contents of this pseudo file are returned in
their entirety.
You might want to use ARGF::readline
Returns the next line from the current file in ARGF.
or maybe ARGF::rewind
Positions the current file to the beginning of input, resetting ARGF.lineno to zero.
A call ARGF.read is not idempotent, that is you cannot invoke it many times and expect the same result as it will "consume" all the command line arguments passed to your hook.
If you want to reuse the command line arguments you have to first store them in a local variable and then do what you want with them
refs = ARGF.read
print refs
print "\n"
# do something here with your arguments
print "\n"
I need to stream data through a Ruby script, and will be calling this program from my terminal like so:
cat file.txt | ruby example.rb
The code inside example.rb looks like this:
ARGF.each do |line|
#program logic
end
This program works fine, but now I need to pass one (or potentially more) parameters to example.rb. I can't use trollop or optparser (or anything of that nature). I am trying to pass my parameter to my program like this:
cat file.txt | ruby example.rb 2
I am trying to use this parameter in my program by extracting it from the ARGV array:
x = ARGV.first
puts x
ARGF.each do |line|
#program logic
end
But now the program gives me this error:
No such file or directory - 2
I seems like ARGF is using now using the parameter as my standard input instead of the file that I am streaming to it. How can I use pass ARGV and ARGF together like this?
From documentation:
The arguments passed to your script are stored in the ARGV Array, one argument per element. ARGF assumes that any arguments that aren't filenames have been removed from ARGV. For example:
$ ruby argf.rb --verbose file1 file2
ARGV #=> ["--verbose", "file1", "file2"]
option = ARGV.shift #=> "--verbose"
ARGV #=> ["file1", "file2"]
You can now use ARGF to work with a concatenation of each of these named files. For instance, ARGF.read will return the contents of file1 followed by the contents of file2.
my Question is how I can convert the STDIN of cmd ARGV or gets from hex to ascii
I know that if I assigned hex string to variable it'll be converted once I print it
ex
hex_var = "\x41\41\x41\41"
puts hex_var
The result will be
AAAA
but I need to get the value from command line by (ARGV or gets)
say I've this lines
s = ARGV
puts s
# another idea
puts s[0].gsub('x' , '\x')
then I ran
ruby gett.rb \x41\x41\x41\x41
I got
\x41\x41\x41\x41
is there a way to get it work ?
There are a couple problems you're dealing with here. The first you've already tried to address, but I don't think your solution is really ideal. The backslashes you're passing in with the command line argument are being evaluated by the shell, and are never making it to the ruby script. If you're going to simply do a gsub in the script, there's no reason to even pass them in. And doing it your way means any 'x' in the arguments will get swapped out, even those that aren't being used to indicate a hex. It would be better to double escape the \ in the argument if possible. Without context of where the values are coming from, it's hard to say with way would actually be better.
ruby gett.rb \\x41\\x41
That way ARGV will actually get '\x41\x41', which is closer to what you want.
It's still not exactly what you want, though, because ARGV arguments are created without expression substitution (as though they are in single quotes). So Ruby is escaping that \ even though you don't want it to. Essentially you need to take that and re-evaluate it as though it were in double quotes.
eval('"%s"' % s)
where s is the string.
So to put it all together, you could end up with either of these:
# ruby gett.rb \x41\x41
ARGV.each do |s|
s = s.gsub('x' , '\x')
p eval('"%s"' % s)
end
# => "AA"
# ruby gett.rb \\x41\\x41
ARGV.each do |s|
p eval('"%s"' % s)
end
# => "AA"
Backlashes entered in the console will be interpreted by the shell and will
not make it into your Ruby script, unless you enter two backlashes in a row,
in which case you script will get a literal backlash and no automatic
conversion of hexadecimal character codes following those backlashes.
You can convert these escaped codes to characters manually if you replace the last line of your script with this:
puts s.gsub(/\\x([[:xdigit:]]{1,2})/) { $1.hex.chr }
Then run it with double backlashed input:
$ ruby gett.rb \\x41\\x42\\x43
ABC
When fetching user input through gets or similar, only a single backslash will be need to be entered by the user for each character escape, since that will indeed be passed to your script as literal backslashes and thus handled correctly by the above gsub call.
An alternative way when parsing command line arguments would be to let the shell interpret the character escapes for you. How to do this will depend on what shell you are using. If using bash, it can be done
like this:
$ echo $'\x41\x42\x43'
ABC
$ ruby -e 'puts ARGV' $'\x41\x42\x43'
ABC
I want to get an input from the user using read
read line
and the proper input would be a string and then a number:
a 5
b 6
+ 87
How do you separate the "a" and 5 into two variables, with the 5 into a integer variable?
read supports the command line option -a, to store the input in an array like that:
$ read -a line
a 4
$ echo ${line[0]}
a
$ echo ${line[1]}
4
That would be nicer than using two variables, in my opinion.
I suggest reading the documenentation of read to get you started:
help read
Here are the first two paragraphs:
Read a line from the standard input and split it into fields.
Reads a single line from the standard input, or from file descriptor FD
if the -u option is supplied. The line is split into fields as with word
splitting, and the first word is assigned to the first NAME, the second
word to the second NAME, and so on, with any leftover words assigned to
the last NAME. Only the characters found in $IFS are recognized as word
delimiters.
Note that bash doesn't have a notion of "integer variables" comparable to other programming languages. Bash variables are untyped. Declaring a variable as integer using declare -i only influences assignments to this variable -- everything that is not a valid integer is silently set to 0.
I presume you're operating in the shell since this post is tagged "bash" but you might want to make that explicit.
Anyway, the "read" command to the shell takes multiple variable names, not just one. You can give it two and it will hand each word on the line to you in the respective variables. (They're split on the field separator given by the IFS variable.)
The shell doesn't really have any distinction between "integer" variables and any other kind in the general case.
I suggest reading the man page for the shell fully if you really want to understand how to write shell scripts properly.
The read command will split your input along whatever is in $IFS. This, by default is whitespace, so simply doing this:
read my_string my_number
will split your input into two sections separated by the space. Sometimes, you'll see this:
read my_string my_number garbage
Because read will read in the entire rest of the line into the last variable no matter how many parameters you had. For example, if I had:
read my_string my_number
And the user put in:
this 1 foo foo foo!
$my_string will be this, but $my_number will be 1 foo foo foo!
By putting another variable (called garbage in this case), I eliminate this issue. If I put:
read my_string my_number garbage
And the user put in:
this 1 foo foo foo!
$my_string will be this, $my_number will be 1, and $garbage would be foo foo foo!.
A simple test program:
while read my_string my_number garbage
do
echo "The string is '$my_string'. The number is '$my_number'."
echo "Note there's no error checking of input."
echo "That's your job, Bunky."
done
I wrote a simple script that writes all given arguments to a single text file, separated by newline. I'd like to pass a list of files to it using OptionParser. I would like to add a couple of files using wildcards like /dir/*.
I tried this:
opts = OptionParser.new
opts.on('-a', '--add FILE') do |s|
puts "DEBUG: before #{s}"
#options.add = s
puts "DEBUG: after #{#options.add}"
end
...
def process_arguments
#lines_to_add = Dir.glob #options.add
end
Put when I add files like this:
./script.rb -a /path/*
I always get only the first file in the directory. All the debug outputs show only the first file of directory, and it seems as if OptionParser does some magic interpretations
Does anyone know how to handle this?
You didn't mention which operating system you are using (it matters).
On Windows, whatever you type on the command line gets passed to the program without modification. So if you type
./script.rb -a /path/*
then the arguments to the program contain "-a" and "/path/*".
On Unix and other systems with similar shells, the shell does argument expansion that automatically expands wildcards in the command line. So when you type the same command above, the shell looks to find the files in the /path/* directory and expands the command line arguments before your program runs. So the arguments to your program might be "-a", "/path/file1", and "/path/file2".
An important point is that the script cannot find out whether argument expansion happened, or whether the user actually typed all those filenames out on the command line.
As mentioned above, the command-line is being parsed before the OS hands off the command to Ruby. The wildcard is being expanded into a list of space-delimited filenames.
You can see what will happen if you type something like echo * at the command-line, then, instead of hitting Return, instead hit Esc then *. You should see the * expanded into the list of matching files.
After hitting Return those names will be added to the ARGV array. OptionParser will walk through ARGV and find the flags you defined, grab the following elements if necessary, then remove them from ARGV. When OptionParser is finished any ARGV elements that didn't fit into the options will remain in the ARGV array where you can get at them.
In your code, you are looking for a single parameter for the '-a' or '--add FILE' option. OptionParser has an Array option which will grab comma-separated elements from the command line but will subsequent space-delimited ones.
require 'optparse'
options = []
opts = OptionParser.new
opts.on('-a', '--add FILE', Array) do |s|
options << s
end.parse!
print "options => ", options.join(', '), "\n"
print "ARGV => ", ARGV.join(', '), "\n"
Save that to a file and try your command line with -a one two three, then with -a one,two,three. You'll see how the Array option grabs the elements differently depending on whether there are commas or spaces between the parameters.
Because the * wildcard gets replaced with space delimited filenames you'll have to post-process ARGV after OptionParser has run against it, or programmatically glob the directory and build the list that way. ARGV has all the files except the one picked up in the -a option so, personally, I'd drop the -a option and let ARGV contain all the files.
You will have to glob the directory if * has to too many files and exceeds the buffer size. You'll know if that happens because the OS will complain.
The shell is expanding the argument before it gets passed to your program. Either keep consuming filenames until you reach another option, or have the user escape the wildcards (e.g. ./script.rb -a '/path/*') and glob them yourself.
What's happening is the shell is expanding the wildcard before Ruby gets to it. So really you are processing:
./script.rb -a /path/file1 /path/file2 ......
Put quotes around /path/* to avoid the shell expansion and pass the wildcard to Ruby:
./script.rb -a '/path/*'