How to append a text to file succinctly - ruby

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"

Related

Dynamic variable from string in Ruby [duplicate]

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

Ruby blank line in file won't remove

I must've browsed every solution on StackOverflow, nothing seems to be removing the blank line's from text file which looks like this:
google
yahoo
facebook
reddit
Amongst other sources, I've tried:
File.foreach("file.txt") { |line|
line.gsub(/^$\n/, '')
}
and
replace = text.gsub /^$\n/, ''
File.open("file.txt", "w") { |file| file.puts replace }
However, these aren't working. I'm tearing my hair out, it seems that there is no native Nokogiri method, and regular expressions aren't working either.
How about you check if it is empty instead?
out = File.new("out.txt", "w")
File.foreach("file.txt") { |line|
out.puts line unless line.chomp.empty?
}
I use below one liner to delete all blank lines from a file
file = "/tmp/hello.log"
File.write(file, File.read(file).gsub(/\n+/,"\n"))
Change the gsub a little bit and it will work
File.foreach("file.txt"){|line|
line.gsub("\n", '')
}
source_file = '/hello.txt'
new_file = File.new('/hello_new.txt')
File::open(new_file,'w') do |file|
File::open(source_file,'r').each(sep="\n") do |line|
file << line unless line.gsub("\n",'').length == 0
end
end
String#squeeze is nice for this. Here it reduces series of line-ends to a single line-end.
open("out.txt", "w") {|out| open("test.txt") {|in| out << in.read.squeeze("\n")}}

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.

Passing in file extension Variable

I am trying to pass in the file extension as a variable but it doesn't seem to be working. when I run getlist.rb(txt)
def getlist(extension)
file = File.new("the_list.txt", "w")
Dir['../path/*.'+ extension].each { |f| file.puts File.basename(f, '.'+ extension).upcase }
end
Basically I want to drop in any file extension (txt, pdf, rb, etc) and it will give me the list without the extension names. Script works fine when it is hard coded just doesn't work when I try to drop in a variable.
Best Regards,
AZCards
My example:
def getlist(extension)
File.open("the_list.txt", "w"){|file|
Dir["*.#{extension}"].each { |f|
file << File.basename(f, File.extname(f)).upcase
file << "\n"
}
}
end
getlist('rb')
getlist(:rb)
My main modifications:
File.open isnted new (you forgot file.close). With File.open and a block the close is done automatic.
Replaces String+ with #-replacements. Advantage: you can use symbols when you call it (you wrote getlist.rb(txt) - this only works, when txt is a variable, but I think you want 'txt')
Use File#extension., see Daves answer.
I added newlines to the resulting file.
Update
Just in case you need a solution with multiple extensions and to call it from command line:
def getlist(*extensions)
p "*.{#{extensions.join(',')}}"
File.open("the_list.txt", "w"){|file|
Dir["*.{#{extensions.join(',')}}"].each { |f|
file << File.basename(f, File.extname(f)).upcase
file << "\n"
}
}
end
#getlist('rb', 'txt')
#getlist(:rb, :txt)
getlist(*ARGV)
Dir['../path/*.'+ extension].each { |f| file.puts File.basename(f, File.extname(f)).upcase }
That said, it works fine for me when using your original string concatenation, too:
ruby-1.9.2-p0 :035 > getlist("html")
=> ["./about.html", "./epl-v10.html", "./notice.html"]
ruby-1.9.2-p0 :037 > getlist("*")
=> ["./libcairo-swt.so", "./eclipse.ini", "./icon.xpm", "./eclipse.ini~", "./about.html", "./epl-v10.html", "./artifacts.xml", "./notice.html"]
ruby-1.9.2-p0 :038 > getlist("ini")
=> ["./eclipse.ini"]
(Prints w/o extension removed for space purposes, but they print w/o the extension, in uppercase.)

trying to find the 1st instance of a string in a CSV using fastercsv

I'm trying to open a CSV file, look up a string, and then return the 2nd column of the csv file, but only the the first instance of it. I've gotten as far as the following, but unfortunately, it returns every instance. I'm a bit flummoxed.
Can the gods of Ruby help? Thanks much in advance.
M
for the purpose of this example, let's say names.csv is a file with the following:
foo, happy
foo, sad
bar, tired
foo, hungry
foo, bad
#!/usr/local/bin/ruby -w
require 'rubygems'
require 'fastercsv'
require 'pp'
FasterCSV.open('newfile.csv', 'w') do |output|
FasterCSV.foreach('names.csv') do |lookup|
index_PL = lookup.index('foo')
if index_PL
output << lookup[2]
end
end
end
ok, so, if I want to return all instances of foo, but in a csv, then how does that work?
so what I'd like as an outcome is happy, sad, hungry, bad. I thought it would be:
FasterCSV.open('newfile.csv', 'w') do |output|
FasterCSV.foreach('names.csv') do |lookup|
index_PL = lookup.index('foo')
if index_PL
build_str << "," << lookup[2]
end
output << build_str
end
end
but it does not seem to work
Replace foreach with open (to get an Enumerable) and find:
FasterCSV.open('newfile.csv', 'w') do |output|
output << FasterCSV.open('names.csv').find { |r| r.index('foo') }[2]
end
The index call will return nil if it doesn't find anything; that means that the find will give you the first row that has 'foo' and you can pull out the column at index 2 from the result.
If you're not certain that names.csv will have what you're looking for then a bit of error checking would be advisable:
FasterCSV.open('newfile.csv', 'w') do |output|
foos_row = FasterCSV.open('names.csv').find { |r| r.index('foo') }
if(foos_row)
output << foos_row[2]
else
# complain or something
end
end
Or, if you want to silently ignore the lack of 'foo' and use an empty string instead, you could do something like this:
FasterCSV.open('newfile.csv', 'w') do |output|
output << (FasterCSV.open('names.csv').find { |r| r.index('foo') } || ['','',''])[2]
end
I'd probably go with the "complain if it isn't found" version though.

Resources