How can I get a Ruby variable from a line read from a file? - ruby

I need to reuse a textfile that is filled with one-liners such:
export NODE_CODE="mio12"
How can I do that in my Ruby program the var is created and assign as it is in the text file?

If the file were a Ruby file, you could require it and be able to access the variables after that:
# variables.rb
VAR1 = "variable 1"
VAR2 = 2
# ruby.rb
require "variables"
puts VAR1
If you're not so lucky, you could read the file and then loop through the lines, looking for lines that match your criteria (Rubular is great here) and making use of Ruby's instance_variable_set method. The gsub is to deal with extra quotes when the matcher grabs a variable set as a string.
# variables.txt
export VAR1="variable 1"
export VAR2=2
# ruby.rb
variable_line = Regexp.new('export\s(\w*)=(.*)')
File.readlines("variables.txt").each do |line|
if match = variable_line.match(line)
instance_variable_set("##{match[1].downcase}", match[2].gsub("\"", ""))
end
end
puts #var1
puts #var2

Creating a hash from this file can be a fairly simple thing.
For var.txt:
export BLAH=42
export WOOBLE=67
File.readlines("var.txt").each_with_object({}) { |line, h|
h[$1] = $2 if line =~ /^ export \s+ (.+?) \s* \= \s* (.+) $/x
}
# => {"BLAH"=>"42", "WOOBLE"=>"67"}

Related

matching a double-quote via quote vs pattern

