How to ignore output for a parameterized Ruby system call - ruby

I found some code that allows me to run an Applescript from Ruby via system calls:
system 'osascript', *script.split(/\n/).map { |line| ['-e', line] }.flatten
It works great, but it's not exactly what I need.
I want to modify the system call so that standard output is ignored.
I started by adding 1>/dev/null parameter:
system 'osascript', *script.split(/\n/).map { |line| ['-e', line] }.flatten, "1>/dev/null"
This didn't work as the third parameter appears to be ignored.
Then I removed the parameterized call and used string interpolation:
system "osascript #{*script.split(/\n/).map { |line| ['-e', line] }.flatten} 1>/dev/null"
which produced a syntax error.
Then I tried various attempts at moving things around, which mostly produced other syntax errors.
What's the correct syntax for ignoring system output in this case?

This would be easier if you switched from Kernel#system to Open3. The methods in Open3 give you convenient control over stdin, stdout, and stderr without having to mess around, you could even feed your script into osascript through stdin rather than splitting it apart and using multiple -e switches.
Something like this perhaps:
out, error, status = Open3.capture3('osascript', stdin_data: script)
And if you want to ignore stdout and stderr, use placeholder variables:
_, _, status = Open3.capture3('osascript', stdin_data: script)
*_, status = Open3.capture3('osascript', stdin_data: script)
And if you don't care about stdout, stderr, or the status:
Open3.capture3('osascript', stdin_data: script)
But your conscience should tell you to at least check the status.

The available options for system, exec and spawn are documented in Kernel#spawn, including those for redirection. To redirect stdout to /dev/null you'd use:
system('...', out: '/dev/null')
Or via File::NULL:
system('...', out: File::NULL)

Related

Passing input to an executable using Python subprocess module

I have an input file called 0.in. To get the output I do ./a.out < 0.in in the Bash Shell.
Now, I have several such files (more than 500) and I want to automate this process using Python's subprocess module.
I tried doing this:
data=subprocess.Popen(['./a.out','< 0.in'],stdout=subprocess.PIPE,stdin=subprocess.PIPE).communicate()
Nothing was printed (data[0] was blank) when I ran this. What is the right method to do what I want to do?
Redirection using < is a shell feature, not a python feature.
There are two choices:
Use shell=True and let the shell handle redirection:
data = subprocess.Popen(['./a.out < 0.in'], stdout=subprocess.PIPE, shell=True).communicate()
Let python handle redirection:
with open('0.in') as f:
data = subprocess.Popen(['./a.out'], stdout=subprocess.PIPE, stdin=f).communicate()
The second option is usually preferred because it avoids the vagaries of the shell.
If you want to capture stderr in data, then add stderr=subprocess.PIPE to the Popen command. Otherwise, stderr will appear on the terminal or wherever python's error messages are being sent.

Why does Open3.popen3 return wrong error when executable is missing?

