Why does executing a string containing function calls using ruby eval fail - ruby

I'm quite new to Ruby so I hope this question isn't already answered elsewhere. But I've been searching here and on the internet for quite a while with no results yet.
I am reading in a bunch of file paths from a text file. Each file path has some expressions (i.e. #{...} ) embedded into the string. For instance:
input = 'E:/files/storage/#{high_or_low}/#{left_or_right}/*.dll'
Anyways, I want to evaluate those strings as if they were ruby code and get those expressions replaced. For instance:
def high_or_low
'low'
end
def left_or_right
'left'
end
# It represents a file path on the disk drive
input = 'E:/files/storage/#{high_or_low}/#{left_or_right}/*.dll'
puts input
# Now how do I execute this 'input' string so that it behaves as if it was defined with quotes?
result = eval input
puts result
I purposefully put single quotes around the original input string to show that when the string comes in off the disk, the expression embedded in the string is unevaluated. So how do I evaluate the string with these expressions? Using eval as shown above doesn't seem to work. I get this error:
test_eval.rb:15: compile error (SyntaxError)
test_eval.rb:15: syntax error, unexpected tIDENTIFIER, expecting $end
Thanks

pst pointed you to why it's failing. As an alternative, which may or may not work depending on what your data looks like... but if it's simple strings with method names (and in particular doesn't contain nested "}"'s...
result = input.gsub(/\#{(.*?)}/) {|s| send($1)}
puts result

Ah found it. pst got me started in the correct direction.
So once I wrapped the input variable in quotes like this, it worked:
result = eval '"' + input + '"'
puts result
=> E:/files/storage/low/left/*.dll

Related

Find youtube url in json file with ruby

For testing purpose my json file (test.json) consists of only the string I want to find:
"https://www.youtube.com/watch?v=hBIZF3sDFTI"
Somehow I cannot find the string in file with this ruby code:
if not File.foreach("test.json").grep(/https://www.youtube.com/watch?v=hBIZF3sDFTI/).any?
puts("string not in file")
end
Output: "string not in file"
But the string is in the file.
Searching for other strings works fine, so it must be a problem with this particular string.
Any help is much appreciated!
Problems
Your regex pattern isn't valid, because it's got too many forward slashes in it. Specifically:
/https://www.youtube.com/watch?v=hBIZF3sDFTI/
is not a valid regular expression. Your String is also not a valid a JSON object.
Solution
You need to escape special regular expression characters like / and ? before trying to use your pattern. For example, you could call Regexp#escape on the String like so:
Regexp.escape 'https://www.youtube.com/watch?v=hBIZF3sDFTI'
#=> "https://www\\.youtube\\.com/watch\\?v=hBIZF3sDFTI"
Then, assuming you have a valid JSON object, you could match the expression as follows:
require 'json'
str = 'https://www.youtube.com/watch?v=hBIZF3sDFTI'
json = str.to_json
#=> "\"https://www.youtube.com/watch?v=hBIZF3sDFTI\""
pattern = Regexp.escape str
json.match pattern
#=> #<MatchData "https://www.youtube.com/watch?v=hBIZF3sDFTI">

YAML: error parsing a string containing a square bracket as its first character

I'm parsing a YAML file in Ruby and some of the input is causing a Psych syntax error:
require 'yaml'
example = "my_key: [string] string"
YAML.load(example)
Resulting in:
Psych::SyntaxError: (<unknown>): did not find expected key
while parsing a block mapping at line 1 column 1
from [...]/psych.rb:456:in `parse'
I received this YAML from an external API that I do not have control over. I can see that editing the input to force parsing as a string, using my_key: '[string] string', as noted in "Do I need quotes for strings in YAML?", fixes the issue however I don't control how the input is received.
Is there a way to force the input to be parsed as a string for some keys such as my_key? Is there a workaround to successfully parse this YAML?
One approach would be to process the response before reading it as YAML. Assuming it's a string, you could use a regex to replace the problematic pattern with something valid. I.e.
resp_str = "---\nmy_key: [string] string\n"
re = /(\: )(\[[a-z]*?\] [a-z]*?)(\n)/
resp_str.gsub!(re, "#{$1}'#{$2}'#{$3}")
#=> "---\n" + "my_key: '[string] string'\n"
Then you can do
YAML.load(resp_str)
#=> {"my_key"=>"[string] string"}
It does not work because square brackets have a special meaning in YAML, denoting arrays:
YAML.load "my_key: [string]"
#⇒ {"my_key"=>["string"]}
and [foo] bar is an invalid type. One should escape square brackets explicitly
YAML.load "my_key: \\[string\\] string"
#⇒ {"my_key"=>"\\[string\\] string"}
Also, one might implement the custom Psych parser.
There is very native and easy solution. If you would like to have string context you can always put quotes around it:
YAML.load "my_key: '[string]'"
=> {"my_key"=>"[string]"}

Literal vs variable with spaces on divide operator in Ruby

Let's say I have a literal Fixnum 1420028751000 and I want to convert using this:
Time.at(1420028751000 / 1000) # => 2014-12-31 20:25:51 +0800
I can put spaces also, let's say I am beginner and my coding is bad:
Time.at(1420028751000 / 1000)
Time.at(1420028751000 /1000)
All these work fine and give me correct result.
However, once I introduced variable:
a = 1420028751000
Time.at(a/1000) # => Works!
Time.at(a / 1000) # => Works!
Time.at(a /1000) # => Strange thing happen
My question is, what is so unique about the /1000 that make it not working when introducing an variable in Ruby?
Ruby's parser is interpreting /1000 as the beginning of a literal regexp, since a is a token which may be a method. That is, imagine that a is a method:
def a(arg); end
Time.at(a /1000)
Ruby will interpret this as "a invoked with an incomplete regexp as the argument". To "complete" this call, Ruby is expecting you might want to do something like:
Time.at( a(/1000/) )

Passing all arguments at once to a method

I am trying to read arguments from a text file and the pass them all at once to a Ruby method.
The arguments in the text file are properly formatted e.g.:
"path", ["elem1","elem2"], 4,"string"
I intend to make a function call like this:
my_method("path", ["elem1","elem2"], 4,"string")
This hopefully I am trying to achieve like this:
IO.readlines("path").each do |line|
puts "#{line}"
my_method(*line.split(","))
end
The problem is that in the method all the array elements are wrapped in quotes. So my method ends up getting this:
""path"", "["elem1","elem2"]", "4",""string""
Now, this is probably because its an array of strings, but why wrap it with an additional "" when I say *arr?
If I use eval:
IO.readlines("path").each do |line|
puts "#{line}"
my_method(*eval(line))
end
I end up with syntax error, unexpected ',' after the first argument in "path", ["elem1","elem2"], 4,"string"
How do I achieve passing all the elements to the method at once reading the arguments from a text file
Also since Ruby does not care about types, why do I have to wrap my arguments with "" in the first place. If I don't wrap the argument in a quote, I get undefined variable for main:object error.
I have one solution, but instead of using "," as your delimiter use some other special character as delimiter in the input line.
# Input line in somefile.txt delimited by "||" :
# "path" || ["elem1","elem2"] || 4 || "string"
def my_method(arg1, arg2, arg3, arg4)
path = arg1
arr = arg2.gsub(/([\[\]])/, "").split(",")
number = arg3.to_i
string = arg4
puts "path : #{path} and is #{path.class}"
puts "arr : #{arr} and is #{arr.class}"
puts "number : #{number} and is #{number.class}"
puts "string : #{string} and is #{string.class}"
end
IO.readlines("somefile.txt").each do |line|
my_method(*line.gsub(/[(\\")]/, " ").split("||"))
end
I hope this helped you out. Let me know if you have any problem.
IO.readlines("path").each do |line|
params = line.split(",").each do |param|
param = eval(param)
end
my_method(*params)
end
When you read the line, all params are strings, so to get arrays and integers you might try to eval then first.
the eval tip might be enough to fix your code.
if you pass the param without quotes, the interpreter will understand it as a constant and not as a string. Thats why you get undefined variable. Again, the eval tip should solve this.
OBS: Be careful with eval since it will execute any code, a command to erase the file or even worse (like mess with your computer or server) if the person behind the source of that file knows it.

Stumped by a simple regex

I am trying to see if the string s contains any of the symbols in a regex. The regex below works fine on rubular.
s = "asd#d"
s =~ /[~!##$%^&*()]+/
But in Ruby 1.9.2, it gives this error message:
syntax error, unexpected ']', expecting tCOLON2 or '[' or '.'
s = "asd#d"; s =~ /[~!##$%^&*()]/
What is wrong?
This is actually a special case of string interpolation with global and instance variables that most seem not to know about. Since string interpolation also occurs within regex in Ruby, I'll illustrate below with strings (since they provide for an easier example):
#foo = "instancefoo"
$foo = "globalfoo"
"##foo" # => "instancefoo"
"#$foo" # => "globalfoo"
Thus you need to escape the # to prevent it from being interpolated:
/[~!#\#$%^&*()]+/
The only way that I know of to create a non-interpolated regex in Ruby is from a string (note single quotes):
Regexp.new('[~!##$%^&*()]+')
I was able to replicate this behavior in 1.9.3p0. Apparently there is a problem with the '#$' combination. If you escape either it works. If you reverse them it works:
s =~ /[~!#$#%^&*()]+/
Edit: in Ruby 1.9 #$ invokes variable interpolation, even when followed by a % which is not a valid variable name.
I disagree, you need to escape the $, its the end of string character.
s =~ /[~!##\$%^&*()]/ => 3
That is correct.

Resources