Dynamic variable from string in Ruby [duplicate] - ruby

This question already has answers here:
How to dynamically create a local variable?
(4 answers)
Closed 3 years ago.
I am copying each line of a file to separate files, depending on the content. Each line begins with "foo" or "bar", and I want to read the first few characters of each line and dynamically change the file name variable.
readfile = File.open("myfile.txt", 'r')
file_foo = File.open("file1.txt", 'w')
file_bar = File.open("file2.txt", 'w')
for line in readfile
writefile = 'file_' + line[0..2]
writefile.write(line)
end
file_foo.close
file_bar.close
This throws an error, as the variable writefile refers to the string "file_foo" or "file_bar".
Suggestions for an elegant Rubyist solution? I couldn't see from the documentation how send method could be applied here if that is indeed the way to go.

Make a hash of files:
readfile = File.open("myfile.txt", 'r')
files = {
'foo' => File.open("file1.txt", 'w'),
'bar' => File.open("file2.txt", 'w')
}
for line in readfile
files[line[0..2]].write(line)
end
files.each {|k, v| v.close}

I think you are looking for eval. It will take a string and evaluate it as Ruby code in the current context. So your example becomes:
readfile = File.open("myfile.txt", 'r')
file_foo = File.open("file1.txt", 'w')
file_bar = File.open("file2.txt", 'w')
for line in readfile
eval('file_' + line[0..2]).write(line)
end
filefoo.close
filebar.close
However, you asked for a "Rubyist" approach. Using eval is certainly NOT a Rubyist approach. Nor is the use of for loops. I'll take a crack at a more Rubyist approach:
infile = "myfile.txt"
foofile = "file1.txt"
barfile = "file2.txt"
def append_to_file(path, content)
File.open(path, 'a') { |f| f << content }
end
IO.readlines(readfile).each do |line|
case line
when /^foo/
append_to_file(foofile, line)
when /^bar/
append_to_file(barfile, line)
end
end

You cannot use send because what you are trying to convert a string into is not a method but is a local variable.
From Ruby 2.1, you will be able to use Binding#local_variable_get.
for line in readfile
writefile = binding.local_variable_get(:"file_#{line[0..2]}")
writefile.write(line)
end

Related

Writing regex result into a new file

I've got a list of devices:
ipc-bei640-r-br-01
ipc-bei640-r-br-02
ipc-bei640-r-br-03
ipc-bei640-r-br-04
ipc-bei640-r-br-05
ipc-bem640-r-br-01
ipc-bem640-r-br-02
ipc-bem640-r-br-03
ipc-crg660-r-br-02
ipc-geb680-r-br-04
ipc-lgv630-r-br-01
This small little ruby script counts the lines of the file braslist.txt scans it with a regex and writes the results to a newfile called "strippedfile.txt"
lines = IO.readlines("/usr/local/bin/braslist.txt")
# Linecount is forwarded to StdOut.
puts lines.length
str = File.read('braslist.txt')
file_name = ['strippedfile.txt']
file_name.each do |file_name|
text = File.read(file_name)
new_contents = str.scan(/^ipc-(?<bng>[a-z]{3}\d{3})-r-br(?<nr>-\d{2})$/)
# open and write to a file with ruby
open('strippedfile.txt', 'w') { |f|
f.print new_contents
}
end
Now what i cant seem to fix, is in the new file "strippedfile" the results are always ["bei640", "-01"] ["bei640", "-02"] ["bei640", "-03"]
And i am trying to get all results in this format:
bei640-01
bei640-02
bei640-03
bei640-04
scan returns an array of matches, you probably want to join them:
- new_contents = str.scan(/^ipc-(?<bng>[a-z]{3}\d{3})-r-br(?<nr>-\d{2})$/)
+ new_contents = str.scan(/^ipc-(?<bng>[a-z]{3}\d{3})-r-br(?<nr>-\d{2})$/).map(&:join)
To print everything without quotes and brackets line by line:
- f.print new_contents
+ f.puts new_contents
Assuming your resultant array is
a = [["bei640", "-02"], ["bei640", "-03"]]
You can use join to get your desired result
a.map{|i| i.join } #=> ["bei640-02", "bei640-03"]
or use shortcut as mudasobwa answered
a.map(&:join) #=> ["bei640-02", "bei640-03"]

How to append a text to file succinctly

Instead of writing
File.open("foo.txt", "w"){|f| f.write("foo")}
We can write it
File.write("foo.txt", "foo")
Is there simpler way to write this one?
File.open("foo.txt", "a"){|f| f.write("foo")}
This has been answered in great depth already:
can you create / write / append a string to a file in a single line in Ruby
File.write('some-file.txt', 'here is some text', File.size('some-file.txt'), mode: 'a')
f = File.open('foo.txt', 'a')
f.write('foo')
f.close
Yes. It's poorly documented, but you can use:
File.write('foo.txt', 'some text', mode: 'a+')
You can use << instead of .write:
File.open("foo.txt", "a") { |f| f << "foo" }
Although this was answered with multiple options, I have always felt that if Ruby has File.write path, content, it should also have File.append path, content, especially since the existing append syntax is not very pleasant.
So, whenever I need to append, I usually add this extension:
class File
class << self
def append(path, content)
File.open(path, "a") { |f| f << content }
end
end
end
# Now it feels intuitive:
File.write "note.txt", "hello\n"
File.append "note.txt", "world\n"

In Ruby- Parsing Directory and reading first row of the file

