Variable expansion in exec from the ruby standard library - ruby

What's the correct quoting syntax to allow for variable expansion in the following script?
ARGF.each do |l|
exec cp #{l} #{l}.bk
end
Thanks

In Ruby, in order to evaluate a variable, you simply reference it by name. So, for example, to evaluate a local variable named foo, you simply write
foo
Ruby doesn't have a concept of "variable expansion" like a POSIX shell does. In a POSIX shell, everything is a string, and so you expand variables into strings. Ruby has a much richer and much stronger (dynamic) type system, and a much richer syntax.
So, in order to evaluate the local variable (or more precisely, the block argument binding) l, you would simply write
l
Therefore, your code would look something like this:
ARGF.each do |l|
exec cp l l.bk
end
Which is parsed like this:
ARGF.each do |l|
exec(cp(l(l.bk())))
end
Note: in this case, the two references to l actually reference two different things: the first l is a message send, the second l is a local variable reference.

Use quotes with string interpolation. the problem is that when you run what is verbatim in the ruby docs, ARGF.each do |line|, it does just as says: it loops over lines of the file in the context, but the objective of this script is to copy the filenames-- not access the contents of the files. Therefore when I reference #{l} I am not referencing the filename, but a line within the file.
If you just use ARGF as it is then (i think) it will try to actually read the contents of those files. To reference the names of the files for doing operations like the one above (copy) there are two ways:
ARGF.argv returns an array of the filenames.
ARGF.filename returns the filename inside the context of a loop.
Since I'm doing a looping structure I can access the current filename of the loop context with ARGF.filename method.
The correct code looks like this:
ARGF.each do |l|
exec ("cp #{ARGF.filename} #{ARGF.filename}.bk"
end

Related

How to make my script use a CSV file that was given in the terminal as a parameter

I tried to google this, but cant really find "good words" to get to my solution. So maybe someone here can help me out.
I have a script (lets call it script.rb) that uses File.read to read a csv file called somefile.csv and i have another csv file called somefileV2.csv.
Script.rb
csv_text = File.read('/home/XXX/XXX/XXX/somefile.csv')
Right now it uses somefile.csv as default, but I would like to know, if it is posseble to make my script use a CSV file that was given in the terminal as a parameter like:
Terminal
home$ script.rb somefileV2
so instead of it reading the file that is in the script, it reads the other csv file (somefileV2.csv) that is in the directory. It is kinda annoying to change the file manually everytime in the script itself.
You can access the parameters (arguments) using the ARGV array.
So your program could be like:
default = "/home/XXX/XXX/XXX/somefile.csv"
csv_text = File.read(ARGV[0] || default)
which gives you the possibility to supply a filename or, if not supplied, use the default value.
ARGV[0] refers to the first, ARGV[1] to the second argument and so on.
ruby myscript.rb foo bar baz would result in ARGV being
´["foo", "bar", "baz"]´. Note that the elements will always be strings. So if you want anything else (Numbers, Date, ...) you need to process it accordingly in your program.

Local variable in Ruby passed to Linux Terminal

This probably isn't as hard as I'm making it, but how does one pass a local variable cleanly to the command line as part of a directory?
I'm concatenating similar text files (based off of a string) and storing them in a new text file that contains that same string.
Assume I have a directory named class which contains a text file named after each student. Formatted as LastName_FM
I also have a list of students' lastnames named students
For example:
for lastname in students
'cat class/*'+lastname+'*.txt > /class/'+lastname+'_list.txt'
end
Theoretically, if I have files named:
Smith_JM.txt
Smith_TR.txt
Smith_WE.txt
2Smith_WE.txt (Different students with the same name.)
I would end up with a file named Smith_list.txt that would contain the contents of the four files.
Thanks for any pointers, I'm brand new to Ruby.
You can enclose the system command in backticks and interpolate the variable into the command because the backtick method supports interpolation.
Here's a contrived example that launches gedit:
local_var = "gedit"
`#{local_var}` # interpolation syntax is #{...}
EDIT: Here's another example that demonstrates interpolation within a block and writes the contents of foo.txt to bar.txt. You could do something similar with a for loop, but that's not a common ruby idiom.
files = ['foo.txt']
files.each do |f|
puts "#{f}" # block variable interpolated within double quotes
`cat #{f} > bar.txt` # block variable interpolated within backticks
end
you can in the command do something along the lines of:
cd /mydir/subdir/subsubdir && cat class/*zoo*.txt > class/zoo_list.txt
Use back ticks as indicated in comments to run this.
or
you can do this in Ruby only using a combination of Dir.glob File.open and reading/writing.

Bash command line parsing containing whitespace

I have a parse a command line argument in shell script as follows:
cmd --a=hello world good bye --b=this is bash script
I need the parse the arguments of "a" i.e "hello world ..." which are seperated by whitespace into an array.
i.e a_input() array should contain "hello", "world", "good" and "bye".
Similarly for "b" arguments as well.
I tried it as follows:
--a=*)
a_input={1:4}
a_input=$#
for var in $a_input
#keep parsing until next --b or other argument is seen
done
But the above method is crude. Any other work around. I cannot use getopts.
The simplest solution is to get your users to quote the arguments correctly in the first place.
Barring that you can manually loop until you get to the end of the arguments or hit the next --argument (but that means you can't include a word that starts with -- in your argument value... unless you also do valid-option testing on those in which you limit slightly fewer -- words).
Adding to Etan Reisners answer, which is absolutely correct:
I personally find bash a bit cumbersome, when array/string processing gets more complex, and if you really have the strange requirement, that the caller should not be required to use quotes, I would here write an intermediate script in, say, Ruby or Perl, which just collects the parameters in a proper way, wraps quoting around them, and passes them on to the script, which originally was supposed to be called - even if this costs an additional process.
For example, a Ruby One-Liner such as
system("your_bash_script here.sh '".(ARGV.join(' ').split(' --').select {|s| s.size>0 }.join("' '"))."'")
would do this sanitizing and then invoke your script.

Convert array to arguments for shell command

I'm trying to do something like:
list = Dir["*.mp4"]
`zip "test.zip" "#{list}"`
But #{list} is coming out as an array, how do I fix that?
You should use Shellwords from the standard library, which is designed to do exactly this—and does proper escaping no matter how weird your filenames are:
require 'shellwords'
list = Dir["*.mp4"]
puts [ "zip", "test.zip", *list ].shelljoin
# => zip test.zip foo.mp4 filename\ with\ spaces.mp4 etc.mp4
Doesn't look like you're storing the result anywhere so you should use the multi-argument form of system and bypass the shell entirely:
system('zip', 'test.zip', *list)
Since no shell is invoked, you don't have to worry about quoting or parsing or any of that nonsense, just build a list of strings and splat it.
If you do need to capture the output, then use one of the Open3 methods. Backticks are almost always the wrong approach, there are too many sharp edges (just browse the CERT reports for Ruby and you'll see how often backticks and the single argument form of system cause problems).
http://www.ruby-doc.org/core-2.1.2/Array.html#method-i-join
You are looking for the join method
["a","b","c"].join(" ") => "a b c"
["a","b","c"].join("-|-") => "a-|-b-|-c"

Parsing command-line arguments as wildcards

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/*'

Resources