How do I execute Date from a string? - ruby

I am trying to read a file which has dynamic dates in it such as Date.today or (Date.today - 1 ), and perform my code based on the date requested.
If I have the string defined with the date in quotes it works. When reading the same string from a file it does not. Is there any eval function that I need to use to make it work?
require 'date'
#Works
abc = "something #{Date.today}"
puts abc
# something 2013-04-19
#does not work
f = File.read("test.txt")
f.each_line { |line| puts line ; words = line.split("\t")
puts line
}
Contents of the test.txt file:
something #{Date.today}
# something #{Date.today}

You're going to have to use eval to actually evaluate the contents of each line as its read.
But since you will be evaluating arbitrary code you will have to trust that its not malicious code.
So, assuming you trust the file and its lines:
require 'date'
f = File.read("test.txt")
f.each_line do |line|
puts eval("\"#{line}\"")
end
Notice the double-quote wrapping, as the "piece of code" you will be evaluating needs to be a "valid" Ruby string (which in turn contains code, so you wrap it in quotes to make it appear to be an actual double-quoted String.
This works if test.txt looks like:
Hello, #{Date.today}
Goodbye, #{Date.today}

Related

Check the formatting of an entire file using regex

I have a file formatted by lines like this (I know it's a terrible format, I didn't write it):
id: 12345 synset: word1,word2
I want to read the entire file and check to see if every line is correct without having to look line by line.
I've looked into File and Regex, but couldn't find what I need. I tried to use File.read to read the entire file all at once, then use m modifier for regex to check multiple lines, but it's not working the way I anticipated (perhaps it's not what I need).
p.s. Ruby newbie :)
Assuming your file always ends with a newline, this should work:
/^(id: \d+ synset: \w+,\w+\n)+$/m
The full ruby:
content = ''
File.open('myfile.txt', 'r') { |f| content = f.read }
puts 'file is valid!' if content =~ /^(id: \d+ synset: \w+,\w+\n)+$/m
You can use this regex to check each line of the file: ^id:\s*\d+\s+synset:\s*(?:\w+,)*\w+$. You can try the following code, but I don't know any Ruby, I just searched and tested a little. It might work.
line_num = 0
text = File.open('file.txt').read
text.each_line do |line|
line_num += 1
if !/^id:\s*\d+\s+synset:\s*(?:\w+,)*\w+$/.match(line)
print "Line #{line_num} is incorrect"
end
end

Reading specific line into an array - ruby

