I have a file containing substituted variables (#{...}) and I would like to copy it into another file, with the variables substituted by their values.
Here's what I have
file = File.open(#batch_file_name, "w+")
script=File.open("/runBatch.script","r")
script.each do |line|
file.puts(line)
end
But this is apparently not the right way to do that. Any suggestion ?
Instead of #{...} in your file use ERB files.
No, this isn't the right way to do it. You can't expect Ruby to magically interpret any #{} it encounters anywhere in your data as variable interpolation. This would (amongst other terrible side effects) yield massive security problems everywhere.
If you want to interpolate data into a string you'll need to eval it, which has its own security risks:
str = 'The value of x is #{x}'
puts str # The value of x is #{x}
x = "123"
puts eval "\"#{str}\"" # Thje value of x is 123
It's not clear which variables you're trying to interpolate into your data. This is almost certainly the wrong way to go about doing whatever it is your doing.
Ok say you have a file named tmp.file that has the following text:
This is #{foobar}!
Then you can easily do the following:
str = ""
File.open("tmp.file", "r") do |f|
str = f.read
end
abc = "Sparta"
puts eval('"' + str + '"')
And your result would be This is Sparta!
But as already suggested you should go with a real template solution like ERB. Then you would use your files like views in Rails. Instead of This is #{foobar}. you would have This is <%= foobar %>.
Related
How can I get the filename without the extensions? For example, input of "/dir1/dir2/test.html.erb" should return "test".
In actual code I will passing in __FILE__ instead of "/dir1/dir2/test.html.erb".
Read documentation:
basename(file_name [, suffix] ) → base_name
Returns the last component of the filename given in file_name, which
can be formed using both File::SEPARATOR and File::ALT_SEPARATOR as
the separator when File::ALT_SEPARATOR is not nil. If suffix is given
and present at the end of file_name, it is removed.
=> File.basename('public/500.html', '.html')
=> "500"
in you case:
=> File.basename("test.html.erb", ".html.erb")
=> "test"
How about this
File.basename(f, File.extname(f))
returns the file name without the extension.. works for filenames with multiple '.' in it.
In case you don't know the extension you can combine File.basename with File.extname:
filepath = "dir/dir/filename.extension"
File.basename(filepath, File.extname(filepath)) #=> "filename"
Pathname provides a convenient object-oriented interface for dealing with file names.
One method lets you replace the existing extension with a new one, and that method accepts the empty string as an argument:
>> Pathname('foo.bar').sub_ext ''
=> #<Pathname:foo>
>> Pathname('foo.bar.baz').sub_ext ''
=> #<Pathname:foo.bar>
>> Pathname('foo').sub_ext ''
=> #<Pathname:foo>
This is a convenient way to get the filename stripped of its extension, if there is one.
But if you want to get rid of all extensions, you can use a regex:
>> "foo.bar.baz".sub(/(?<=.)\..*/, '')
=> "foo"
Note that this only works on bare filenames, not paths like foo.bar/pepe.baz. For that, you might as well use a function:
def without_extensions(path)
p = Pathname(path)
p.parent / p.basename.sub(
/
(?<=.) # look-behind: ensure some character, e.g., for ‘.foo’
\. # literal ‘.’
.* # extensions
/x, '')
end
Split by dot and the first part is what you want.
filename = 'test.html.erb'
result = filename.split('.')[0]
Considering the premise, the most appropriate answer for this case (and similar cases with other extensions) would be something such as this:
__FILE__.split('.')[0...-1].join('.')
Which will only remove the extension (not the other parts of the name: myfile.html.erb here becomes myfile.html, rather than just myfile.
Thanks to #xdazz and #Monk_Code for their ideas. In case others are looking, the final code I'm using is:
File.basename(__FILE__, ".*").split('.')[0]
This generically allows you to remove the full path in the front and the extensions in the back of the file, giving only the name of the file without any dots or slashes.
name = "filename.100.jpg"
puts "#{name.split('.')[-1]}"
Yet understanding it's not a multiplatform solution, it'd work for unixes:
def without_extensions(path)
lastSlash = path.rindex('/')
if lastSlash.nil?
theFile = path
else
theFile = path[lastSlash+1..-1]
end
# not an easy thing to define
# what an extension is
theFile[0...theFile.index('.')]
end
puts without_extensions("test.html.erb")
puts without_extensions("/test.html.erb")
puts without_extensions("a.b/test.html.erb")
puts without_extensions("/a.b/test.html.erb")
puts without_extensions("c.d/a.b/test.html.erb")
Sorry for the newbie question. Was loading a .txt file into the following code:
line_count = 0
File.open("text.txt").each {|line| line_count += 1}
puts line_count
Does .each simply read until the end of a line before passing its value to the code block? Little explanation would be great. Thanks!
You can use .each_line to be more explicit, but yes, http://www.ruby-doc.org/core-2.0.0/IO.html#method-i-each each reads a line.
f = File.new("testfile")
f.each {|line| puts "#{f.lineno}: #{line}" }
It's really important to read the documentation, because all sorts of things are explained there. For instance, the documentation for each says:
Executes the block for every line in ios, where lines are separated by sep.
sep means "\r", "\n" or "\r\n", depending on the OS the code is running on which is also the value of the special $/ global variable which contains the default line-ending character for that OS. You can tell Ruby to use a different value for the line-end/separator if you know the file uses something else.
Regarding your code:
I'd do it this way:
line_count = 0
File.foreach("text.txt") do |line|
line_count += 1
end
puts line_count
foreach is very self-explanatory, which is important when writing code. You want it to be self-documenting as much as possible. foreach iterates over "each" line in the file. It also assumes the line-ends are the same as $/, but you can force it to be something different, perhaps the letter "z" or "." or " ", depending on your whim and fancy at the moment.
I'd like to switch the extension of a file. For example:
test_dir/test_file.jpg to .txt should give test_dir/test_file.txt.
I also want the solution to work on a file with two extensions.
test_dir/test_file.ext1.jpg to .txt should should give test_dir/test_file.ext1.txt
Similarly, on a file with no extension it should just add the extension.
test_dir/test_file to .txt should give test_dir/test_file.txt
I feel like this should be simple, but I haven't found a simple solution. Here is what I have right now. I think it is really ugly, but it does seem to work.
def switch_ext(f, new_ext)
File.join(File.dirname(f), File.basename(f, File.extname(f))) + new_ext
end
Do you have any more elegant ways to do this? I've looked on the internet, but I'm guessing that I'm missing something obvious. Are there any gotcha's to be aware of? I prefer a solution that doesn't use a regular expression.
Your example method isn't that ugly. Please do continue to use file naming semantic aware methods over string regexp. You could try the Pathname stdlib which might make it a little cleaner:
require 'pathname'
def switch_ext(f, new_ext)
p = Pathname.new f
p.dirname + "#{ p.basename('.*') }#{ new_ext }"
end
>> puts %w{ test_dir/test_file.jpg test_dir/test_file.ext1.jpg testfile .vimrc }.
| map{|f| switch_ext f, '.txt' }
test_dir/test_file.txt
test_dir/test_file.ext1.txt
testfile.txt
.vimrc.txt
def switch_ext(f, new_ext)
(n = f.rindex('.')) == 0 ? nil : (f[0..n] + new_ext)
end
It will find the most right occurrence of '.' if it is not the first character.
Regular expressions were invented for this sort of task.
def switch_ext f, new_ext
f.sub(/((?<!\A)\.[^.]+)?\Z/, new_ext)
end
puts switch_ext 'test_dir/test_file.jpg', '.txt'
puts switch_ext 'test_dir/test_file.ext1.jpg', '.txt'
puts switch_ext 'testfile', '.txt'
puts switch_ext '.vimrc', '.txt'
Output:
test_dir/test_file.txt
test_dir/test_file.ext1.txt
testfile.txt
.vimrc.txt
def switch_ext(filename, new_ext)
filename.chomp( File.extname(filename)) + new_ext
end
I just found this answer to my own question here at the bottom of this long discussion.
http://www.ruby-forum.com/topic/179524
I personally think it is the best one I've seen. I definitely want to avoid a regular expression, because they are hard for me to remember and therefore error prone.
For dotfiles this function just adds the extension onto the file. This behaviour seems sensible to me.
switch_ext('.vimrc', '.txt') # => ".vimrc.txt"
Please continue to post better answers if there are any, and post comments to let me know if you see any deficiencies in this answer. I'll leave the question open for now.
You can use regular expressions, or you can use things like the built-in filename manipulation tools in File:
%w[
test_dir/test_file.jpg
test_dir/test_file.ext1.jpg
test_dir/test_file
].each do |fn|
puts File.join(
File.dirname(fn),
File.basename(fn, File.extname(fn)) + '.txt'
)
end
Which outputs:
test_dir/test_file.txt
test_dir/test_file.ext1.txt
test_dir/test_file.txt
I personally use the File methods. They're aware of different OS's needs for filename separators so porting to another OS is a no brainer. In your use-case it's not a big deal. Mix in path manipulations and it becomes more important.
def switch_ext f, new_ext
"#{f.sub(/\.[^.]+\z/, "")}.#{new_ext}"
end
def switch_ext(filename, ext)
begin
filename[/\.\w+$/] = ext
rescue
filename << ext
end
filename
end
Usage
>> switch_ext('test_dir/test_file.jpeg', '.txt')
=> "test_dir/test_file.txt"
>> switch_ext('test_dir/no_ext_file', '.txt')
=> "test_dir/no_ext_file.txt"
Hope this help.
Since Ruby 1.9.1 the easiest answer is Pathname::sub_ext(replacement) which strips off the extension and replaces it with the given replacement (which can be an empty string ''):
Pathname.new('test_dir/test_file.jpg').sub_ext('.txt')
=> #<Pathname:test_dir/test_file.txt>
Pathname.new('test_dir/test_file.ext1.jpg').sub_ext('.txt')
=> #<Pathname:test_dir/test_file.ext1.txt>
Pathname.new('test_dir/test_file').sub_ext('.txt')
=> #<Pathname:test_dir/test_file.txt>
Pathname.new('test_dir/test_file.txt').sub_ext('')
=> #<Pathname:test_dir/test_file>
One thing to watch out for is that you need to have a leading . in the replacement:
Pathname.new('test_dir/test_file.jpg').sub_ext('txt')
=> #<Pathname:test_dir/test_filetxt>
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.
I have a Ruby script that used string interpolation to build error messages.
p "#{vName} is not a defined variable" => 'xxx is not a defined variable'
Another programmer came through and attempted to externalize the string literals to a separate configuration file. Of course, he doesn't get the substitution.
p err_string_from_config => '#{vName} is not a defined variable'
I've looked around, but couldn't come up with anything better than converting to sprintf strings and using printf.
Does anybody know how to get the #{} substitution to work on strings that are not double quote literals within the Ruby script?
Actually Ruby has functionality very similar to John's Python example:
$ irb
>> greeting = 'hello %s, my name is %s!'
>> interpolated = greeting % ['Mike', 'John']
=> "hello Mike, my name is John!"
>>
This is also useful if your argument is an array constant. If you must use #{} style interpolation you could use eval:
>> greeting = 'hi #{name}' # notice name is not defined yet
>> name = "mike"
>> eval '"' + greeting + '"'
The eval approach is going to be much slower than using % style interpolation, so it's a trade-off.
I suggest that you look at Liquid templating language which provides more powerful features (e.g. you can reference parameters by name).
Previous example would look like:
greeting = Liquid::Template.parse("hello {{your_name}}, my name is {{my_name}}!")
interpolated = greeting.render('your_name' => 'Mike', 'my_name' => 'John')
# => "hello Mike, my name is John!"
Here's how I do it, just for the record. A bit clearer imo.
gief = '#{later} please'
later = "later"
puts eval(%Q["#{gief}"])
# => later please
But honestly, that is such a hack. Unless you have a really good reason to use a string, use a proc instead. I always try to use plain ruby instead of evaling strings.
gief = Proc.new {|l| "#{l} please" }
later = "later"
puts gief.call(later)
# => later please