Ruby: Insert a string after/before a pattern - ruby

I know how to add string(or text) to end of the file as well as beginning of the file, but could not google out the way to insert string (with a variable) after/before a pattern.
For example,
root#vikas:~# cat /etc/resolv.conf
search reachvikas.com
nameserver 192.168.1.27
root#vikas:~#
Now, I want to add another nameserver like below.
root#vikas:~# cat /etc/resolv.conf
search reachvikas.com
nameserver 192.168.181.2
nameserver 192.168.1.27
root#vikas:~#
I can do this with sed easily, but just looking a way out with Ruby.
Update: I have written below code, but this replaces the last line does not adds one. I guess tweaking file.seek would help me, but not sure how.
File.open('/etc/resolv.conf', 'r+') do |file|
count = Integer(0)
file.each do |line|
#puts count
if count == 1
#puts count.to_i
file.seek(-1, IO::SEEK_CUR)
file.puts("\nnameserver 192.168.181.2")
end
count += 1
end
end

Here's a Ruby "one-liner" that does what I think you're trying to do. I created a resolv.conf file matching your first file contents. Then the following Ruby "one-liner", which I broke into several lines for readability, searches for a line that begins with "nameserver" and inserts an arbitrary list of new namservers with IPs you define.
$ cat resolv.conf
search reachvikas.com
nameserver 192.168.1.27
$ ruby -wnl -i.$SECONDS -e '
BEGIN { server_ips = %w(
ip1
ip2
ip3
) }
if $_.start_with?("nameserver")
server_ips.each{ |ip| puts "nameserver #{ip}"; }
end
puts $_
' resolv.conf
$ ls resolv.conf*
resolv.conf resolv.conf.27729
$ cat resolv.conf
search reachvikas.com
nameserver ip1
nameserver ip2
nameserver ip3
nameserver 192.168.1.27
$ cat resolv.conf.27729
search reachvikas.com
nameserver 192.168.1.27
If you truly want it as a one-liner, you have to add semicolons where line breaks are needed:
ruby -wnl -i.$SECONDS -e 'BEGIN { server_ips = %w(ip1 ip2 ip3); }; if $_.start_with?("nameserver") ; server_ips.each{|ip| puts "nameserver #{ip}";}; end; puts $_;' resolv.conf
The -i.$SECONDS flag tells the Ruby interpreter to modify your input file in-place and to save the original version with a filename extension of $SECONDS, which is the number of seconds your terminal session has been alive. That makes it very unlikely you will permanently clobber a good file with bad code. The backup copies are there if you need them. You just have to clean up afterwards.
EDIT: Here's a short script that inserts rows into an existing file. Note that this does not save multiple copies of the input file like the one-liner does. This script reads an input file (resolv.conf), saves modified output to a temp file, then renames that temp file, replacing the original file. You would run this in the terminal like this $ ./script.rb resolv.conf
Script:
#! /usr/bin/env ruby
require 'tempfile'
require 'fileutils'
server_ips = %w(
ip1
ip2
ip3
)
input_file = ARGV[0]
temp_file = Tempfile.new("#{input_file}.temp")
modified = false
begin
File.open(input_file, 'r') do |file|
file.each_line do |line|
if modified == false && line.start_with?('nameserver')
server_ips.each do |ip|
temp_file.puts "nameserver #{ip}"
end
modified = true
end
temp_file.print line
end
end
temp_file.close
FileUtils.mv(temp_file.path, input_file)
ensure
temp_file.close!
end
See the Ruby documentation for the Tempfile class for an explanation of the begin... ensure... end usage and the explicit close on the Tempfile object.

Many thanks Jamin. I have slightly modified your code to suit my future needs as well, like if someone wants to add a block of lines with spaces before a keyword/pattern. This is what I have come up with. May be this helps someone.
txt_to_insert = %q(nameserver 192.168.181.2
nameserver 8.8.8.8)
input_file = "/etc/resolv.conf"
temp_file = Tempfile.new("tmp_file.temp")
modified = false
begin
File.open(input_file, 'r') do |file|
file.each_line do |line|
if modified == false && line.start_with?('nameserver')
temp_file.puts txt_to_insert
modified = true
end
temp_file.print line
end
end
temp_file.close
FileUtils.mv(temp_file.path, input_file)
ensure
temp_file.close!
end

Related

How can I get a Ruby variable from a line read from a file?

