Convert array to arguments for shell command - ruby

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"

Related

Variable expansion in exec from the ruby standard library

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

How to separate out command with its arguments coming in format of string using ruby

I have to pass a command with its arguments in a scheduled task, while separating the arguments from the command. I used:
split(/(?=\s-)/)
to do this, but it won't work when the argument is not passed as -arg format.
Example of commands can be passed in format:
"ping http://www.google.com" here url is argument
"abc-abc -V"
"abc-abc -L c:\\folder name\\test.log"
'"C:\\Program Files\\example\\program.exe" -arg1 -arg2'
"C:\\Program Files\\example\\program.exe"
To make this more clear these commands are not passed as command line argument which can get in ARGV
The command gets set in command property which accepts input in string format
command '"C:\\Program Files\\example\\program.exe" -arg1 -arg2'
Use Shellwords.split, from the standard library:
Shellwords.split("ping http:\\www.google.com here url is argument")
#=> ["ping", "http:www.google.com", "here", "url", "is", "argument"]
Shellwords.split("abc-abc -V")
#=> ["abc-abc", "-V"]
Shellwords.split("abc-abc -L c:\\folder name\\test.log")
#=> ["abc-abc", "-L", "c:folder", "nametest.log"]
Shellwords.split('"C:\\Program Files\\example\\program.exe" -arg1 -arg2')
#=> ["C:\\Program Files\\example\\program.exe", "-arg1", "-arg2"]
Shellwords.split('"C:\\Program Files\\example\\program.exe"')
#=> ["C:\\Program Files\\example\\program.exe"]
No need to reinvent the wheel with a custom regex/splitter, or an external system call.
It seems to me that if there's no consistent pattern to your command syntax, then any regex based approach will inevitably fail. It seems better instead to solve this problem the way a human would, i.e. with some knowledge of context.
In a *nix terminal, you can use the compgen command to list available commands. This Ruby script invokes that command to print the first 5 options from that list:
list = `cd ~ && compgen -c`
list_arr = list.split("\n")
list_arr[0,6].each{|x| puts x }
(The cd in the first line seems to be needed because of the context in which my Ruby is running with rvm.) For Windows, you may find this thread a useful starting point.
I'd match against the elements of this list to identify my commands, and take it from there.
Tom Lord's answer is far better than this one.
You probably want to look at OptionParser or GetOptLong if you need parsing of command line arguments provided to a ruby program.
If you are interested in parsing some strings that may or may not be commands with arguments, here's a quick-and-dirty:
I'd use scan instead of split with the following regex: /(".*"|[\w\:\:\.\-\\]+)/.
Best results come from: 'some string'.scan(/(".*"|[\w\:\:\.\-\\]+)/).flatten:
["ping", "http:\\www.google.com"]
["abc-abc", "-V"]
["abc-abc", "-L", "c:\\folder\\", "name\\test.log"]
# Technically, this is wrong, but so is the non-escaped whitespace.
["\"C:\\Program Files\\example\\program.exe\"", "-arg1", "-arg2"]
["\"C:\\Program Files\\example\\program.exe\""]

How to list files given path with poorly escaped Windows separator

