Modifying content in a file

Hi all,

I've written a little code that replaces some content in a file, but I really think there is a better way to do so. Here's my code:

def clean_file(filename)
   # Define the new caption
   caption = "caption: new caption"

   # Process the content of the file
   File.open(filename) do |from_file|
     File.open(filename + ".new", 'w') do |to_file|
       from_file.each do |line|
         case
           when line =~ /^caption:.*$/: line.sub!($&, caption)
         end
         to_file.print line
       end
     end
   end

   # Rename the file
   File.delete(filename)
   File.rename(filename + ".new", filename)
end

Can't it be done using the read-write mode on the file? Any better proposition is welcome :wink:

Thanks.

Ghislain

"Ghislain Mary" <nospam@lunacymaze.org> schrieb im Newsbeitrag
news:4263856D.3050008@lunacymaze.org...

Hi all,

I've written a little code that replaces some content in a file, but I
really think there is a better way to do so. Here's my code:

def clean_file(filename)
   # Define the new caption
   caption = "caption: new caption"

   # Process the content of the file
   File.open(filename) do |from_file|
     File.open(filename + ".new", 'w') do |to_file|
       from_file.each do |line|
         case
           when line =~ /^caption:.*$/: line.sub!($&, caption)
         end
         to_file.print line

You can do that easier:

to_file.puts( /^caption:/ =~ line ? caption : line )

       end
     end
   end

   # Rename the file
   File.delete(filename)
   File.rename(filename + ".new", filename)
end

Can't it be done using the read-write mode on the file? Any better
proposition is welcome :wink:

Depends on your file's sizes. If they are moderately sized you can slurp
the whole thing in, modify contents in mem with a single gsub and then
write them in one go.

Regards

    robert

Excerpts from Ghislain Mary's mail of 18 Apr 2005 (EDT):

def clean_file(filename)
  # Define the new caption
  caption = "caption: new caption"

  # Process the content of the file
  File.open(filename) do |from_file|
    File.open(filename + ".new", 'w') do |to_file|
      from_file.each do |line|
        case
          when line =~ /^caption:.*$/: line.sub!($&, caption)
        end
        to_file.print line
      end
    end
  end

  # Rename the file
  File.delete(filename)
  File.rename(filename + ".new", filename)
end

Sure, here's a two line version:

def clean_file(filename)
  caption = "caption: new caption"
  system %%ruby -i -pe '$_.gsub! /^caption:.*$/,"#{caption}"' #{filename}%
end

(heh heh heh...)

···

--
William <wmorgan-ruby-talk@masanjin.net>

Robert Klemme a écrit :

You can do that easier:

to_file.puts( /^caption:/ =~ line ? caption : line )

In fact, I am doing several substitution, but I forgot to tell it.

Depends on your file's sizes. If they are moderately sized you can slurp
the whole thing in, modify contents in mem with a single gsub and then
write them in one go.

Thanks. Since the file can be quite large, I continue using something
like before but with a Tempfile now. That gives something like:

require 'tempfile'
require 'fileutils'
include FileUtils

def clean_file(filename)
   # Define the new caption
   caption = "caption: my caption"

   # Process the content of the file
   begin
     tf = Tempfile.new(filename)
     File.open(filename) do |from_file|
       from_file.each do |line|
         case
           when line =~ /^caption:/: line.sub!($&, caption)
           # Other substitutions...
         end
         tf.print line
       end
     end
   ensure
     tf.close unless tf.nil?
   end

   # Replace the previous file with the new.
   cp(tf.path, filename)
end

But can't we open a file in read-write mode and directly do
substitutions in it without using an auxiliary file?

Ghislain

Excerpts from William Morgan's mail of 19 Apr 2005 (EDT):

Sure, here's a two line version:

def clean_file(filename)
  caption = "caption: new caption"
  system %%ruby -i -pe '$_.gsub! /^caption:.*$/,"#{caption}"' #{filename}%
end

Oh wait, I thought of a shorter version:

def clean_file(filename)
  caption = "caption: new caption"
  system %%perl -i -pe 's/^caption:.*$/#{caption}/' #{filename}%
end

···

--
William <wmorgan-ruby-talk@masanjin.net>

You can use IO.pos to seek within a file, however what will you do if
your substitute string is not the same length as the original string?
Files have no concept of insert/delete.

···

On 4/19/05, Ghislain Mary <nospam@lunacymaze.org> wrote:

But can't we open a file in read-write mode and directly do
substitutions in it without using an auxiliary file?