Below is the piece of code that is supposed read the directory and for each file entry prints the first row of the file. The issue is x is not visible so file is not being parsed.
Dir.foreach("C:/fileload/src") do |file_name|
x = file_name
puts x
f = File.open("C:/fileload/src/" +x)
f.readlines[1..1].each do |line|
puts line
end
end
Why are you assigning x to file_name? You can use file_name directly. And if you are only reading the first line of the file, why not try this?
#!/usr/bin/ruby
dir = "C:/fileload/src"
Dir.foreach(dir) do |file_name|
full = File.join(dir, file_name)
if File.file?(full)
f = File.open(full)
puts f.first
f.close
end
end
You should use File.join to safely combine paths in Ruby. I also checked that you are opening a file using the File.file? method.
You have no visibility issue with x. You should be using File::join or Pathname#+ to build your file paths. You should exclude non-files from consideration. You're selecting the second line, not the first with [1..1]. Here's a cleaner correct replacement for your sample code.
dir = "C:/fileload/src"
Dir.foreach(dir).
map { |fn| File.join(dir,fn) }.
select { |fn| File.file?(fn) }.
each { |fn| puts File.readlines(fn).first }

How can I copy the contents of one file to another using Ruby's file methods?

I want to copy the contents of one file to another using Ruby's file methods.
How can I do it using a simple Ruby program using file methods?
There is a very handy method for this - the IO#copy_stream method - see the output of ri copy_stream
Example usage:
File.open('src.txt') do |f|
f.puts 'Some text'
end
IO.copy_stream('src.txt', 'dest.txt')
For those that are interested, here's a variation of the IO#copy_stream, File#open + block answer(s) (written against ruby 2.2.x, 3 years too late).
copy = Tempfile.new
File.open(file, 'rb') do |input_stream|
File.open(copy, 'wb') do |output_stream|
IO.copy_stream(input_stream, output_stream)
end
end
As a precaution I would recommend using buffer unless you can guarantee whole file always fits into memory:
File.open("source", "rb") do |input|
File.open("target", "wb") do |output|
while buff = input.read(4096)
output.write(buff)
end
end
end
Here my implementation
class File
def self.copy(source, target)
File.open(source, 'rb') do |infile|
File.open(target, 'wb') do |outfile2|
while buffer = infile.read(4096)
outfile2 << buffer
end
end
end
end
end
Usage:
File.copy sourcepath, targetpath
Here is a simple way of doing that using ruby file operation methods :
source_file, destination_file = ARGV
script = $0
input = File.open(source_file)
data_to_copy = input.read() # gather the data using read() method
puts "The source file is #{data_to_copy.length} bytes long"
output = File.open(destination_file, 'w')
output.write(data_to_copy) # write up the data using write() method
puts "File has been copied"
output.close()
input.close()
You can also use File.exists? to check if the file exists or not. This would return a boolean true if it does!!
Here's a fast and concise way to do it.
# Open first file, read it, store it, then close it
input = File.open(ARGV[0]) {|f| f.read() }
# Open second file, write to it, then close it
output = File.open(ARGV[1], 'w') {|f| f.write(input) }
An example for running this would be.
$ ruby this_script.rb from_file.txt to_file.txt
This runs this_script.rb and takes in two arguments through the command-line. The first one in our case is from_file.txt (text being copied from) and the second argument second_file.txt (text being copied to).
You can also use File.binread and File.binwrite if you wish to hold onto the file contents for a bit. (Other answers use an instant copy_stream instead.)
If the contents are other than plain text files, such as images, using basic File.read and File.write won't work.
temp_image = Tempfile.new('image.jpg')
actual_img = IO.binread('image.jpg')
IO.binwrite(temp_image, actual_img)
Source: binread,
binwrite.

Read Certain Lines from File

Hi just getting into Ruby, and I am trying to learn some basic file reading commands, and I haven't found any solid sources yet.
I am trying to go through certain lines from that file, til the end of the file.
So in the file where it says FILE_SOURCES I want to read all the sources til end of file, and place them in a file.
I found printing the whole file, and replacing words in the file, but I just want to read certain parts in the file.
Usually you follow a pattern like this if you're trying to extract a section from a file that's delimited somehow:
open(filename) do |f|
state = nil
while (line = f.gets)
case (state)
when nil
# Look for the line beginning with "FILE_SOURCES"
if (line.match(/^FILE_SOURCES/))
state = :sources
end
when :sources
# Stop printing if you hit something starting with "END"
if (line.match(/^END/))
state = nil
else
print line
end
end
end
end
You can change from one state to another depending on what part of the file you're in.
I would do it like this (assuming you can read the entire file into memory):
source_lines = IO.readlines('source_file.txt')
start_line = source_lines.index{ |line| line =~ /SOURCE_LINE/ } + 1
File.open( 'other_file.txt', 'w' ) do |f|
f << source_lines[ start_line..-1 ].join( "\n" )
end
Relevant methods:
IO.readlines to read the lines into an array
Array#index to find the index of the first line matching a regular expression
File.open to create a new file on disk (and automatically close it when done)
Array#[] to get the subset of lines from the index to the end
If you can't read the entire file into memory, then I'd do a simpler variation on #tadman's state-based one:
started = false
File.open( 'other_file.txt', 'w' ) do |output|
IO.foreach( 'source_file.txt' ) do |line|
if started then
output << line
elsif line =~ /FILE_SOURCES/
started = true
end
end
end
Welcome to Ruby!
File.open("file_to_read.txt", "r") {|f|
line = f.gets
until line.include?("FILE_SOURCES")
line = f.gets
end
File.open("file_to_write.txt", "w") {|new_file|
f.each_line {|line|
new_file.puts(line)
}
new_file.close
}
f.close
}
IO functions have no idea what "lines" in a file are. There's no straightforward way to skip to a certain line in a file, you'll have to read it all and ignore the lines you don't need.

Resources