Have a txt file with the following:
Anders Hansen;87442355;11;87
Jens Hansen;22338843;23;11
Nanna Kvist;25233255;24;84
I would like to search the file after a specific name taken from the user input. Then save that line into an array, splittet via ";". Can't get it to work though. This is my code:
user1 = []
puts "Start by entering the full name of user 1: "
input = gets.chomp
File.open("userregister.txt") do |f|
f.each_line { |line|
if line =~ input then do |line|
user1 << line.split(';').map
=~ in ruby tries to match a string with a regex (or vice versa). Here, you use it with two strings, which gives an error:
'foo' =~ 'bar' # => TypeError: type mismatch: String given
There are more appropriate String methods to use instead. In your case, #start_with? does the job. If you wanted to check if the latter is contained somewhere as a substring (but not necessary the beginning), you can use #include?.
In case you actually wanted to take a regex as a user input (generally a bad idea), you can convert it from string to regex:
line =~ /#{input}/
Looking at the file format, I would actually use Ruby CSV class. By specifying the column separator to ;, you will get an array for each row.
require 'csv'
input = gets.chomp
CSV.foreach('userregister.txt', col_sep: ';') do |row|
if row[0].downcase == input.downcase
# Do stuffs with row[1..-1]
end
end

Ruby: sub/gsub at a particular line OR before/after a pattern

I know that I can replace text as below in a file
File.write(file, File.read(file).gsub(/text/, "text_to_replace"))
Can we also use sub/gsub to:-
Replace a string on a particular line number (useful when there is a same string at different locations in a file)
Example
root#vikas:~# cat file.txt
fix grammatical or spelling errors
clarify meaning without changing it
correct minor mistakes
add related resources or links
root#box27:~#
I want to insert some text at 3rd line
root#vikas:~# cat file.txt
fix grammatical or spelling errors
clarify meaning without changing it
Hello, how are you ?
correct minor mistakes
add related resources or links
root#box27:~
Replace a string on the line just before/after matching a pattern
Example
root#vikas:~# cat file.txt
fix grammatical or spelling errors
clarify meaning without changing it
correct minor mistakes
add related resources or links
root#box27:~#
I want to search 'minor mistakes' and put text 'Hello, how are you ?' before that.
root#vikas:~# cat file.txt
fix grammatical or spelling errors
clarify meaning without changing it
Hello, how are you ?
correct minor mistakes
add related resources or links
root#box27:~
Here is the answer.
File.open("file.txt", "r").each_line do |line|
if line =~ /minor mistakes/
puts "Hello, how are you ?"
end
puts "#{line}"
end
Here is ruby one-liner.
ruby -pe 'puts "Hello, how are you ?" if $_ =~ /minor mistakes/' < file.txt
You can find this functionality in a gem like Thor. Check out the documentation for the inject_into_file method here:
http://www.rubydoc.info/github/erikhuda/thor/master/Thor/Actions#inject_into_file-instance_method.
Here is the source code for the method:
https://github.com/erikhuda/thor/blob/067f6638f95bd000b0a92cfb45b668bca5b0efe3/lib/thor/actions/inject_into_file.rb#L24-L32
If you wish to match on line n (offset from zero):
def match_line_i(fname, linenbr regex)
IO.foreach(fname).with_index { |line,i|
return line[regex] if i==line_nbr }
end
or
return scan(regex) if i==line_nbr }
depending on your requirements.
If you wish to match on a given line, then return the previous line, for application of gsub (or whatever):
def return_previous_line(fname, regex)
last_line = nil
IO.foreach(fname) do |line|
line = f.readline
return last_line if line =~ regex
last_line = line
end
end
Both methods return nil if there is no match.
Okay, as there is no such option available with sub/gsub, I am pasting here my code (with slight modifications to BMW's code) for all three options. Hopefully, this helps someone in a similar situation.
Insert text before a pattern
Insert text after a pattern
Insert text at a specific line number
root#box27:~# cat file.txt
fix grammatical or spelling errors
clarify meaning without changing it
correct minor mistakes
add related resources or links
always respect the original author
root#box27:~#
root#box27:~# cat ruby_script
puts "#### Insert text before a pattern"
pattern = 'minor mistakes'
File.open("file.txt", "r").each_line do |line|
puts "Hello, how are you ?" if line =~ /#{pattern}/
puts "#{line}"
end
puts "\n\n#### Insert text after a pattern"
pattern = 'meaning without'
File.open("file.txt", "r").each_line do |line|
found = 'no'
if line =~ /#{pattern}/
puts "#{line}"
puts "Hello, how are you ?"
found = 'yes'
end
puts "#{line}" if found == 'no'
end
puts "\n\n#### Insert text at a particular line"
insert_at_line = 3
line_number = 1
File.open("file.txt", "r").each_line do |line|
puts "Hello, how are you ?" if line_number == insert_at_line
line_number += 1
puts "#{line}"
end
root#box27:~#

ruby - how to correctly parse varying numbers of command line arguments

n00b question alert!
here is the problem:
I am creating a shell script that takes a minimum of 3 arguments: a string, a line number, and at least one file.
I've written a script that will accept EXACTLY 3 arguments, but I don't know how to handle multiple file name arguments.
here's the relevant parts of my code (skipping the writing back into the file etc):
#!/usr/bin/env ruby
the_string = ARGV[0]
line_number = ARGV[1]
the_file = ARGV[2]
def insert_script(str, line_n, file)
f = file
s = str
ln = line_n.to_i
if (File.file? f)
read_in(f,ln,s)
else
puts "false"
end
end
def read_in(f,ln,s)
lines = File.readlines(f)
lines[ln] = s + "\n"
return lines
end
# run it
puts insert_script(the_string, line_number, the_file)
now I know that it's easy to write a block that will iterate through ALL the arguments:
ARGV.each do |a|
puts a
end
but I need to ONLY loop through the args from ARGV[2] (the first file name) to the last file name.
I know there's got to be - at a minimum - at least one easy way to do this, but I just can't see what it is at the moment!
in any case - I'd be more than happy if someone can just point me to a tutorial or an example, I'm sure there are plenty out there - but I can't seem to find them.
thanks
Would you consider using a helpful gem? Trollop is great for command line parsing because it automatically gives you help messages, long and short command-line switches, etc.
require 'trollop'
opts = Trollop::options do
opt :string, "The string", :type => :string
opt :line, "line number", :type => :int
opt :file, "file(s)", :type => :strings
end
p opts
When I call it "commandline.rb" and run it:
$ ruby commandline.rb --string "foo bar" --line 3 --file foo.txt bar.txt
{:string=>"foo bar", :line=>3, :file=>["foo.txt", "bar.txt"], :help=>false, :string_given=>true, :line_given=>true, :file_given=>true}
If you modify the ARGV array to remove the elements you're no longer interested in treating as filenames, you can treat all remaining elements as filenames and iterate over their contents with ARGF.
That's a mouthful, a small example will demonstrate it more easily:
argf.rb:
#!/usr/bin/ruby
str = ARGV.shift
line = ARGV.shift
ARGF.each do |f|
puts f
end
$ ./argf.rb one two argf.rb argf.rb
#!/usr/bin/ruby
str = ARGV.shift
line = ARGV.shift
ARGF.each do |f|
puts f
end
#!/usr/bin/ruby
str = ARGV.shift
line = ARGV.shift
ARGF.each do |f|
puts f
end
$
There are two copies of the argf.rb file printed to the console because I gave the filename argf.rb twice on the command line. It was opened and iterated over once for each mention.
If you want to operate on the files as files, rather than read their contents, you can simply modify the ARGV array and then use the remaining elements directly.
The canonical way is to use shift, like so:
the_string = ARGV.shift
line_number = ARGV.shift
ARGV.each do |file|
puts insert_script(the_string, line_number, the_file)
end
Take a look at OptionParser - http://ruby-doc.org/stdlib-1.9.3/libdoc/optparse/rdoc/OptionParser.html. It allows you to specify the number of arguments, whether they are mandatory or optional, handle errors such as MissingArgument or InvalidOption.
An alternate (and somewhat uglier) trick if you don't want to use another library or change the ARGV array is to use .upto
2.upto(ARGV.length-1) do |i|
puts ARGV[i]
end

Looking to clean up a small ruby script

I'm looking for a much more idiomatic way to do the following little ruby script.
File.open("channels.xml").each do |line|
if line.match('(mms:\/\/{1}[a-zA-Z\.\d\/\w-]+)')
puts line.match('(mms:\/\/{1}[a-zA-Z\.\d\/\w-]+)')
end
end
Thanks in advance for any suggestions.
The original:
File.open("channels.xml").each do |line|
if line.match('(mms:\/\/{1}[a-zA-Z\.\d\/\w-]+)')
puts line.match('(mms:\/\/{1}[a-zA-Z\.\d\/\w-]+)')
end
end
can be changed into this:
m = nil
open("channels.xml").each do |line|
puts m if m = line.match(%r|(mms://{1}[\w\./-]+)|)
end
File.open can be changed to just open.
if XYZ
puts XYZ
end
can be changed to puts x if x = XYZ as long as x has occurred at some place in the current scope before the if statement.
The Regexp '(mms:\/\/{1}[a-zA-Z\.\d\/\w-]+)' can be refactored a little bit. Using the %rXX notation, you can create regular expressions without the need for so many backslashes, where X is any matching character, such as ( and ) or in the example above, | |.
This character class [a-zA-Z\.\d\/\w-] (read: A to Z, case insensitive, the period character, 0 to 9, a forward slash, any word character, or a dash) is a little redundant. \w denotes "word characters", i.e. A-Za-z0-9 and underscore. Since you specify \w as a positive match, A-Za-z and \d are redundant.
Using those 2 cleanups, the Regexp can be changed into this: %r|(mms://{1}[\w\./-]+)|
If you'd like to avoid the weird m = nil scoping sorcery, this will also work, but is less idiomatic:
open("channels.xml").each do |line|
m = line.match(%r|(mms://{1}[\w\./-]+)|) and puts m
end
or the longer, but more readable version:
open("channels.xml").each do |line|
if m = line.match(%r|(mms://{1}[\w\./-]+)|)
puts m
end
end
One very easy to read approach is just to store the result of the match, then only print if there's a match:
File.open("channels.xml").each do |line|
m = line.match('(mms:\/\/{1}[a-zA-Z\.\d\/\w-]+)')
puts m if m
end
If you want to start getting clever (and have less-readable code), use $& which is the global variable that receives the match variable:
File.open("channels.xml").each do |line|
puts $& if line.match('(mms:\/\/{1}[a-zA-Z\.\d\/\w-]+)')
end
Personally, I would probably just use the POSIX grep command. But there is Enumerable#grep in Ruby, too:
puts File.readlines('channels.xml').grep(%r|mms://{1}[\w\./-]+|)
Alternatively, you could use some of Ruby's file and line processing magic that it inherited from Perl. If you pass the -p flag to the Ruby interpreter, it will assume that the script you pass in is wrapped with while gets; ...; end and at the end of each loop it will print the current line. You can then use the $_ special variable to access the current line and use the next keyword to skip iteration of the loop if you don't want the line printed:
ruby -pe 'next unless $_ =~ %r|mms://{1}[\w\./-]+|' channels.xml
Basically,
ruby -pe 'next unless $_ =~ /re/' file
is equivalent to
grep -E re file

Resources