Controlling a PowerShell/bash/REPL session from Go?

polaris · · 530 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I am trying to spawn a PowerShell process and then use the stdin/out/err pipes to send commands and receive their output.</p> <pre><code>cmd := exec.Command(&#34;powershell.exe&#34;, &#34;-NoExit&#34;, &#34;-Command&#34;, &#34;-&#34;) stdin, _ := cmd.StdinPipe() stdout, _ := cmd.StdoutPipe() cmd.Start() stdin.Write([]byte(&#34;$a = 1\r\n&#34;)) ioutil.ReadAll(stdout) stdin.Write([]byte(&#34;$b = 2\r\n&#34;)) ioutil.ReadAll(stdout) stdin.Write([]byte(&#34;$a + $b\r\n&#34;)) ioutil.ReadAll(stdout) </code></pre> <p>Unfortunately, it seems I must call <code>.Close()</code> on the stdin pipe before the command is actually send and I can read stdout. Obviously the second <code>.Write()</code> attempt then fails flat on its face.</p> <p>Is it possible to send multiple inputs into a process? I&#39;ve found a <a href="https://groups.google.com/forum/#!topic/golang-nuts/KYfp4uh9mvk" rel="nofollow">golang-nuts thread</a> from 2013, but that ended without a solution.</p> <p>[Edit: Here is a minimal compiling sample: <a href="http://pastebin.com/MNTeyrGt" rel="nofollow">http://pastebin.com/MNTeyrGt</a>]</p> <hr/>**评论:**<br/><br/>Matthias247: <pre><p>Your problem is that you try to read the stdout with ioutil.ReadAll. This will block until stdout produces end of stream. However as you might imagine that a shell will normally not close it&#39;s output until the shell is closed: It has to read new commands (through stdin) and provide the output for them (via stdout). So after writing a command you should not read up to EndOfStream but only up to the point where the shell expects a new input. That&#39;s however not too easy, as some commands will output only a single line and others multiple ones - and it may even take some time until all output is produced.</p></pre>xrstf: <pre><p>Thanks for your input; it kind of confirms my thoughts that I simply cannot just use <code>ReadAll()</code> in this case, but have to handle reading from the stream myself.</p> <p>I got inspired by jPowerShell, a Java wrapper around PS, to handle the end-of-output by always including an <code>...; echo random_marker_string</code> to the command and then scanning for that string.</p> <p>[Edit: So I&#39;ve changed my code to not close stdin and manually read a few bytes from stdout, like so</p> <pre><code>func run(stdin io.WriteCloser, stdout io.ReadCloser, command string) (string, error) { fmt.Printf(&#34;&gt; %s\n&#34;, command) written, err := stdin.Write([]byte(command + &#34;\r\n&#34;)) if err != nil { return &#34;&#34;, err } buf := make([]byte, 128) read, err := stdout.Read(buf) fmt.Printf(&#34;read %d bytes from stdout.\n&#34;, read) if err != nil { return &#34;&#34;, err } return string(buf), err } </code></pre> <p>This works better, but it also clearly brings up a bunch of timing issues. Like waiting for 2 seconds after writing to stdin before reading from stdout works much better. There&#39;s a lot of work to do, but at least it seems possible after all :-)]</p></pre>jammerlt: <pre><p>Did you try to flush the writes?</p></pre>xrstf: <pre><p>The stdin pipe is an <a href="https://golang.org/pkg/io/#WriteCloser" rel="nofollow"><code>io.WriterCloser</code></a> and supports only <code>.Write</code> and <code>.Close</code>, if I&#39;m not mistaken.</p></pre>jammerlt: <pre><p>I think you can type assert it to a io.Flusher, and call flush, same like you can on a http.ResponseWriter</p></pre>xrstf: <pre><p>The value returned by <code>StdinPipe()</code> is an <a href="https://golang.org/src/os/exec/exec.go#L522" rel="nofollow"><code>exec.closeOnce</code></a> struct, which does not support <code>Flush()</code>. I could try to gain access to the embedded <code>os.File</code>, but that would still not give me a flush, only a <code>Sync()</code>.</p></pre>ChristophBerger: <pre><p>Both <code>io.Writer.Write</code> and <code>ioutil.ReadAll</code>return an error as the second return value. Always check returned error values, they help determining why a call fails.</p></pre>xrstf: <pre><p>Sorry for providing a misleading sample; my code does indeed check for any error that occurs. :)</p> <p>I&#39;ve created a minimal running sample at <a href="http://pastebin.com/MNTeyrGt" rel="nofollow">http://pastebin.com/MNTeyrGt</a></p> <p>Without the <code>Close()</code>, <code>ReadAll()</code> just hangs and no errors occur anywhere. With closing, the second attempt to write to stdin results in a</p> <pre><code>&gt; echo foo ~ wrote 10 bytes to stdin. foo &gt; echo bar panic: write |1: Das Handle ist ungültig. goroutine 1 [running]: panic(0x4c5d20, 0xc042096b10) D:/opt/go/src/runtime/panic.go:500 +0x1af main.main() Q:/gospace/src/github.com/xrstf/testps/main.go:35 +0x2ca </code></pre></pre>ChristophBerger: <pre><p>Ok, I believe <a href="/u/Matthias247" rel="nofollow">/u/Matthias247</a> nailed the problem. ReadAll waits indefinitely, and when not reading all, it is difficult to know when to stop. </p> <p>If you just want to collect all output (e.g. for logging purposes), you could read the output in a separate goroutine.</p> <p>If you need the output from one shell command for invoking the next one, this trick might help:</p> <p>After each command, send an echo command (or whatever PowerShell&#39;s equivalent to Bash echo is) with a unique text, e.g. <code>echo ***end***</code>. Then read stdout up to this text - then you know you have captured all of the recent command&#39;s output.</p></pre>sbinet: <pre><p>not sure whether powershell works exactly like bash, but here what one should do in that situation: use <code>pkg/term</code>.</p> <p><em>e.g.</em>: <a href="https://github.com/driusan/gosh/blob/master/main.go#L226" rel="nofollow">https://github.com/driusan/gosh/blob/master/main.go#L226</a></p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

530 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传