Why does check_char1 fail to find the double-quote?
#!/usr/bin/env ruby
line = 'hello, "bob"'
def check_char1(line, _char)
puts "check_char1 found #{_char} in #{line}" if line =~ /_char/
end
check_char1(line, '"')
def check_char2(line, _char)
puts "check_char2 found #{_char.inspect} in #{line}" if line =~ _char
end
check_char2(line, /"/)
...and can it be made to work using line =~ /_char/? (How should the double-quote be passed to the method?)
If _char is just a string (i.e. no regex pattern matching needed) then just use String#include?
if line.include?(_char)
If you must use a regex for this then Regexp.escape is your friend:
if line =~ /#{Regexp.escape(_char)}/
if line =~ Regexp.new(Regexp.escape(_char))
and if you want _char to be treated like a regex (i.e. '.' matches anything) then drop the Regexp.escape:
if line =~ /#{_char}/
if line =~ Regexp.new(_char)
In check_char1, _char in /_char/ is treated as a literal, not a variable. You need /#{_char}/.
If _char were treated as variable how could one enter a literal in a regex that was the name of a variable, method or constant?

Ruby is returning actual newlines instead of \n

I have a file "Foo.md", which contains three lines:
Foo
Bar
I want File.read("Foo.md") to return "Foo\n\nBar" It does this when I run it from irb in the Terminal, but when I run it from a script such as
content = File.read('Foo.md')
puts content
it returns with the lines converted to actual returns. I need that variable as a single line for what comes next in my script.
To be clear: I'm not interested in changing the number of lines, just debugging to make sure the variable content is being passed as a single line.
You are still reading "Foo\n\n\nBar". However, puts interprets the special characters.
You can use String#inspect:
puts content.inspect # => "Foo\n\n\nBar"
str =
"Foo
Bar"
#=> "Foo\n\n\nBar"
You could also do this:
str.gsub(/\n{3,}/,"\n\n")
#=> "Foo\\nnBar"
It might help you visualize what is happening by meditating on this:
str = <<EOT
Foo
Bar
EOT
str's contents look like:
str # => "Foo\n\n\nBar\n"
Inspecting it escapes the backslashes for a visual representation, but it's NOT what you'd want to use when creating a string with embedded line-ends. You'd want to define it as it's shown above.
str.inspect # => "\"Foo\\n\\n\\nBar\\n\""
inspect output varies when viewed in IRB vs. the console. Here's what you'd see in the console:
ruby -e 'puts "\n"; puts "\n".inspect'
"\n"
Printing str to the console:
puts str
# >> Foo
# >>
# >>
# >> Bar

Replacing escape quotes with just quotes in a string

So I'm having an issue replacing \" in a string.
My Objective:
Given a string, if there's an escaped quote in the string, replace it with just a quote
So for example:
"hello\"74" would be "hello"74"
simp"\"sons would be simp"sons
jump98" would be jump98"
I'm currently trying this: but obviously that doesn't work and messes everything up, any assistance would be awesome
str.replace "\\"", "\""
I guess you are being mistaken by how \ works. You can never define a string as
a = "hello"74"
Also escape character is used only while defining the variable its not part of the value. Eg:
a = "hello\"74"
# => "hello\"74"
puts a
# hello"74
However in-case my above assumption is incorrect following example should help you:
a = 'hello\"74'
# => "hello\\\"74"
puts a
# hello\"74
a.gsub!("\\","")
# => "hello\"74"
puts a
# hello"74
EDIT
The above gsub will replace all instances of \ however OP needs only to replace '" with ". Following should do the trick:
a.gsub!("\\\"","\"")
# => "hello\"74"
puts a
# hello"74
You can use gsub:
word = 'simp"\"sons';
print word.gsub(/\\"/, '"');
//=> simp""sons
I'm currently trying str.replace "\\"", "\"" but obviously that doesn't work and messes everything up, any assistance would be awesome
str.replace "\\"", "\"" doesn't work for two reasons:
It's the wrong method. String#replace replaces the entire string, you are looking for String#gsub.
"\\"" is incorrect: " starts the string, \\ is a backslash (correctly escaped) and " ends the string. The last " starts a new string.
You have to either escape the double quote:
puts "\\\"" #=> \"
Or use single quotes:
puts '\\"' #=> \"
Example:
content = <<-EOF
"hello\"74"
simp"\"sons
jump98"
EOF
puts content.gsub('\\"', '"')
Output:
"hello"74"
simp""sons
jump98"

Building multi-line strings, programmatically, in Ruby

Here's something I often do when programming:
code = ''
code << "next line of code #{something}" << "\n"
code << "another line #{some_included_expression}" << "\n"
Is there some better way than having << "\n" or + "\n" on every line? This seems quite inefficient.
I'm interested in Ruby solutions, in particular. I'm thinking something like
code = string.multiline do
"next line of code #{something}"
"another line #{some_included_expression}"
end
If you're looking to build a block of text, the easy way to do it is to just use the % operator. For example:
code = %{First line
second line
Third line #{2 + 2}}
'code' will then be
"First line\n second line\n Third line 4"
This would be one way:
code = []
code << "next line of code #{something}"
code << "another line #{some_included_expression}"
code.join("\n")
Use <<- operator:
code = <<-CODE
var1 = "foo"
var2 = "bar"
CODE
It would work for you to just embed ...\n" in your strings, I suppose. Here is a fun way to do it:
class String
def / s
self << s << "\n"
end
end
then
f = "" # => ""
f / 'line one' # => "line one\n"
f / 'line two' # => "line one\nline two\n"
f / 'line three' # => "line one\nline two\nline three\n"
This would enable something like:
"" / "line 1" / "line 2" / "line 3" # => "line 1\nline 2\nline 3\n"
Or even:
f/
"line one"/
"line two"/
"line three" # => "line one\nline two\nline three\n"
Here's a method presented here:
str = <<end.margin
|This here-document has a "left margin"
|at the vertical bar on each line.
|
| We can do inset quotations,
| hanging indentions, and so on.
end
This is accomplished by using this:
class String
def margin
arr = self.split("\n") # Split into lines
arr.map! {|x| x.sub!(/\s*\|/,"")} # Remove leading characters
str = arr.join("\n") # Rejoin into a single line
self.replace(str) # Replace contents of string
end
end
I guess the question with this is: does the lack of portability / presence of monkey patching make this solution bad.
What's wrong with:
code = "next line of code #{something}\n"+
"another line #{some_included_expression}"
You could place your multi-line text in a file, and use ERB to parse it (note ERB is included with Ruby)
require 'erb'
multi_line_string = File.open("multi_line_string.erb", 'r').read
template = ERB.new(multi_line_string)
template.result(binding)
(ERB can access variables from a Binding, an object that provides access to the instance methods and variables that are owned by another object. By setting it to "binding" it points to itself)
Documentation here.

Parsing delimited text with escape characters

I'm trying to parse (in Ruby) what's effectively the UNIX passwd file-format: comma delimiters, with an escape character \ such that anything escaped should be considered literally. I'm trying to use a regular expression for this, but I'm coming up short — even when using Oniguruma for lookahead/lookbehind assertions.
Essentially, all of the following should work:
a,b,c # => ["a", "b", "c"]
\a,b\,c # => ["a", "b,c"]
a,b,c\
d # => ["a", "b", "c\nd"]
a,b\\\,c # => ["a", "b\,c"]
Any ideas?
The first response looks pretty good. With a file containing
\a,,b\\\,c\,d,e\\f,\\,\
g
it gives:
[["\\a,"], [","], ["b\\\\\\,c\\,d,"], ["e\\\\f,"], ["\\\\,"], ["\\\ng\n"], [""]]
which is pretty close. I don't need the unescaping done on this first pass, as long as everything splits correctly on the commas. I tried Oniguruma and ended up with (the much longer):
Oniguruma::ORegexp.new(%{
(?: # - begins with (but doesn't capture)
(?<=\A) # - start of line
| # - (or)
(?<=,) # - a comma
)
(?: # - contains (but doesn't capture)
.*? # - any set of characters
[^\\\\]? # - not ending in a slash
(\\\\\\\\)* # - followed by an even number of slashes
)*?
(?: # - ends with (but doesn't capture)
(?=\Z) # - end of line
| # - (or)
(?=,)) # - a comma
},
'mx'
).scan(s)
Try this:
s.scan(/((?:\\.|[^,])*,?)/m)
It doesn't translate the characters following a \, but that can be done afterwards as a separate step.
I'd give the CSV class a try.
And a regex solution (hack?) might look like this:
#!/usr/bin/ruby -w
# contents of test.csv:
# a,b,c
# \a,b\,c
# a,b,c\
# d
# a,b\\\,c
file = File.new("test.csv", "r")
tokens = file.read.scan(/(?:\\.|[^,\r\n])*|\r?\n/m)
puts "-----------"
tokens.length.times do |i|
if tokens[i] == "\n" or tokens[i] == "\r\n"
puts "-----------"
else
puts ">" + tokens[i] + "<"
end
end
file.close
which will produce the output:
-----------
>a<
>b<
>c<
-----------
>\a<
>b\,c<
-----------
>a<
>b<
>c\
d<
-----------
>a<
>b\\\,c<
-----------

Resources