I'm attempting to do this:
Dir["c:\temp\*.*"]
but that is failing. I understand why, but I seem to lack the Ruby prowess to work around it.
I am given the path in a variable and otherwise have no control over it. Nor do I know the contents ahead of time.
Is there a way to make Dir function with double quoted strings that are poorly escaped? Alternatively, how does one take a variable with the apparent contents
"c:\temp\*.*"
and convert it into
'c:/temp/*.*'
This problem at the core seems to be how to potentially escape a string that should have been escaped but now is not.
The end result is I am not able to use the given string to do this as conceptually simple as puts() or Dir[].
If given 'c:\temp\*.*' then I have no problem. I can fix that:
foo = 'c:\temp\*.*'.gsub('\\', '/')
If given "c:\\\\temp\\\\*.*" then I have no problem. I can fix that:
foo = "c:\\temp\\*.*".gsub("\\", "/")
However, I am passed neither of those, but rather "c:\\temp\\*.*". This string contains a TAB and a second undefined escape. It is this that I can't fix in a general way.
Even if I knew the contents ahead of time I am stumped on how to properly escape and transform this. I should add that I am not a ruby programmer at the moment so maybe there is some simple method to deal with this that I am not aware of.
I tried a bunch of stuff like:
"c:\temp\*.*".gsub("\t", "/t")
which gets me part of the way, but since the actual contents of the string are not known to me ahead of time this is a little wonky. Further, if the escape character is not valid as in \\* then I am also in a jam. So this also fails:
"c:\temp\*.*".gsub("\t", "/t").gsub("\*", "/*")
Is there a way to make Dir function with double quoted strings that are poorly escaped?
No.
Garbage in, garbage out. There is no Rumpelstiltskin routine that returns gold when given trash.
Ruby auto-converts forward-slashes in filenames/paths to reverse-slashes when running on Windows. Simply make it a habit of using forward, *nix-style, slashes and you'll be fine.
From the IO documentation:
Ruby will convert pathnames between different operating system conventions if possible. For instance, on a Windows system the filename "/gumby/ruby/test.rb" will be opened as "\gumby\ruby\test.rb". When specifying a Windows-style filename in a Ruby string, remember to escape the backslashes:
"c:\\gumby\\ruby\\test.rb"
I don't have "c:\temp" I have "c:\temp" as input
In a properly defined Windows path you should see:
'c:' + '\temp' + '\*.*' # => "c:\\temp\\*.*"
Note that the single-quotes are treating "\t" as an escaped-escape + "t". Your source for the variable is creating the string improperly by using double-quotes:
'c:' + "\temp" + "\*.*" # => "c:\temp*.*"
If you have "\t", you have a TAB character. It's possible to change it to an escaped-T using:
"c:\temp" # => "c:\temp"
"c:\temp"[2] # => "\t"
"c:\temp"[2].ord # => 9
'\t' # => "\\t"
"c:\temp".sub("\t", '\t') # => "c:\\temp"
The next problem is what to do when you have a String containing "*" to convert it to "\*". There's no way to search for "\*" because that's the same as "*" as seen above:
"\*.*" # => "*.*"
But, since "*.*" is a fairly specific "anything" wildcard, maybe simply searching for and replacing that pattern would work:
"c:\temp\*.*".gsub('*.*', '\\*.*') # => "c:\temp\\*.*"
or:
"c:\temp\*.*".gsub('*.*', '/*.*') # => "c:\temp/*.*"
Back to dealing with "\t" and putting it all together... I'd start with:
"c:\temp\*.*".gsub("\t", '\t').gsub('*.*', '/*.*') # => "c:\\temp/*.*"
"c:\temp\*.*".gsub("\t", '/t').gsub('*.*', '/*.*') # => "c:/temp/*.*"
You'll have to figure out what to do if you have something like:
c:/dir/file*.*
where they mean they want all files starting with file. Since you're seeing ambiguous inputs it seems the input routine needs to be more rigorous to not allow reversed-slashes.

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.

How do I capture the output of a pry shell command?

I'm using pry and I want to capture, and work with output of a shell command.
For example, If I run
pry(main)> .ls
I want to get the list of files into an array that I can work with in Ruby.
How can I do this?
This is a pretty old question but I'll answer it anyways. There are two primary methods of getting data out of pry commands. The first is if the command sets the keep_retval option to true, which the shell command does not. The second, is to use the virtual pipe. In your example this can be done as:
fizz = []
.ls | {|listing| fizz = listing.split("\n")} # can also be written as
.ls do |listing|
fizz = listing.split("\n")
end
I assume it's some kind of pry's magic ;-)
After quick look at what's happening (I didn't look at pry's source), you might want to use this:
`ls`.split("\n")
or
Dir['./*']
What's good about this solution is that it will work outside of pry

Resources