3

I want to replace a line in a file with a new line, example being:

File:

test
test
test
testing
test

Source:

def remove_line(line)
  if line == line
    #remove line including whitespace
    File.open('test.txt', 'a+') { |s| s.puts('removed successfully') }
  end
end

So the expected output of this would be something like this:

remove_line('testing')
test
test
test
removed successfully
test

Now I've done some research and have only been able to find adding a blank line, I guess I could run through it and remove all blank lines and just append to the file, but there has to be an easier way to replace a line with another string?

JasonBorne
  • 87
  • 2
  • 9
  • When you say `if line == line` what are you trying to do? This is always going to be true. – Nobita Apr 16 '16 at 20:50
  • @Nobita I think he's referring to it as example, if the string given as an argument is equal to one of the lines in the file, rewrite the line, etc.. – 13aal Apr 16 '16 at 21:20

4 Answers4

5

First, open the file and save the actual content. Then, replace the string and write the full content back to file.

def remove_line(string)
  # save the content of the file
  file = File.read('test.txt')
  # replace (globally) the search string with the new string
  new_content = file.gsub(string, 'removed succesfully')
  # open the file again and write the new content to it
  File.open('test.txt', 'w') { |line| line.puts new_content }
end

Or, instead of replacing globally:

def remove_line(string)
  file = File.read('test.txt')
  new_content = file.split("\n")
  new_content = new_content.map { |word| word == string ? 'removed succesfully' : word }.join("\n")
  File.open('test.txt', 'w') { |line| line.puts new_content }
end
user3097405
  • 813
  • 1
  • 8
  • 16
  • This overwrites the entire file – JasonBorne Apr 17 '16 at 02:30
  • It writes to the file exactly what you need. You could try using `File.seek`, that would not rewrite the entire file, but you would need to replace the string with another string with exactly same size. – user3097405 Apr 17 '16 at 06:11
  • edited second method because I had left the statement to replace string globally. – user3097405 Apr 17 '16 at 09:58
  • @user3097405 Wouldn't you want to append to the file, not write to the file? – 13aal Apr 17 '16 at 15:40
  • @13aal, I'm not sure if I understand your question. It is only possible to **read** and **write** (append?) to a file, right? – user3097405 Apr 17 '16 at 18:07
  • @user3097405 Append = `a+` Write = `w`. From what I understand, writing to a file rewrites the entire file. – JasonBorne Apr 17 '16 at 18:11
  • [here](http://stackoverflow.com/questions/1581674/difference-between-the-access-modes-of-the-file-object-ie-w-r). `a+` starts writing to the end of the file. It is not what you wanted, right? – user3097405 Apr 17 '16 at 18:20
1

I found a reference answer here it's not exactly the same because they're searching for a pattern.

So here's how I would go about doing this:

def remove_line(remove)
  file.open('test.txt', 'a+').each_line do |line|
    line.gsub(remove, '<new/line/here>')
  end
end

This should replace that line given as an argument with whatever new line you want it replaced to..

kraftydevil
  • 5,144
  • 6
  • 43
  • 65
13aal
  • 1,634
  • 1
  • 21
  • 47
1

Given this input file, stored as "test.txt":

test
test
test
testing
test

and this sample code:

def remove_line(line_to_remove)
  File.foreach("test.txt").with_index do |line, line_num|
    line.gsub!(/[\r\n]+$/, '')
    if line == line_to_remove
      puts "removed successfully"
    else
      puts line
    end
  end
end

You can successfully run:

remove_line('testing')

and get this output:

test
test
test
removed successfully
test

The function takes the content of the line to remove, and opens the file in "line-by-line" fashion. This is idiomatic Ruby, and should be preferred over "slurping" files, as explained in the accepted answer to this SO question.

Once we have a line, it's necessary to strip the line endings from it. Since we don't know exactly which platform this will be run on (or the text file was created on), we use a regular expression to find all known line ending characters ('\r\n' is Windows, '\n' is Linux/Unix/Mac OS X, and '\r' is Mac Classic).

Then we check to see if the line matches what we want to remove. If it does match, we omit it from the output and instead print that it's been "removed successfully"; otherwise, we output the non-matching line.

This matches the original design intent, however, there are plenty of things to do to improve the design and make an overall more useful function. So why not go ahead and do that?

First, we'll make the function take a filename as an argument. This will remove the hardcoded "test.txt" filename from the foreach call. That will make this change happen:

def remove_line(filename, line_to_remove)
  File.foreach(filename).with_index do |line, line_num|
    line.gsub!(/[\r\n]+$/, '')
    if line == line_to_remove
      puts "removed successfully"
    else
      puts line
    end
  end
end

You can successfully run it this way, and it will produce the exact same output:

remove_line("test.txt", "testing")

Next, let's change how the output occurs, and we'll use a block to do this, because that's The Ruby Way. Here's what the function looks like with a block to output:

def remove_line(filename, line_to_remove, &block)
  proc = block_given? ? block : lambda {|s| puts s }
  File.foreach(filename).with_index do |line, line_num|
    line.gsub!(/[\r\n]+$/, '')
    if line == line_to_remove
      proc.call "removed successfully"
    else
      proc.call line
    end
  end
end

This is built with an optional block argument, so you can call it exactly like the previous version to make it work exactly the same way, or you can call it with an explicit block and do something cool. This example of running it modifies the string slightly before calling puts to print the line:

remove_line("test.txt", "testing") {|s| puts "#{s} in your pants" }

And with that extra pinch of childishness, you get this output:

test in your pants
test in your pants
test in your pants
removed successfully in your pants
test in your pants

You now have the power to do interesting things, built up from other interesting things. Use it wisely and keep doing it The Ruby Way.

Community
  • 1
  • 1
Michael Gaskill
  • 7,913
  • 10
  • 38
  • 43
0

I've wired solution but here is:

create a function that have file path argument and block with line where we modify each line as we need

def override_file_line(file_path)
  file_lines = File.read(file_path).split("\n")

  updated_lines = []
  file_lines.map do |line|
    updated_lines << yield(line)
  end

  File.open(file_path, 'w') { |line| line.puts updated_lines.join("\n") }
end

Then I call this function and modify line as I need inside block

# here is I add 2 in end of each line
override_file_line(@source_files[7]) do |line|
  "#{line}2"
end
r3cha
  • 111
  • 1
  • 6