I need to reuse a textfile that is filled with one-liners such:
export NODE_CODE="mio12"
How can I do that in my Ruby program the var is created and assign as it is in the text file?
If the file were a Ruby file, you could require it and be able to access the variables after that:
# variables.rb
VAR1 = "variable 1"
VAR2 = 2
# ruby.rb
require "variables"
puts VAR1
If you're not so lucky, you could read the file and then loop through the lines, looking for lines that match your criteria (Rubular is great here) and making use of Ruby's instance_variable_set method. The gsub is to deal with extra quotes when the matcher grabs a variable set as a string.
# variables.txt
export VAR1="variable 1"
export VAR2=2
# ruby.rb
variable_line = Regexp.new('export\s(\w*)=(.*)')
File.readlines("variables.txt").each do |line|
if match = variable_line.match(line)
instance_variable_set("##{match[1].downcase}", match[2].gsub("\"", ""))
end
end
puts #var1
puts #var2
Creating a hash from this file can be a fairly simple thing.
For var.txt:
export BLAH=42
export WOOBLE=67
File.readlines("var.txt").each_with_object({}) { |line, h|
h[$1] = $2 if line =~ /^ export \s+ (.+?) \s* \= \s* (.+) $/x
}
# => {"BLAH"=>"42", "WOOBLE"=>"67"}

How to save chunk of information between two words to a file?

I have a following file:
old_file
new_file
Some string.
end
Text in the middle that is not supposed to go to any of files.
new_file
Another text.
end
How using regex can I create two files with the following content:
file1
new_file
Some string.
end
file2
new_file
Another text.
end
How can I get information which is between keywords 'new_file' and 'end' to write it to the file?
If your files are not that large, you can read them in as a string, (use File.read(file_name)), and then run the following regex:
file_contents.scan(/^new_file$.*?^end$/m).select { |block| WRITE_TO_FILE_CODE_HERE }
See the regex demo
The ^new_file$.*?^end$ regex matches new_file that is a whole line content, then 0+ any characters as few as possible (incl. a newline as /m modifier is used), and then end (a whole line).
Else, you may adapt this answer here as
printing = false
File.open(my_file).each_line do |line|
printing = true if line =~ /^new_file$/
puts line if printing
printing = false if line =~ /^end$/
end
Open the file when the starting line is found, write to it where puts line is in the example above, and close when printing false occurs.
You can also read the file chunk by chunk by changing what constitutes a "line" in ruby:
File.open("file1.txt", "w") do |file1|
File.open("file2.txt", "w") do |file2|
enum = IO.foreach("old_file.txt", sep="\n\n")
file1.puts enum.next.strip
enum.next #discard
file2.puts enum.next.strip
end #automatically closes file2
end #automatically closes file1
By designating the separator as "\n\n" ruby will read all the characters up to and including two consecutive newlines--and return that as a "line".
If that kind of format is fixed, then you may try this (new_file\n.*\nend)

How do I add a line after another line in a file, in Ruby?

Updated description to be clearer.
Say I have a file and it has these lines in it.
one
two
three
five
How do I add a line that says "four" after the line that says "three" so my file now looks like this?
one
two
three
four
five
Assuming you want to do this with the FileEdit class.
Chef::Util::FileEdit.new('/path/to/file').insert_line_after_match(/three/, 'four')
Here is the example ruby block for inserting 2 new line after match:
ruby_block "insert_lines" do
block do
file = Chef::Util::FileEdit.new("/path/of/file")
file.insert_line_after_match("three", "four")
file.insert_line_after_match("four", "five")
file.write_file
end
end
insert_line_after_match searches for the regex/string and it will insert the value in after the match.
The following Ruby script should do what you want quite nicely:
# insert_line.rb
# run with command "ruby insert_line.rb myinputfile.txt", where you
# replace "myinputfile.txt" with the actual name of your input file
$-i = ".orig"
ARGF.each do |line|
puts line
puts "four" if line =~ /^three$/
end
The $-i = ".orig" line makes the script appear to edit the named input file in-place and make a backup copy with ".orig" appended to the name. In reality it reads from the specified file and writes output to a temp file, and on success renames both the original input file (to have the specified suffix) and the temp file (to have the original name).
This particular implementation writes "four" after finding the "three" line, but it would be trivial to alter the pattern being matched, make it count-based, or have it write before some identified line rather than after.
This is an in memory solution. It looks for complete lines rather than doing a string regex search...
def add_after_line_in_memory path, findline, newline
lines = File.readlines(path)
if i = lines.index(findline.to_s+$/)
lines.insert(i+1, newline.to_s+$/)
File.open(path, 'wb') { |file| file.write(lines.join) }
end
end
add_after_line_in_memory 'onetwothreefive.txt', 'three', 'four'
An AWK Solution
While you could do this in Ruby, it's actually trivial to do this in AWK. For example:
# Use the line number to choose the insertion point.
$ awk 'NR == 4 {print "four"}; {print}' lines
one
two
three
four
five
# Use a regex to prepend your string to the matched line.
$ awk '/five/ {print "four"}; {print}' lines
one
two
three
four
five

