【问题标题】:How do I execute a command, feed data to its stdin, and read from its stdout in MacRuby?如何在 MacRuby 中执行命令、向其标准输入提供数据以及从标准输出读取数据?
【发布时间】:2013-03-13 03:38:24
【问题描述】:

我正在尝试执行一个命令,将数据提供给它的标准输入,并从它的标准输出中读取。我尝试使用通过 MacRuby 公开的 Ruby 的 Open3#popen3 以及 NSTask。我正在编写的程序的源代码是here。我在 Xcode 和 MacRuby 中这样做。

这里有一些选择代码:

入口点,只是简单地让我在两种方法之间轻松切换。

def do_gpg_cmd cmd
  do_gpg_cmd_nstask cmd
end

ruby 方式,使用 Open3#popen3。

def do_gpg_cmd_ruby cmd
  gpg = "#{@gpg_path} --no-tty "
  cmd_output = ''
  logg "executing [#{cmd}]"
  Dispatch::Queue.concurrent.async do
    logg "new thread starting"
    Open3.popen3(gpg + cmd) do |stdin, stdout, stderr|
      stdin.write input_text
      stdin.close
      cmd_output = stdout.read
      output_text cmd_output
      stdout.close
      logg stderr.read
      stderr.close
    end
  end
  return cmd_output
end

在这种方法中,应用程序会冻结(我正在通过单击应用程序中的“签名”按钮进行测试,该按钮运行gpg --clearsign --local-user $key)。

当我终止应用程序时,Xcode 会在自动出现的线程诊断中显示:

libsystem_kernel.dylib`__psynch_cvwait:
0x7fff84b390f0:  movl   $33554737, %eax
0x7fff84b390f5:  movq   %rcx, %r10
0x7fff84b390f8:  syscall
0x7fff84b390fa:  jae    0x7fff84b39101            ; __psynch_cvwait + 17 ; THIS LINE IS HIGHLIGHTED
0x7fff84b390fc:  jmpq   0x7fff84b3a4d4            ; cerror_nocancel
0x7fff84b39101:  ret    
0x7fff84b39102:  nop    
0x7fff84b39103:  nop  

Cocoa 方式,使用 NSTask。

def do_gpg_cmd_nstask cmd
  Dispatch::Queue.concurrent.async do
    fcmd = "--no-tty " + cmd
    task = NSTask.alloc.init
    task.setLaunchPath(@gpg_path)
    task.setArguments(fcmd.split(" ") << nil)

    task.arguments.each {|a| puts "ARG: [#{a}]" }

    inpipe = NSPipe.pipe
    outpipe = NSPipe.pipe
    errpipe = NSPipe.pipe

    task.setStandardOutput(outpipe)
    task.setStandardInput(inpipe)
    task.setStandardError(errpipe)

    output = outpipe.fileHandleForReading
    errput = errpipe.fileHandleForReading
    input = inpipe.fileHandleForWriting

    task.launch

    input.writeData input_text.dataUsingEncoding(NSUTF8StringEncoding)
    input.closeFile

    outdata = output.readDataToEndOfFile
    errdata = errput.readDataToEndOfFile
    output.closeFile
    errput.closeFile
    outstring = NSString.alloc.initWithData(outdata, encoding: NSUTF8StringEncoding)
    errstring = NSString.alloc.initWithData(errdata, encoding: NSUTF8StringEncoding)

    output_text outstring
    logg errstring
  end
end

当我运行它时,我在 Xcode 调试输出中收到此错误。显然,我自己将 ARG 部分输出为超级愚蠢的日志记录。子进程未执行。

ARG: [--no-tty]
ARG: [--clearsign]
ARG: [--local-user]
ARG: [0xC2808780]
ARG: []
2013-03-12 23:27:39.305 GPGBoard[84924:3503] -[NSNull fileSystemRepresentation]: unrecognized selector sent to instance 0x7fff75b05310
*** Dispatch block exited prematurely because of an uncaught exception:
/Users/colin/Library/Developer/Xcode/DerivedData/GPGBoard-bradukgmaegxvmbukhwehepzyxcv/Build/Products/Debug/GPGBoard.app/Contents/Resources/AppDelegate.rb:81:in `block': NSInvalidArgumentException: -[NSNull fileSystemRepresentation]: unrecognized selector sent to instance 0x7fff75b05310 (RuntimeError)

我怀疑这两种方法的问题是相互排斥的:Open3#popen3 问题可能与阻塞读取有关,而 NSTask 问题与管道问题有关。

【问题讨论】:

    标签: cocoa pipe stdout stdin macruby


    【解决方案1】:

    这段代码对我有用,并打印出当前目录中的文件:

    framework "Cocoa"
    task = NSTask.new
    task.launchPath = "/bin/ls"
    task.arguments = ["-l", "-a"]
    stdoutPipe = NSPipe.pipe
    task.standardOutput = stdoutPipe
    task.launch
    data = stdoutPipe.fileHandleForReading.readDataToEndOfFile
    puts NSString.alloc.initWithData data, :encoding => NSASCIIStringEncoding
    

    现在,如果我将 task.arguments = ["-l", "-a"] 替换为 task.arguments = "-l -a".split(" ") &lt;&lt; nil,我会收到以下错误:

    macruby[86209:707] -[NSNull fileSystemRepresentation]: unrecognized selector sent to instance 0x7fff77d6f310

    所以,我认为您的问题是task.setArguments(fcmd.split(" ") &lt;&lt; nil)。将其更改为task.setArguments(fcmd.split(" ")),您应该不再遇到NSNull 问题。

    【讨论】:

    • 感谢您的回答。当我删除它时,我会收到一条消息,说某些东西不在自动区域中。我阅读了更多代码并决定切换回常规样式的哈希(:sym=>:key 而不是 sym::key)作为 NSString#initWithData 的编码参数。现在,行为与完整的 Ruby 版本相同。我单击按钮,它只是挂起。必须强制退出才能摆脱它。
    • 实际上,我仍然使用 Ruby 版本。切换回来后,我实际上可以从命令中获得输出(耶!),但之后它立即挂起。
    • 根据任务的实际执行情况,您可以尝试调用terminate
    • 如果程序在读取后似乎挂起,我将如何调用终止?
    • 我在读取之前将其放入,并在其后放置 5 秒睡眠,以确保程序完成对管道的写入。没有骰子。编辑:Sign 操作现在通常可以工作,但是输出更大的 Encrypt 操作似乎仍然无法工作。
    【解决方案2】:

    问题是管道有一个缓冲区大小。当缓冲区已满时,写入命令会阻塞,直到另一端读取一些数据为新数据腾出空间。

    您的代码首先尝试将所有数据写入命令的标准输入。假设该命令确实读取了一些数据,将一些输出写入标准输出,然后继续从其标准输入读取。如果有很多数据要通过,则命令的标准输出管道的缓冲区有时会变满。该命令会阻塞,直到有人从标准输出管道读取数据。但是,您的 ruby​​ 代码尚未完成将数据写入标准输入,并继续这样做,直到标准输入管道也已满。现在有一个死锁。

    解决方案是同时将数据写入标准输入并从标准输出读取数据,可以是并发的,也可以只是逐块的(块大小不大于管道的缓冲区大小。)

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-05
    • 1970-01-01
    • 2012-01-25
    • 1970-01-01
    • 2011-08-06
    • 1970-01-01
    • 2019-12-18
    相关资源
    最近更新 更多