Capture ANSI colorized output with Ruby's Open3 / Process.spawn()

Multi tool use


Capture ANSI colorized output with Ruby's Open3 / Process.spawn()
I'm using the sass-lint
NPM package to style-check .scss
files from within a Rake task, thus:
sass-lint
.scss
sass_lint_cmd = "sass-lint --config #{ui_library_path}/scss/.sass-lint.yml '#{ui_library_path}/scss/*.scss' -v -q --max-warnings=0"
output, status = Open3.capture2e(sass_lint_cmd)
raise IOError, output unless status == 0
This basically works, insofar as in the event of any linter warnings or errors the Rake task aborts and the sass-lint
output, including errors, is dumped to the console.
sass-lint
However, when run directly, sass-lint
produces nice colorized output. When captured by capture2e
, the colors are lost.
sass-lint
capture2e
I assume the issue is that sass-lint
(or Node) detects it's not running in a TTY, and so outputs plain text. Is there some Process.spawn()
option I can pass to Open3.capture2e()
, or some other method, by which I can make it think it's running in a TTY?
sass-lint
Process.spawn()
Open3.capture2e()
(Note: I did look at Trick an application into thinking its stdout is a terminal, not a pipe, but the BSD version of script
that ships with macOS doesn't seem to support either the --return
or the -c
options, and I'm running on macOS.)
script
--return
-c
Update: I tried script -q /dev/null
and PTY.spawn()
as per Piccolo's answer, but no luck.
script -q /dev/null
PTY.spawn()
script -q /dev/null …
works from the command line, but doesn't work in Open3.capture2e()
(it runs, but produces monochrome output and a spurious Bundler::GemNotFound
stack trace).
script -q /dev/null …
Open3.capture2e()
Bundler::GemNotFound
As for PTY.spawn()
, replacing the code above with the following:
PTY.spawn()
r, _w, pid = PTY.spawn(scss_lint_command)
_, proc_status = Process.wait2(pid)
output, status = [r, proc_status.exitstatus]
(warn(output); raise) unless status == 0
the subprocess never seems to complete; if I ps
in another terminal it shows as in interruptible sleep status. Killing the subprocess doesn't free up the parent process.
ps
The same happens with the block form.
output, status = nil
PTY.spawn(scss_lint_command) do |r, _w, pid|
_, proc_status = Process.wait2(pid)
output, status = [r, proc_status.exitstatus]
end
(warn(output); raise) unless status == 0
script -q ...
cat
1 Answer
1
Have you considered using Ruby's excellent pty
library instead of Open3
?
pty
Open3
Pseudo terminals, per the thread you linked, seem to emulate an actual TTY, so the script wouldn't know it wasn't in a terminal unless it checked for things like $TERM
, but that can also be spoofed with relative ease.
$TERM
According to this flowchart, the downside of using pty
instead of Open3
is that STDERR
does not get its own stream.
pty
Open3
STDERR
Alternatively, per this answer, also from the thread you linked, script -q /dev/null $COMMAND
appears to do the trick on Mac OS X.
script -q /dev/null $COMMAND
On Macs, ls -G
colorizes the output of ls
, and as a brief test, I piped ls -G
into cat
as follows:
ls -G
ls
ls -G
cat
script -q /dev/null ls -G | cat
and it displayed with colors, whereas simply running
ls -G | cat
did not.
This method also worked in irb
, again using ls -G
:
irb
ls -G
$ touch regular_file
$ touch executable_file
$ mkdir directory
$ chmod +x executable_file
$ irb
2.4.1 :001 > require 'Open3'
=> true
2.4.1 :002 > output, status = Open3.capture2e("ls -G")
=> ["directorynexecutable_filenregular_filen", #<Process::Status: pid 39299 exit 0>]
2.4.1 :003 > output, status = Open3.capture2e("script -q /dev/null ls -G")
=> ["^Dbbe[1me[36mdirectorye[39;49me[0m e[31mexecutable_filee[39;49me[0m regular_filern", #<Process::Status: pid 39301 exit 0>]
2.4.1 :004 >
Tried both, no luck. :( See update. But I'm probably not calling
PTY.spawn()
/ Process.wait2()
correctly.– David Moles
yesterday
PTY.spawn()
Process.wait2()
@DavidMoles Hmm, not sure about the
script -q /dev/null ...
not working, but let me see if I can figure out what's wrong with your PTY.spawn()
ing.– Piccolo
yesterday
script -q /dev/null ...
PTY.spawn()
@DavidMoles I tried running the following:
lines = ; PTY.spawn("ls -G && sleep 5") {|reader, writer, pid| reader.each {|line| lines << line }; Process.wait2(pid) }
, and Process.wait2(pid)
blocked for the right amount of time, and the output was correct. I don't really see any difference between what you did and that; can you try running mine on your system so we can see if it's a difference between ls -G
and your command, or if it's a system issue?– Piccolo
yesterday
lines = ; PTY.spawn("ls -G && sleep 5") {|reader, writer, pid| reader.each {|line| lines << line }; Process.wait2(pid) }
Process.wait2(pid)
ls -G
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Does the
script -q ...
thing work from the command line if you pipe it intocat
?– Piccolo
yesterday