Open3.popen2e hangs when executing commands from Ruby on Ubuntu - ruby

I'm using a similar script to do some setup tooling for my remote development setup in Ruby 2.7.2:
require "open3"
Open3.popen2e("ssh -N -f root#example.com") do |stdin, stdout_and_stderr, wait_thread|
puts wait_thread.value.inspect
puts "closed? #{stdout_and_stderr.closed?}"
stdout_and_stderr.each do |line|
puts line
end
puts wait_thread.value.inspect
end
On macOS 12.4 Monterey (M1 Chip) it produces the following output:
#<Process::Status: pid 76747 exit 0>
closed? false
#<Process::Status: pid 76747 exit 0>
But when I execute the same command on a Ubuntu 20.04, it hangs when stdout_and_stderr.each is called:
#<Process::Status: pid 13115 exit 0>
closed? false
--> here it just hangs and does nothing
The same effect can be ovserved when calling stdout_and_stderr.eof?. It looks like no more output is comming, and it just waits for it but never finishes. Unfortunately, I didn't find a way in Ruby to tell if there is more output or not.
When using a normal ls -lah command for example, it does not hang. So it must have something to do with the ssh command. These are the versions I'm using:
# macOS 12.4
❯ ssh -V
OpenSSH_8.6p1, LibreSSL 3.3.6
# Ubuntu 20.04
ubuntu#user:~/workbox$ ssh -V
OpenSSH_8.2p1 Ubuntu-4ubuntu0.5, OpenSSL 1.1.1f 31 Mar 2020
I can't really control the SSH version, as more people will use this tooling. I thought about using a timeout, but when a command takes longer to produce output, this could lead to some really confusing behaviour.

