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

Multi tool use
Multi tool use
The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP


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





Does the script -q ... thing work from the command line if you pipe it into cat?
– Piccolo
yesterday


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.

zSr4qpU,Lp1 sQT SCzoGDfMXjfApQB5TB8kZ6H 06lXcJaHa M4T
CeUVPTXxsFvvnp42RMaJXV nejOri

Popular posts from this blog

Keycloak server returning user_not_found error when user is already imported with LDAP

PHP parse/syntax errors; and how to solve them?

415 Unsupported Media Type while sending json file over REST Template