grep the input file with keyword, then generate new report

cat infile
abc 123 678
sda 234 345 321
xyz 234 456 678
I need grep the file for keyword sda and report with first and last column.
sda has the value of 321
If you know bash script, I need a function in ruby as in below bash(awk) script:
awk '/sda/{print $1 " has the value of " $NF}' infile
How about something like this?
File.open("infile", "r").each_line do |line|
next unless line =~ /^sda/ # don't process the line unless it starts with "sda"
entries = line.split(" ")
var1 = entries.first
var2 = entries.last
puts "#{var1} has the value of #{var2}"
end
I don't know where you are defining the "sda" matcher. If it's fixed, you can just put it in there.
If not, you might try grabbing it from commandline arguments.
key, *_, value = line.split
next unless key == 'sda' # or "next if key != 'sda'"
puts your_string
Alternatively, you could use a regexp matcher in the beginning to see if the line starts with 'sda' or not.

How do I join two lines of a file by matching pattern, in Ruby or Bash?

I'm using a Ruby script to do a lot of manipulation and cleaning to get this, and a bunch of other files, ready for import.
I have a really large file with some data that I'm trying to import into a database. There are some data issues with newline characters being in the data where they should not be, messing with the import.
I was able to solve this problem with sed using this:
sed -i '.original' -e ':a' -e 'N' -e '$!ba' -e 's/Oversight Bd\n/Oversight Bd/g' -e 's/Sciences\n/Sciences/g' combined_old_individual.txt"
However, I can't call that command from inside a Ruby script, because Ruby messes up interpreting the newline characters and won't run that command. sed needs the non-escaped newline character but when calling a system command from Ruby it needs a string, where the newline character needs to be escaped.
I also tried doing this using Ruby's file method, but it's not working either:
File.open("combined_old_individual.txt", "r") do |f|
File.open("combined_old_individual_new.txt","w") do |new_file|
to_combine = nil
f.each_line do |line|
if(/Oversight Bd$/ =~ line || /Sciences$/ =~ line)
to_combine = line
else
if to_combine.nil?
new_file.puts line
else
combined_line = to_combine + line
new_file.puts combined_line
to_combine = nil
end
end
end
end
end
Any ideas how I can join lines where the first line ends with "Bd" or "Sciences", from within a Ruby script, would be very helpful.
Here's an example of what might go in a testfile.txt:
random line
Oversight Bd
should be on the same line as the above, but isn't
last line
and the result should be
random line
Oversight Bdshould be on the same line as the above, but isn't
last line
With ruby (My first attempt at a ruby answer):
File.open("combined_old_individual.txt", "r") do |f|
File.open("combined_old_individual_new.txt","w") do |new_file|
f.each_line do |line|
if(/(Oversight Bd|Sciences)$/ =~ line)
new_file.print line.strip
else
new_file.puts line
end
end
end
end
You have to realize that sed normally works line by line, so you cannot match for \n in your initial pattern. You can however match for the pattern on the first line and then pull in the next line with the N command and then run the substitute command on the buffer to remove the newline like so:
sed -i -e '/Oversight Bd/ {;N;s/\n//;}' /your/file
Run from Ruby (without -i so that the output goes to stdout):
> cat test_text
aaa
bbb
ccc
aaa
bbb
ccc
> cat test.rb
cmd="sed -e '/aaa/ {;N;s/\\n//;}' test_text"
system(cmd)
> ruby test.rb
aaabbb
ccc
aaabbb
ccc
Since you are asking in bash, here is a pure-bash solution:
$ r="(Oversight Bd|Sciences)$"
$ while read -r; do printf "%s" "$REPLY"; [[ $REPLY =~ $r ]] || echo; done < combined_old_individual.txt
random line
Oversight Bdshould be on the same line as the above, but isn't
last line
$

Resources