cmd = "bash"
args = "-i"
reader,writer = IO.pipe
pp writer
p1 = Process.new(cmd, shell: false, input: writer, output: reader)
pp p1
sleep 2
writer.puts "pwd"
sleep 2
puts reader.gets
sleep
#<Process:0x7f09a79f3fc0
@channel=nil,
@error=nil,
@input=nil, # why is input nil here ?
@output=nil,
@pid=15373,
@wait_count=0,
@waitpid=#<Channel(Int32):0x7f09a79f3c80>>
Please learn to use markdown code blocks and preserve indentation while copy pasting code. Your code posts tend to be really hard to read.
Process.run
with a block sets up pipes for you by default:
Process.run("ls", args: {"/"}) do |proc|
puts proc.output.gets
end
If you set them explicitly, Process
does not bother to retain a reference to them, you passed one in, so you got it already! Just reuse your writer
variable.
Ok, I will try to write better readable postings.
I have some experience with Process.run and the default pipes.
My issue is that I want to use STDIN and STDOUT in the main process,
and at the same time use a pipe for communication with a spawned long running process like an interactive shell session(“bash -i”).
- I do not want that STDIN and STDOUT is piped to the spawned process, therefore I want to setup an explicit additional pipe which uses its own filedesrciptors. The main process still needs to read from the original STDIN. So somehow I want to pass filedescriptors to process.new or process.run.
This is the case with above code. STDIN
is still the original in the block.
You may need to spawn
a fiber and communicate to it through a Channel
if you want to read from the process and STDIN
at the same time rather than on a back and forth kind of basis.
I got my shell wrapper working by using socat or python to achieve a sort of PTY session. Is there a way in crystal to fake/spawn a PTY ? This wrapper can be used to talk to any process STDIN which is normally used in a terminal session.
- I always wanted to have a sort of mediation script on STDIN which allows me to add additional help when commands are typed in, e.g. open a browser help window or search an API for code completion or code examples while typing.
def run_cmd(cmd, mystdin)
spawn do
Process.run(cmd, shell: true, output: STDOUT, input: mystdin)
end
end
#Make the shell believe that input comes from a PTY (pseudo terminal)
#cmd ="python -c 'import pty; pty.spawn(\"/bin/bash\")'"
cmd ="socat exec:'bash -i',pty,stderr,setsid,sigint,sane -"
reader, writer = IO.pipe
handle_stdin(STDIN,writer)
run_cmd(cmd, reader)
writer << "date\n" #write something to the shells STDIN
#handle stdin char by char - this allows command completion with TAB
def handle_stdin(myin,writer)
spawn do
while (char = STDIN.raw &.read_char) != '\u{3}' #ctrl-c
#p! char
print " #Return" if char == '\r'
writer << char
end
puts "You have pressed crtl-c"
exit
end
end
sleep
I’m not aware of any standard library or shard for dealing with pty’s, which means the way is to figure out how to do it in C, then write a C binding doing the same thing. Then ideally building a nice abstraction on it and publishing it as a shard so others don’t have to do it again :)
Here is how Ruby 2.7 does it in the ruby stdlib:
https://www.rubydoc.info/stdlib/pty/PTY.spawn
https://ruby-doc.org/stdlib-2.7.0.rc2/libdoc/pty/rdoc/PTY.html
The C code (its about 700 lines):
Back to the original question, which I think has 2 parts:
-
Why are
input
andoutput
nil inProcess
?
Looking at the Crystal code forProcess
, these are only set if the input/output arguments are passed asProcess::Redirect::Pipe
. -
Why doesn’t your code work?
The problem is that you are only using one pipe. Pipes are unidrectional, unlike sockets. A process writes into one end and another reads from the other end. If you want to pass commands tobash
, you need one pipe to do that, and if you want to read its output, you need a second pipe. This variation of your original code seems to work:
cmd = "bash" reader1,writer1 = IO.pipe reader2,writer2 = IO.pipe p1 = Process.new(cmd, shell: false, input: reader1, output: writer2, error: Process::Redirect::Inherit) reader1.close writer2.close pp p1 sleep 2 writer1.puts "pwd" puts reader2.gets sleep 2 writer1.puts "ls -l" puts reader2.gets puts reader2.gets puts reader2.gets sleep
This also makes sure that bash’s STDERR is not closed so you can see any errors. What would be even neater is to let Process.new
create the pipes for you by setting them to Process::Redirect::Pipe
, then you can use p1.input
& p1.output
.