I'm currently grep-ing the system and returning the results into ruby to manipulate.
def grep_system(search_str, dir, filename)
cmd_str ="grep -R '#{search_str}' #{dir} > #{filename}"
system(cmd_str)
lines_array = File.open(filename, "r").read.split("\n)
end
As you can see, I'm just writing the results from the grep into a temp file, and then re-opening that file with "File.open".
Is there a better way to do this?
Never ever do anything like this:
cmd_str ="grep -R '#{search_str}' #{dir}"
Don't even think about it. Sooner or later search_str or dir will contain something that the shell will interpret in unexpected ways. There's no need to invoke a shell at all, you can use Open3.capture3 thusly:
lines = Open3.capture3('grep', '-R', search_str, dir).first
lines, _ = Open3.capture3('grep', '-R', search_str, dir)
That will leave you with a newline delimited list in lines and from there it should be easy.
That will invoke grep directly without using a shell at all. capture3 also nicely lets you ignore (or capture) the command's stderr rather than leaving it be printed wherever your stderr goes by default.
If you use this form of capture3, you don't have to worry about shell metacharacters or quoting or unsanitary inputs.
Similarly for system, if you want to use system with arguments you'd use the multi-argument version:
system('ls', some_var)
instead of the potentially dangerous:
system("ls #{some_var}")
You shouldn't need to pass an argument for the temporal filename. After all, writing and reading to/from a temporal file is something you should avoid if possible.
require "open3"
def grep_system(search_str, dir)
Open3.capture2("grep -R '#{search_str}' #{dir}").first.each_line.to_a
end
Instead of using system(cmd_str), you could use:
results = `#{cmd_str}`
Yes, there are a few better ways. The easiest is just to assign the result of invoking the command with backticks to a variable:
def grep_system(search_str, dir, filename)
cmd_str ="grep -R '#{search_str}' #{dir}"
results = `#{cmd_str}`
lines_array =results.split("\n)
end
Related
I want to write a command in the terminal like config.section.key, parse the command, and get the strings "section" and "key". I want to use these two keys in my function to search a hash.
Is there any way to parse a command from the terminal to do this?
To execute terminal commands you can use either backticks or a system call here's some examples keep in mind that this is all pseudo code and I have no idea if this will run correctly:
def create_file
`touch test.txt`
end
def cmd
system('ls')
end
def check_file
results = cmd
if results.include?('test.txt')
puts 'File exists.'
else
puts 'Creating file..'
create_file
end
end
Now to the parsing part, depending on what you want to do, you can either save the information into a variable, or you could use a regex to extract the information. So if you wanted to extract digits with a regex: /\d+/ if you wanted to save the information: results = cmd..
I hope this answers your question.
To split the information, you could use the split method for example:
def cmd
`prt_jobs`
end
def check_jobs
res = cmd
res.split(".")
end
This will split the results of a print jobs command by periods and make them into an array. I'd show you more except I'm on my phone so it will have to wait
As Tadman commented, you can use the String#split method to split the argv on period characters, if that is your desire:
config, section, key, *rest = ARGF.argv.split('.')
Another good option when dealing with parsing command lines is the Ruby standard library OptionParser class. Rather than rebuild all of the CLI parsing by hand, the OptionParser class has that built in and much more. The resulting scripts can feel much more linux like and be familiar to anyone who's used bash before.
Want to run Ruby like this
cat *.txt |my_print_last_line.rb
And have the last line of each file printed (might do something interesting if I could first print)
The ARGF class seems promising to solve it.
Tried this:
ARGF.each do |line|
last_line_of_file = line[last]
puts last_line_of_file
end
However ARGF.each seems to be one long string of all files concatenated rather than the individual files.
This has to do with how cat handles multiple files. It just concatenates them:
$ echo 'foo' > foo
$ echo 'bar' > bar
$ cat *
foo
bar
This means that after issuing the cat command there's no way of telling where the individual files started and ended. If you would just invoke your script as ruby my_print_last_line.rb *.txt you would still have the same problem because using ARGF will also simply concatenate all files.
As an alternative, you could take the filenames as parameters to the ruby script to open and read the files directly in ruby:
ARGV.each do |file|
puts IO.readlines(file).last
end
Then invoke your script like this:
$ ruby my_print_last_line.rb *.txt
If you are reading really large files, you may gain a significant performance benefit by using a seek based approach, as described by #DonaldScottWilde in this answer to a similar question.
Ok this is driving me crazy:
`ls #{"/media/music/Miles Davis"}`
fails because of the space between "Miles" and "Davis"
Say I write a ruby script and a user passes file path as an argument. How do I escape it and feed to a shell-out command. Yes, yes, I know, shelling out should be avoided. But this is a contrived example, I still need this.
I would do system("ls", ARGV[0]), but it doesn't return the stdout output of ls as a string, which is what backticks do well.
How do escape whatever you insert in a shellout?
Use require 'shellwords' and Shellwords.escape, which will fix this sort of stuff for you:
http://apidock.com/ruby/Shellwords/shellescape
Stay away from building shell strings
This is a fine vector for arbitrary code execution.
In this case, you could use popen, which does the escaping for you, e.g.:
#!/usr/bin/env ruby
IO.popen(['printf', 'a b']) do |f|
puts f.read
end
This outputs:
a b
just as if we had run on the terminal:
/usr/bin/printf 'a b'
If a b hadn't been escaped, we wouldn't get a b as expected, because running an unquoted:
/usr/bin/printf a b
in the terminal gives:
a/usr/bin/printf: warning: ignoring excess arguments, starting with ‘b’
Tested in Ubuntu 20.02, Ruby 2.6.
Double quotes also works:
`ls "#{'/media/music/Miles Davis'}"`
or
`ls "#{ARGV[0]}"`
I'm working on implementing Project Euler solutions as semantic Ruby one-liners. It would be extremely useful if I could coerce Ruby to automatically puts the value of the last expression. Is there a way to do this? For example:
#!/usr/bin/env ruby -Ilib -rrubygems -reuler
1.upto(100).into {|n| (n.sum.squared - n.map(&:squared).sum)
I realize I can simply puts the line, but for other reasons (I plan to eval the file in tests, to compare against the expected output) I would like to avoid an explicit puts. Also, it allots me an extra four characters for the solution. :)
Is there anything I can do?
You might try running it under irb instead of directly under a Ruby interpreter.
It seems like the options -f --noprompt --noverbose might be suitable (.
#!/usr/bin/env irb -f --noprompt --noverbose -Ilib -rrubygems -reuler
'put your one-liner here'
The options have these meanings:
-f: do not use .irbrc (or IRBRC)
--noverbose: do not display the source lines
--noprompt: do not prefix the output (e.g. with =>)
result = calculate_result
puts result if File.exist?(__FILE__)
result of eval is last executed operation just like any other code block in ruby
is doing
puts eval(file_contents)
an option for you?
EDIT
you can make use of eval's second parameter which is variables binding
try the following:
do_not_puts = true
eval(file_contents, binding)
and in the file:
....
result = final_result
if defined?(do_not_puts)
result
else
puts(result)
end
Is it an option to change the way you run scripts?
script.rb:
$_= 1.upto(100).into {|n| (n.sum.squared - n.map(&:squared).sum)
invoke with
echo nil.txt | /usr/bin/env/ruby -Ilib -rrubygems -reuler -p script.rb, where nil.txt is a file with a single newline.
I wrote a simple script that writes all given arguments to a single text file, separated by newline. I'd like to pass a list of files to it using OptionParser. I would like to add a couple of files using wildcards like /dir/*.
I tried this:
opts = OptionParser.new
opts.on('-a', '--add FILE') do |s|
puts "DEBUG: before #{s}"
#options.add = s
puts "DEBUG: after #{#options.add}"
end
...
def process_arguments
#lines_to_add = Dir.glob #options.add
end
Put when I add files like this:
./script.rb -a /path/*
I always get only the first file in the directory. All the debug outputs show only the first file of directory, and it seems as if OptionParser does some magic interpretations
Does anyone know how to handle this?
You didn't mention which operating system you are using (it matters).
On Windows, whatever you type on the command line gets passed to the program without modification. So if you type
./script.rb -a /path/*
then the arguments to the program contain "-a" and "/path/*".
On Unix and other systems with similar shells, the shell does argument expansion that automatically expands wildcards in the command line. So when you type the same command above, the shell looks to find the files in the /path/* directory and expands the command line arguments before your program runs. So the arguments to your program might be "-a", "/path/file1", and "/path/file2".
An important point is that the script cannot find out whether argument expansion happened, or whether the user actually typed all those filenames out on the command line.
As mentioned above, the command-line is being parsed before the OS hands off the command to Ruby. The wildcard is being expanded into a list of space-delimited filenames.
You can see what will happen if you type something like echo * at the command-line, then, instead of hitting Return, instead hit Esc then *. You should see the * expanded into the list of matching files.
After hitting Return those names will be added to the ARGV array. OptionParser will walk through ARGV and find the flags you defined, grab the following elements if necessary, then remove them from ARGV. When OptionParser is finished any ARGV elements that didn't fit into the options will remain in the ARGV array where you can get at them.
In your code, you are looking for a single parameter for the '-a' or '--add FILE' option. OptionParser has an Array option which will grab comma-separated elements from the command line but will subsequent space-delimited ones.
require 'optparse'
options = []
opts = OptionParser.new
opts.on('-a', '--add FILE', Array) do |s|
options << s
end.parse!
print "options => ", options.join(', '), "\n"
print "ARGV => ", ARGV.join(', '), "\n"
Save that to a file and try your command line with -a one two three, then with -a one,two,three. You'll see how the Array option grabs the elements differently depending on whether there are commas or spaces between the parameters.
Because the * wildcard gets replaced with space delimited filenames you'll have to post-process ARGV after OptionParser has run against it, or programmatically glob the directory and build the list that way. ARGV has all the files except the one picked up in the -a option so, personally, I'd drop the -a option and let ARGV contain all the files.
You will have to glob the directory if * has to too many files and exceeds the buffer size. You'll know if that happens because the OS will complain.
The shell is expanding the argument before it gets passed to your program. Either keep consuming filenames until you reach another option, or have the user escape the wildcards (e.g. ./script.rb -a '/path/*') and glob them yourself.
What's happening is the shell is expanding the wildcard before Ruby gets to it. So really you are processing:
./script.rb -a /path/file1 /path/file2 ......
Put quotes around /path/* to avoid the shell expansion and pass the wildcard to Ruby:
./script.rb -a '/path/*'