I'm making a Ruby wrapper around a CLI. And I found a neat method, Open3.capture3 (which internally uses Open3.popen3), which lets me execute commands and captures stdout, stderr and exit code.
One thing that I want to detect is if the CLI executable wasn't found (and raise a special error for that). I know that the UNIX shell gives exit code 127 when command wasn't found.
And when I execute $ foo in bash, I get -bash: foo: command not found, which is exactly the error message I want to display.
With all that in mind, I wrote code like this:
require "open3"
stdout, stderr, status = Open3.capture3(command)
case status.exitstatus
when 0
return stdout
when 1, 127
raise MyError, stderr
end
But, when I ran it with command = "foo", I get an error:
Errno::ENOENT: No such file or directory - foo
/Users/janko/.rbenv/versions/2.1.3/lib/ruby/2.1.0/open3.rb:193:in `spawn'
/Users/janko/.rbenv/versions/2.1.3/lib/ruby/2.1.0/open3.rb:193:in `popen_run'
/Users/janko/.rbenv/versions/2.1.3/lib/ruby/2.1.0/open3.rb:93:in `popen3'
/Users/janko/.rbenv/versions/2.1.3/lib/ruby/2.1.0/open3.rb:252:in `capture3'
Why does this error occur? I thought Open3.capture3 was supposed to execute that command directly in the shell, why then don't I get a normal STDERR and exit code of 127?
Open3.popen3 delegates to Kernel.spawn, which depending on the way the command is passed in, gives the command to shell or directly to OS.
commandline : command line string which is passed to the standard shell
cmdname, arg1, ... : command name and one or more arguments (This form does not use the shell. See below for caveats.)
[cmdname, argv0], arg1, ... : command name, argv[0] and zero or more arguments (no shell)
We might expect that if we call Kernel.spawn("foo"), it would be passed to the shell (and not OS). But it doesn't, documentation for Kernel.exec explains why:
If the string from the first form (exec("command")) follows these simple rules:
* no meta characters
* no shell reserved word and no special built-in
* Ruby invokes the command directly without shell
You can force shell invocation by adding ";" to the string (because ";" is a meta character).
Last paragraph reveals the solution.
require "open3"
stdout, stderr, status = Open3.capture3(command + ";")
case status.exitstatus
when 0
return stdout
when 1, 127
raise MyError, stderr
end

Is it possible to capture output from a system command and redirect it?

What I would like to do is:
run a ruby script...
that executes a shell command
and redirects it to a named pipe accessible outside the script
from the system shell, read from that pipe
That is, have the Ruby script capture some command output and redirect it in such a way that it's connectable to from outside the script?
I want to mention that the script cannot simply start and exit, since it's a REPL. The idea is that using the REPL you would be able to run a command and redirect its output elsewhere to consume it.
Using abort and an exit message, will pass the message to STDERR (and the script will fail with exit code 1). You can pass this shell command output in this way.
This is possibly not the only (or best) way, but it has worked for me in the past.
[edit]
You can also redirect the output to a file (using standard methods), and read that file outside the ruby script.
require 'open3'
stdin, stderr, status = Open3.capture3(commandline)
stdin.chomp #Here, you should ge
Incase, if someone wanted to use you can get the output via stdin.chomp

hiding system command results in ruby

How easy is it to hide results from system commands in ruby? For example, some of my scripts run
system "curl ..."
and I would not prefer to see the results of the download.
To keep it working with system without modifying your command:
system('curl ...', :err => File::NULL)
Source
You can use the more sophisticated popen3 to have control over STDIN, STDOUT and STDERR separately if you like:
Open3.popen3("curl...") do |stdin, stdout, stderr, thread|
# ...
end
If you want to silence certain streams you can ignore them, or if it's important to redirect or interpret that output, you still have that available.
Easiest ways other than popen:
Use %x instead of system. It will automatically pipe
rval = %x{curl ...} #rval will contain the output instead of function return value
Manually pipe to /dev/null. Working in UNIX like system, not Windows
system "curl ... > /dev/null"
The simplest one is to redirect stdout :)
system "curl ... 1>/dev/null"
# same as
`curl ... 1>/dev/null`

Capturing STDOUT and STDERR of an external program *while* it's executing (Ruby)

Scenario:
I have to call an external program from my Ruby script, and this program sends a lot of useful (but cryptic) info to stdout and stderr.
While the program is running, I'd like to parse the lines it sends to stdout and stderr and:
Remove them if not necessary
Reformat/replace them if necessary
I tried all the usual tricks (system, exec, popen, popen3, backticks, etc. etc.), but I can only retrieve stdout/stderr after the program is executed, not during its execution.
Any ideas?
Oh, and I'm on Windows :-(
Actually, it was simpler than I thought, this seems to work perfectly:
STDOUT.sync = true # That's all it takes...
IO.popen(command+" 2>&1") do |pipe| # Redirection is performed using operators
pipe.sync = true
while str = pipe.gets
puts "-> "+str # This is synchronous!
end
end
...and yes, it works on Windows!

Resources