I read file using ruby and use .split to split line.
Example.txt
1
2
3
line1,line2,line3= #line.to_s.split("\n",3)
#actual
line1 => ["1
line2 => ", "2
line3 => ", "3"]
#what I expect
line1=1
line2=2
line3=3
how can i get what i expected?
Edit: it 's just 1 new line because I can't enter 1 new line in my question. To be more specific:
Example.txt
first_line\nsecond_line\nthird_line
File.open('Example.txt', 'r') do |f1|
#line = f1.readlines
f1.close
end
line1,line2,line3= #line.to_s.split("\n",3)
#actual
line1 => ["first_line
line2 => ", "second_line
line3 => ", "third_line"]
#what I expect
line1=first_line
line2=second_line
line3=third_line
You can't split using '\n', if you're trying to use line-ends. You MUST use "\n".
Strings using '\n' do not interpret \n as a line-ending, instead, they treat it as a literal backslash followed by "n":
'\n' # => "\\n"
"\n" # => "\n"
The question isn't at all clear, nor is the input file example clear given the little example code presented, however, guessing at what you want from the desired result...
If the input is a file called 'example.txt' looking like:
1
2
3
You can read it numerous ways:
File.read('example.txt').split("\n")
# => ["1", "2", "3"]
Or:
File.readlines('example.txt').map(&:chomp)
# => ["1", "2", "3"]
Either of those work, however, there is a very bad precedence set when reading files into memory like this. It's called "slurping" and can crash your code or take it, and the machine it's running on, to a crawl if the file is larger than the available memory. And, even if it fits into memory, loading a huge file into memory can cause pauses as memory is allocated, and reallocated. So, don't do that.
Instead, read the file line-by-line and process it that way if at all possible:
File.foreach('example.txt') do |line|
puts line
end
# >> 1
# >> 2
# >> 3
Don't do this:
File.open('Example.txt', 'r') do |f1|
#line = f1.readlines
f1.close
end
Ruby will automatically close a file opened like this:
File.open('Example.txt', 'r') do |f1|
...
end
There is no need to use close inside the block.
Depends a bit on what exactly you're expecting (and what #line is). If you are looking for numbers you can use:
line1, line2, line3 = #line.to_s.scan(/\d/)
If you want other characters you can use some other regular expression.
Related
I have a text file that starts with:
Title
aaa
bbb
ccc
I don't know what the line would include, but I know that the structure of the file will be Title, then an empty line, then the actual lines. I want to modify it to:
New Title
fff
aaa
bbb
ccc
I had this in mind:
lineArray = File.readlines(destinationFile).drop(2)
lineArray.insert(0, 'fff\n')
lineArray.insert(0, '\n')
lineArray.insert(0, 'new Title\n')
File.writelines(destinationFile, lineArray)
but writelines doesn't exist.
`writelines' for File:Class (NoMethodError)
Is there a way to delete the first two lines of the file an add three new lines?
I'd start with something like this:
NEWLINES = {
0 => "New Title",
1 => "\nfff"
}
File.open('test.txt.new', 'w') do |fo|
File.foreach('test.txt').with_index do |li, ln|
fo.puts (NEWLINES[ln] || li)
end
end
Here's the contents of test.txt.new after running:
New Title
fff
aaa
bbb
ccc
The idea is to provide a list of replacement lines in the NEWLINES hash. As each line is read from the original file the line number is checked in the hash, and if the line exists then the corresponding value is used, otherwise the original line is used.
If you want to read the entire file then substitute, it reduces the code a little, but the code will have scalability issues:
NEWLINES = [
"New Title",
"",
"fff"
]
file = File.readlines('test.txt')
File.open('test.txt.new', 'w') do |fo|
fo.puts NEWLINES
fo.puts file[(NEWLINES.size - 1) .. -1]
end
It's not very smart but it'll work for simple replacements.
If you really want to do it right, learn how diff works, create a diff file, then let it do the heavy lifting, as it's designed for this sort of task, runs extremely fast, and is used millions of times every day on *nix systems around the world.
Use put with the whole array:
File.open("destinationFile", "w+") do |f|
f.puts(lineArray)
end
If your files are big, the performance and memory implications of reading them into memory in their entirety are worth thinking about. If that's a concern, then your best bet is to treat the files as streams. Here's how I would do it.
First, define your replacement text:
require "stringio"
replacement = StringOI.new <<END
New Title
fff
END
I've made this a StringIO object, but it could also be a File object if your replacement text is in a file.
Now, open your destination file (a new file) and write each line from the replacement text into it.
dest = File.open(dest_fn, 'wb') do |dest|
replacement.each_line {|ln| dest << ln }
We could have done this more efficiently, but there's a good reason to do it this way: Now we can call replacement.lineno to get the number of lines read, instead of iterating over it a second time to count the lines.
Next, open the original file and seek ahead by calling gets replacement.lineno times:
orig = File.open(orig_fn, 'r')
replacement.lineno.times { orig.gets }
Finally, write the remaining lines from the original file to the new file. We'll do it more efficiently this time with File.copy_stream:
File.copy_stream(orig, dest)
orig.close
dest.close
That's it. Of course, it's a drag closing those files manually (and when we do we should do it in an ensure block), so it's better to use the block form of File.open to automatically close them. Also, we can move the orig.gets calls into the replacement.each_line loop:
File.open(dest_fn, 'wb') do |dest|
File.open(orig_fn, 'r') do |orig|
replacement.each_line {|ln| dest << ln; orig.gets }
File.copy_stream(orig, dest)
end
end
First create an input test file.
FNameIn = "test_in"
text = <<_
Title
How now,
brown cow?
_
#=> "Title\n\nHow now,\nbrown cow?\n"
File.write(FNameIn, text)
#=> 27
Now read and write line-by-line.
FNameOut = "test_out"
File.open(FNameIn) do |fin|
fin.gets; fin.gets
File.open(FNameOut, 'w') do |fout|
fout.puts "New Title"
fout.puts
fout.puts "fff"
until fin.eof?
fout.puts fin.gets
end
end
end
Check the result:
puts File.read(FNameOut)
# New Title
#
# fff
# How now,
# brown cow?
Ruby will close each of the two files when its block terminates.
If the files are not large, you could instead write:
File.write(FNameOut,
["New Title\n", "\n", "fff\n"].concat(File.readlines(FNameIn).drop(2)).join)
I have a text file (a.txt) that looks like the following.
open
close
open
open
close
open
I need to find a way to replace the 3rd line with "close". I did some search and most method involve searching for the line than replace it. Can't really do it here since I don't want to turn all the "open" to "close".
Essentially (for this case) I'm looking for a write version of IO.readlines("./a.txt") [2].
How about something like:
lines = File.readlines('file')
lines[2] = 'close' << $/
File.open('file', 'w') { |f| f.write(lines.join) }
str = <<-_
my
dog
has
fleas
_
FNameIn = 'in'
FNameOut = 'out'
First, let's write str to FNameIn:
File.write(FNameIn, str)
#=> 17
Here are a couple of ways to replace the third line of FNameIn with "had" when writing the contents of FNameIn to FNameOut.
#1 Read a line, write a line
If the file is large, you should read from the input file and write to the output file one line at a time, rather than keeping large strings or arrays of strings in memory.
fout = File.open(FNameOut, "w")
File.foreach(FNameIn).with_index { |s,i| fout.puts(i==2 ? "had" : s) }
fout.close
Let's check that FNameOut was written correctly:
puts File.read(FNameOut)
my
dog
had
fleas
Note that IO#puts writes a record separator if the string does not already end with a record separator.1. Also, if fout.close is omitted FNameOut is closed when fout goes out of scope.
#2 Use a regex
r = /
(?:[^\n]*\n) # Match a line in a non-capture group
{2} # Perform the above operation twice
\K # Discard all matches so far
[^\n]+ # Match next line up to the newline
/x # Free-spacing regex definition mode
File.write(FNameOut, File.read(FNameIn).sub(r,"had"))
puts File.read(FNameOut)
my
dog
had
fleas
1 File.superclass #=> IO, so IO's methods are inherited by File.
I need to find each occurrence of "$" and change it to a number using a count. eg str = "foo $ bar $ foo $ bar $ * run code here * => "foo 1 bar 2 foo 3 bar 4
It feels like this should be a lot easier than i'm making it out to be. Here's my code:
def counter(file)
f = File.open(file, "r+")
count = 0
contents = f.readlines do |s|
if s.scan =~ /\$/
count += 1
f.seek(1)
s.sub(/\$/, count.to_s)
else
puts "Total changes: #{count}"
end
end
end
However I'm not sure if I'm meant to be using .match, .scan, .find or whatever else.
When i run this it doesn't come up with any errors but it doesn't change anything either.
Your syntax for scan is incorrect and it should throw error.
You can try something along this line:
count = 0
str = "foo $ bar $ foo $ bar $ "
occurences = str.scan('$')
# => ["$", "$", "$", "$"]
occurences.size.times do str.sub!('$', (count+=1).to_s) end
str
# => "foo 1 bar 2 foo 3 bar 4 "
Explanation:
I am finding all occurences of $ in the string, then I am using sub! in iteration as it replaces only the first occurrence at a time.
Note: You may want to improve scan line by using regex with boundary match instead of plain "$" as it will replace $ even from within words. Eg: exa$mple will also get replace to something like: exa1mple
Why your code is not throwing error?
If you read the description about readlines, you will find:
Reads the entire file specified by name as individual lines, and
returns those lines in an array.
As it reads the entire file at once there is no value passing block along this method. Following example will make it more clear:
contents = f.readlines do |s|
puts "HELLO"
end
# => ["a\n", "b\n", "c\n", "d\n", "asdasd\n", "\n"] #lines of file f
As you can see "HELLO" never gets printed, showing the block code is never executed.
I have 2 log files that both contain lines of text starting with date stamps in chronological order. I want to merge both files into a single file that contains all lines from both files merged in chronological order. Due to the layout of the date stamps chronological in this case is the same as alphabetical.
I wrote a Ruby script that does this and it works fine, but I'm a Ruby novice just learning the language and one thing I really like about Ruby is all the syntactic sugar and the readability of the code. And I can't help but feel that my solution is quite clunky and there is surely a much nicer more elegant way to solve the same problem. I'm not necessarilly looking for algorithmically better, but syntactically. Ruby seems to have a one-liner solution for almost everything, so maybe for a problem like this too.
if ARGV.length != 2
puts "Wrong number of arguments. Expected 2 arguments (path to 2 log files to be merged)"
end
merged_file = File.open("merge_out.txt", "w")
file1 = File.open(ARGV[0], "r")
file2 = File.open(ARGV[1], "r")
line1 = file1.gets
line2 = file2.gets
while (line1 != nil or line2 !=nil)
if line1 == nil
# no more line1 so write line2 and proceed file2
merged_file.puts line2
line2 = file2.gets
elsif line2 == nil
# no more line2 so write line1 and proceed file1
merged_file.puts line1
line1 = file1.gets
else
comp = line1<=>line2
#both lines present, write and proceed the (alphabetically) smaller one
#as this is the one with the earlier time stamp
if comp == -1
merged_file.puts line1
line1 = file1.gets
else
merged_file.puts line2
line2 = file2.gets
end
end
end
So, how can this be done more elegantly?
Sometimes adding a dimension makes a solution prettier. Essentially, turn your file1, file2 variables into an array [ file1, file2 ], and this opens up a lot of Ruby Array syntax that performs the tests you have coded into your initial solution.
if ARGV.length < 2
puts "Wrong number of arguments. Expected 2 or more files to merge."
end
merged_file = File.open("merge_out.txt", "w")
files = ARGV.map { |filename| File.open( filename, "r") }
lines = files.map { |file| file.gets }
while lines.any?
next_line = lines.compact.min
file_id = lines.index( next_line )
merged_file.print next_line
lines[ file_id ] = files[ file_id ].gets
end
So not only is this shorter, but as a side effect can handle more input files at once. Although if you don't need that, simply change back first check.
I know it's not ruby, but for this kind of problem a simple bash command can do the work :
cat file1.dat file2.dat | sort > out.dat
If you really want ruby to do the work with a small piece of code :
File.open('out.dat','w') do |f|
f.puts (File.read('file1.dat') << File.read('file2.dat') ).split("\n").sort
end
Please note that this doesn't take into account the fact that your data is already partially sorted. It's concise, but not necessarily the most efficient way to do it.
You could merge your 4 cases into two cases: Simply ask yourself if line1 is the line to put first:
if line1 && line1 < line2
…
This isn't ruby syntax, but algorithmically, you can read each file into an array or list, then sort the array/list, then print it. It looks nicer using a built-in to do the sort. Depending on the implementation of the built-in, it may be faster than anything you can code, as well. I guess you would only worry about performance if it becomes an issue :)
In python:
# create a list with all the first file in it as elements
with open(file1) as f:
content = f.readlines()
# add the second file contents to the list
with open(file1) as f
content = content + f.readlines() # list catenation
content.sort() # sort the list
# join all the lines in the list together into a string an print them
outfile.write(''.join(content))
Since you're a ruby novice, it will be a good learning exercise to find the equivalent ruby builtins :)
Note that this is not a good idea if the files are huge, though, because it is sucking the whole file into memory. Once again - the price of simple code. If you have humungous files, you need some clunkier code to process them :)
Edit:
I googled some ruby. It appears that
# google "read file into array ruby"
array = IO.readlines file1_pathname + IO.readlines file2_pathname
# google "sort array ruby
array.sort
# google "print array ruby"
puts array.inspect
Is the kind of thing.
HTH
NeilSlater's comment on my other answer got my mind going on "so, if you can't slurp, then what?"
How about this:
line1 = file1.gets
line2 = file2.gets
while (line1 && line2)
while(line1.to_s >= line2.to_s) # to_s to protect against nil
merged_file.puts line2
line2 = file2.gets
end
while(line2.to_s > line1.to_s)
merged_file.puts line1
line1 = file1.gets
end
end
while(line1)
merged_file.puts line1
line1 = file1.gets
end
while(line2)
merged_file.puts line2
line2 = file2.gets
end
It's not a lot shorter, and doesn't use any syntactic magic or handy builtins, but at least it is more regular...
In Ruby language, how can I get the number of lines in a string?
There is a lines method for strings which returns an Enumerator. Call count on the enumerator.
str = "Hello\nWorld"
str.lines.count # 2
str = "Hello\nWorld\n" # trailing newline is ignored
str.lines.count # 2
The lines method was introduced in Ruby 1.8.7. If you're using an older version, checkout the answers by #mipadi and #Greg.
One way would be to count the number of line endings (\n or \r\n, depending on the string), the caveat being that if the string does not end in a new line, you'll have to make sure to add one to your count. You could do so with the following:
c = my_string.count("\n")
c += 1 unless c[-1,1] == "\n"
You could also just loop through the string and count the lines:
c = 0
my_string.each { |line| c += 1 }
Continuing with that solution, you could get really fancy and use inject:
c = my_string.each.inject(0) { |count, line| count += 1 }
string".split("\n").size works nicely. I like that it ignores trailing new-lines if they don't contain content.
"Hello\nWorld\n".split("\n") # => ["Hello", "World"]
"hello\nworld\nfoo bar\n\n".split("\n").size # => 3
That might not be what you want, so use lines() as #Anurag suggested instead if you need to honor all new-lines.
"hello\nworld\nfoo bar\n\n".lines.count # => 4
"hello\nworld\nfoo bar\n\n".chomp.split("\n",-1).size # => 4
String#chomp gets rid of an end of line if it exists, and the -1 allows empty strings.
given a file object (here, in rails)
file = File.open(File.join(Rails.root, 'lib', 'file.json'))
file.readlines.count
returns the number of lines
IO#readlines performs a split method on strings (IOStrings in this case) using newlines as the separator
This will not count blank lines:
string.split("\n").select{ |line| line != "" }.size