Related

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_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.
However, when run directly, sass-lint produces nice colorized output. When captured by capture2e, the colors are lost.
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?
(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.)
Update: I tried script -q /dev/null and PTY.spawn() as per Piccolo's answer, but no luck.
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).
As for PTY.spawn(), replacing the code above with the following:
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.
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
Have you considered using Ruby's excellent pty library instead of 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.
According to this flowchart, the downside of using pty instead of Open3 is that STDERR does not get its own stream.
Alternatively, per this answer, also from the thread you linked, script -q /dev/null $COMMAND appears to do the trick on Mac OS X.
On Macs, ls -G colorizes the output of ls, and as a brief test, I piped ls -G into cat as follows:
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:
$ 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")
=> ["directory\nexecutable_file\nregular_file\n", #<Process::Status: pid 39299 exit 0>]
2.4.1 :003 > output, status = Open3.capture2e("script -q /dev/null ls -G")
=> ["^D\b\b\e[1m\e[36mdirectory\e[39;49m\e[0m \e[31mexecutable_file\e[39;49m\e[0m regular_file\r\n", #<Process::Status: pid 39301 exit 0>]
2.4.1 :004 >

bash --debugger does not work on with bash 4.3 on a script with no arguments after installing bashdb

I installed bashdb on fedora 21 which uses bash 4.3 . I need to run using --debugger because I want $0 to be set correctly to the name of the script rather than bashdb.
bash --debugger my.bash
But the script is just executed, there is no debug session. On the other hand running:
bash --debugger my.bash ""
works fine.
What am I doing wrong?
This is a bug that I think was introduced in bash 4.3: Wed Feb 26 09:36:43 2014 -0500 Bash-4.3 distribution sources and documentation
The code added that causes this is:
if (debugging_mode && locally_skip_execution == 0 && running_setuid == 0 && dollar_vars[1])
start_debugger ();
I have posted a bug report on this. See http://lists.gnu.org/archive/html/bug-bash/2015-04/msg00183.html
The short story in the above thread is that although bash versions 4.3 up to 4.3.33 have this bug, in the next release after 4.3.33 (sometime after April 2015), this bug will be fixed.
When you use bash --debugger, the debugger has to be installed in the location that bash expects. Although the configure script tries to figure this out, many times it gets this wrong. To see where bash thinks the debugger should be installed, run:
strings /bin/bash | grep bashdb
On Ubuntu, the answer I got was: /usr/share/bashdb/bashdb-main.inc. So bashdb is expected to be installed in /usr/share/bashdb. Using this, then run configure like this:
/bin/bash ./configure -with-bashdb-main.inc=/usr/share/bashdb/bashdb-main.inc

Call a unix program from a TCL variable

My TCL script puts together a call to a command line tool using TCL variables.
I've tried exec or eval for the command line but nothing worked.
#!/usr/bin/tclsh
set dbg 0
set iso 100
set cmd "gphoto2 --set-config-value /main/imgsettings/iso=${iso}"
if {$dbg} {puts $cmd} else {eval $cmd}
Gives :
invalid command name "gphoto2"
while executing
"gphoto2 --set-config-value /main/imgsettings/iso=100"
("eval" body line 1)
invoked from within
"eval $cmd"
invoked from within
"if {$dbg} {puts $cmd} else {eval $cmd}"
(file "./canon.tcl" line 22)
If tried { exec $cmd } but that did not work either.
couldn't execute "gphoto2 --set-config-value /main/imgsettings/iso=100": no such file or directory
while executing
"exec $cmd"
invoked from within
"if {$dbg} {puts $cmd} else {exec $cmd}"
(file "./mofi_canon.tcl" line 22)
I tried giving absolute path name /usr/bin/gphoto2 but again no success.
I guess the problem has to do with the entire command string being one object and the unix execution cannot parse it. But what's the way to give it to the shell properly?
from the linux command line of course the command works fine.
#ubuntu:~/TCL$ gphoto2 --summary
Camera summary:
Manufacturer: Canon Inc.
Model: Canon EOS DIGITAL REBEL XSi
This is from a kubuntu 14.04 linux.
uname -a
Linux ubuntu 3.13.0-36-generic #63-Ubuntu SMP Wed Sep 3 21:30:07 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
Thanks & Cheers,
Gert
If you are using tcl 8.5 or higher version, then you can use as follows,
exec {*}$cmd
Else, use the below code
eval exec $cmd
Let's consider an example as such
example.tcl
#usr/bin/tclsh
puts $argc
puts [ lindex $argv 0 ]
puts [ lindex $argv 1 ]
execargs.tcl
#usr/bin/tclsh
#Calling the example.tcl file with the command line args
set cmd "tclsh example.tcl 1 2"
#For Tcl versions less than 8.5, this will work
puts [ eval exec $cmd ]
#For Tcl 8.5 or higher, this will work
puts [ exec {*}$cmd ]
I haven't used tcl in a long while, but I suspect the problem is you are combining the command into one variable.
Try executing
exec gphoto2 --set-config-value /main/imgsettings/iso=100
in tclsh. That should work.
If you combine the entire command into one var, you are trying to find an executable of that name.

Losing STDOUT when using Open3.capture3 and rake?

A build system that I use at work invokes several external console applications, Node.js among others.
The issue I am seeing is the STDOUT channel seems to not work after Open3.capture3 is invoked. For instance, I have a task called compileLess:
desc "Compile LESS"
task :compileLess do
puts "Preparing to compile LESS..."
execute "recess less/bootstrap.less --compress > output/css/bootstrap.min.css"
puts "Finished compiling LESS"
end
def execute(cmdLine, print_stdout = false)
puts "Executing #{cmdLine}"
stdout, stderr, status = Open3.capture3(cmdLine)
puts stdout if print_stdout
return stdout, stderr, status
end
What I would expect to see is something like:
Preparing to compile LESS...
Executing recess less/bootstrap.less --compress > output/css/bootstrap.min.css
Finished compiling LESS
But anything after the invocation of Open3.capture3 disables puts and print. I can force them to work by explicitly using:
STDOUT.puts "goodbye world"
I just want to know why it doesn't work.
Specs:
Window 7 Professional 32 bit
Ruby 1.9.3p392 (2013-02-22) [i386-mingw32]
Rake, version 10.1.0
Node v0.10.22
You redirected the STDOUT of the command-line with > output/css/bootstrap.min.css.
Your STDOUT from capture3() is empty of course.

How do I configure ruby to enter the debugger on Ctrl-C (SIGINT)?

I'd like to enter the debugger upon typing ctrl-C (or sending a SIGINT). I have installed the debugger (I'm running Ruby 1.9.3) and verified that it works. I've added this to my setup files (this is for Padrino, but I assume it would be similar for Rails):
# file: config/boot.rb
Padrino.before_load do
trap("SIGINT") { debugger } if Padrino.env == :development
end
... but typing Ctrl-C does not invoke the debugger. In fact, if I replace debugger with puts "saw an interrupt!", typing Ctrl-C doesn't cause a print to happen either.
update
Following this suggestion from Mike Dunlavey, I tried explicitly calling catch Interrupt from within the debugger:
$ rdebug `which padrino` console
^Z^Z$HOME/usr/bin/padrino:9
require 'rubygems'
(rdb:1) catch Interrupt
Catch exception Interrupt.
(rdb:1) c
=> Loading development console (Padrino v.0.10.7)
=> Loading Application BlueDotAe
=> Loading Application Admin
irb(main):001:0> C-c C-c^C
irb(main):001:0>
No joy -- interrupt did not enter the debugger.
What am I missing?
If you want to trap SIGINT while running in the console, the short answer is: you cannot unless you monkey-patch IRB. Every Ruby app (whether padrino, or rails or whatnot) that uses the console will end up calling usr/lib/ruby/1.9.1/irb.rb, and in IRB.start, it does:
trap("SIGINT") do
irb.signal_handle
end
... just before entering the main loop. This will override any trap("SIGINT") you might have put in your startup code.
But if you want to trap SIGINT in a script file (for example, if you want to profile your code as described by Mike Dunlavey here), you can create a script file such as:
# File: profile_complex_operation.rb
trap("SIGINT") { debugger }
MyApp.complex_operation
and then invoke it as in:
$ ruby profile_complex_operation.rb
Now, when you hit ^C (or send SIGINT from another process), it will enter the debugger.
You may try to use GDB wrapper for Ruby (GitHub).
Install on Linux via:
sudo apt-get install gdb python-dev ncurses-dev ruby-rvm
gem install gdb.rb
Basic usage:
require 'gdb'
# create a new GDB::Ruby instance and attach it to
# pid 12345
gdb = GDB::Ruby.new(12345)
# print the (ruby) backtrace of the remote process
gdb.backtrace.each { |line| puts line }
# show the current local variables, and their values
p gdb.local_variables
# evaluate arbitrary ruby code in the remote process
p gdb.eval('%(pid #{$$})')
# show how many instances of each class exist in the
# remote process
p gdb.object_space
# raise an exception in the remote process
gdb.raise Exception, "go boom!"
# close the connection to the remote process
gdb.quit
Or to debug the hung process, attach it via:
rvmsudo gdb.rb PID
then:
# in gdb get a ruby stacktrace with file names and line numbers
# here I'm filtering by files that are actually in my app dir
(gdb) ruby eval caller.select{|l| l =~ /app\//}
Source: Using gdb to inspect a hung ruby process
Some alternatives:
rbtrace - like strace, but for ruby code (usage: rbtrace -p <PID> --firehose).
debug.rb script by tmm1 (author of gdb.rb) which can help to debug a process using strace/gdb.
See also:
Debugging Ruby Tools
Check why ruby script hangs

Resources