--
Into RFID? www.rfidnewsupdate.com Simple, fast, news.

Robert Klemme a écrit :
> You can do that easier:
>
> to_file.puts( /^caption:/ =~ line ? caption : line )
>

In fact, I am doing several substitution, but I forgot to tell it.

>
> Depends on your file's sizes. If they are moderately sized you can slurp
> the whole thing in, modify contents in mem with a single gsub and then
> write them in one go.
>

Thanks. Since the file can be quite large, I continue using something
like before but with a Tempfile now. That gives something like:

require 'tempfile'
require 'fileutils'
include FileUtils

def clean_file(filename)
   # Define the new caption
   caption = "caption: my caption"

   # Process the content of the file
   begin
     tf = Tempfile.new(filename)
     File.open(filename) do |from_file|
       from_file.each do |line|
         case
           when line =~ /^caption:/: line.sub!($&, caption)
           # Other substitutions...
         end
         tf.print line
       end
     end
   ensure
     tf.close unless tf.nil?
   end

   # Replace the previous file with the new.
   cp(tf.path, filename)
end

But can't we open a file in read-write mode and directly do
substitutions in it without using an auxiliary file?

You can do this, but only if you don't want to insert something inside
of the file, you can only overwrite parts of the file. So here is an
example:

File.open('test', 'w') { | f | f.print('This is a test') }
puts File.read('test')

File.open('test', 'r+') { | f | f.print('That') }
puts File.read('test')

File.open('test', 'r+') { | f | f.seek(5); f.print('was') }
puts File.read('test')

Output:
This is a test
That is a test
That wasa test

regards,

Brian

···

On 4/19/05, Ghislain Mary <nospam@lunacymaze.org> wrote:

Ghislain

--
Brian Schröder
http://ruby.brian-schroeder.de/

"Ghislain Mary" <nospam@lunacymaze.org> schrieb im Newsbeitrag
news:4264D003.4080500@lunacymaze.org...

Robert Klemme a écrit :
> You can do that easier:
>
> to_file.puts( /^caption:/ =~ line ? caption : line )
>

In fact, I am doing several substitution, but I forgot to tell it.

>
> Depends on your file's sizes. If they are moderately sized you can

slurp

> the whole thing in, modify contents in mem with a single gsub and then
> write them in one go.
>

Thanks. Since the file can be quite large, I continue using something
like before but with a Tempfile now. That gives something like:

require 'tempfile'
require 'fileutils'
include FileUtils

def clean_file(filename)
   # Define the new caption
   caption = "caption: my caption"

   # Process the content of the file
   begin
     tf = Tempfile.new(filename)
     File.open(filename) do |from_file|
       from_file.each do |line|
         case
           when line =~ /^caption:/: line.sub!($&, caption)
           # Other substitutions...
         end
         tf.print line
       end
     end
   ensure
     tf.close unless tf.nil?
   end

   # Replace the previous file with the new.
   cp(tf.path, filename)
end

Another small remark: I'd refactor the actual replacement out of this
method into another method that receives two IO instances. That's more
modular and it makes testing easier (you can use StringIO).

But can't we open a file in read-write mode and directly do
substitutions in it without using an auxiliary file?

Yes, but this has the problems others have pointed out. This works only
good as long as substitutions have the same lenght as the original piece
they replace - a rather seldom scenario I figure.

Kind regards

    robert

Lyndon Samson wrote:

···

On 4/19/05, Ghislain Mary <nospam@lunacymaze.org> wrote:

But can't we open a file in read-write mode and directly do
substitutions in it without using an auxiliary file?

You can use IO.pos to seek within a file, however what will you do if
your substitute string is not the same length as the original string?
Files have no concept of insert/delete.

Although it would be an interesting exercise to create
a descendant of File that would behave that way. It would
be analogous to the way strings are reallocated in RAM.

Inefficient, certainly, but disk drives are faster than
they were in 1980. :wink:

Hal

You can use IO.pos to seek within a file, however what will you do if
your substitute string is not the same length as the original string?
Files have no concept of insert/delete.

Although it would be an interesting exercise to create
a descendant of File that would behave that way. It would
be analogous to the way strings are reallocated in RAM.

Inefficient, certainly, but disk drives are faster than
they were in 1980. :wink:

That's it. I know that files don't have concept of insert or delete, but I was telling myself that maybe Ruby provided a way to abstract this lack and to provide a tranparent mean to modify the content of a file. But I can do without it :wink:

Thanks all.

Ghislain