Separate stdin and stdout pipes with OS.execute_with_pipe

Godot Version

4.7.2

Problem

This is essentially the same post as Close only stdin pipe when using OS.execute_with_pipe but since that one is marked as solved I wanted to create my own post with a different workaround.

The main issue is that execute_with_pipebundles together stdin and stdout, even though this doesn’t seem to work in my case. I have a binary bin that reads a json string from stdin, and outputs a response json to stdout. This way you can do something like cat file.json | bin.

When doing this through execute_with_pipe, in order to let bin know that there is nothing left to receive, EOF needs to be sent, which you can do with proc[“stdio”].close(). But since stdio includes stdout, you can’t read anymore.

The solution in the thread linked above is to pass the length of the data sent to stdin, but then piping using shell commands becomes more awkward, so this is not a good solution imo. What I ended up doing was making bin output to stderr instead, and then I can do:

var proc = OS.execute_with_pipe(path, ["get"], false)
if proc.is_empty():
	return {}

var file: FileAccess = proc["stdio"]
var stderr: FileAccess = proc["stderr"]
file.store_string('{"test": 1}')
file.close()

while OS.is_process_running(proc["pid"]):
	pass
var ret = stderr.get_as_text()

Question

My two questions here are:

  • Is it wrong to depend on EOF in bin? Maybe I should output the moment the closing root} is received.
  • Am I missing something here and maybe it is possible to independently manage stdin and stdout?

My guess is that stdin sends data on EOL, maybe there’s a way you can send this character instead of closing the file ? have you tried with line feeds (\n) ?

I just made a test with your code, without the file.close() and executing a little shell script that reads input and outputs the result

read hello
echo you typed:
echo $hello
echo end

I confirm it works with \n as “enter” (EOL)
if I omit the \n the script hangs

Don’t close the file, don’t forget to flush() it and you can read the output and send inputs on the same file

...
var file: FileAccess = proc["stdio"]
var stderr: FileAccess = proc["stderr"]
print("ready for stdin")
file.store_string('hello\n') # use \n for input
file.flush() # important !
...

PS: if your bin really needs EOF for closing stdin, her’s an interresting post https://stackoverflow.com/a/1622108

in brief: don’t use EOF for end-of-input

Turns out that both are needed indeed.

I changed my binary to naively count { and matching } until the end of the json is reached, and parse that. That way I don’t rely on EOF.

On top of that in Godot I also had to write \n and flush, as you said. That works as expected, no need for stderr.

Thanks!

I’ll add : if your json is minified without newlines and textual newlines are escaped, written \n in the input, you could save yourself the json parsing and juste parse the first input line as the full json, avoiding bugs if the data contains { or } characters and improving performance

I wonder what is the “official” way for your case, maybe it’s giving the name of the JSON file and actually read it and wait for EOF in the bin, I imagine that’s how pipe works (with a temp buffer file)… or maybe there’s another EOX we don’t think about