I want to define a function to wrap %x as below
def myx(arg)
puts %x("#{arg}")
end
It's OK to call myx("ls") but failed to call myx("ls /usr"). The error message is
sh: 1: ls /usr: not found
I understand the error. It's because that the whole word ls /usr is taken as a single argument passed to the shell. But how can I fix the problem?
Make sure you quote your entire command inside the string:
def myx(arg)
puts %x("#{arg}")
end
myx('"ls /usr"')
Alternatively backticks don't require the string to be quoted:
def myx(arg)
puts `#{arg}`
end
myx("ls /usr")
Even nicer in my opinion:
def myx(arg)
system(*arg)
end
myx(['ls', '/usr'])
Outputs:
bin
games
include
lib
lib32
lib64
libx32
local
sbin
share
src
The first two pass the entire command as a string to the shell for execution. The last one bypasses the shell and ruby executes it itself.
Do not quote the command line with " in %x.
arg = "ls /usr"
%x("#{arg}")
will send the "ls /usr" as a single argument to the system, as if you execute the following in shell:
$ "ls /usr"
The system will try to locate a command/executable named ls /usr, which leads to the problem you met. So update your method to the following will work
def myx(arg)
puts %x(#{arg})
end
myx('ls /usr')
The previous answer
def myx(arg)
puts %x("#{arg}")
end
myx('"ls /usr"')
works as if you execute the following in the shell:
$ ""ls /usr""
# which results in
$ ls /usr
That's not quoting the command, rather, it uses extra " to clear the quote.
Related
I'd like to create a Ruby script that prefixes the console output. For example:
I want to implement an interface like this:
puts 'MainLogger: Saying hello'
prefix_output_with('MainLogger') do
system 'echo hello'
end
So this shows up in the console:
MainLogger: Saying hello
MainLogger: hello
What would be a good approach to prefix all of the syscall output to have some logger?
Note: I don't care if we echo what the system call is or not
The important point here is that there's no way to know if system will actually produce output. I'm assuming you don't want a blank MainLogger: whenever a system call doesn't print anything, so you'll need to do the prefixes in the shell:
def prefix_system_calls pre
sys = Kernel.instance_method(:system)
# redefine to add prefix
Kernel.send(:define_method, :system) do |cmd|
sys.bind(self)["#{cmd} | sed -e 's/^/#{pre}: /'"]
end
yield
# redefine to call original method
Kernel.send(:define_method, :system) do |*args|
sys.bind(self)[*args]
end
end
system "echo foo"
prefix_system_calls("prefix") do
system "echo bar"
end
system "echo baz"
# foo
# prefix: bar
# baz
This implementation is pretty fragile, though. It doesn't handle all the different ways you can call system, and prefixes containing special shell characters could cause an error.
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
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'm new to ruby ... wondering if the following is possible:
I currently run a test app within irb (irb -r test.rb) and manually execute
various command implemented in test.rb. One of these functions is currently implemented as follows:
def cli(cmd)
ret=$client.Cli(cmd)
print ret, "\n"
end
Where $client.Cli() takes a string. I currently type the following in the IRB prompt
> cli "some command with parameters"
This is sent over socket and results are returned
I would like to be able to do this WITHOUT the quotes. This would be just for this command
Is there a way to do this generally in ruby? if not how would you extend irb to do this?
For those who know 'C' this would be like the following:
#define CLI(CMD) cli(#CMD)
CLI(Quadafi and Sheen walk into a bar...)
where the pre-processed output is:
cli("Quadafi and Sheen walk into a bar...")
Thanks
You could actually monkey patch the gets method of the IRB::StdioInputMethod and IRB::ReadlineInputMethod classes, and perform a rewrite if the cli method is called, by adding the following to your test.rb file:
module IRB
def self.add_quotes(str)
str.gsub(/^cli (..+?)(\\+)?$/, 'cli "\1\2\2"') unless str.nil?
end
class StdioInputMethod
alias :old_gets :gets
def gets
IRB::add_quotes(old_gets)
end
end
class ReadlineInputMethod
alias :old_gets :gets
def gets
IRB::add_quotes(old_gets)
end
end
end
This way, any input line matching cli ... will be replaced with cli "..." before it's evaluated.
I don't think it's possible, because the command that you type to irb has to parse as ruby, and all those bare words will report errors like this:
NameError: undefined local variable or method `hello' for main:Object
(My first attempt, I just called it via cli hello.)
But if you didn't mind a more radical change, you could do something like this:
$ cat /tmp/test_cases
hello world
one
two
three
riding the corpse sled
$ ruby -e 'def f(arg) puts arg end' -ne 'f($_)' < /tmp/test_cases
hello world
one
two
three
riding the corpse sled
I just defined a simple function f() here that would show how it works; you could replace f($_) with $Client.cli($_), and set up the $Client global variable in the first -e argument. And you can leave off the < /tmp/test_cases if you want to type them in interactively:
$ ruby -e 'def f(arg) puts arg end' -ne 'f($_)'
hello
hello
world
world
Of course, if you want it to be any more advanced than this, I'd just write a script to do it all, rather than build something hideous from -pe or -ne commands.