0

我正在尝试执行一个命令,将数据提供给它的标准输入,并从它的标准输出中读取。我尝试使用通过 MacRuby 公开的 Ruby 的 Open3#popen3 以及 NSTask。我正在编写的程序的源代码可在此处获得。我在 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

在这种方法中,应用程序会冻结(我正在通过单击应用程序中的 Sign 按钮进行测试,该按钮运行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 问题与管道问题有关。

4

2 回答 2

3

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

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(" ") << nil我收到以下错误:

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

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

于 2013-03-13T17:50:51.760 回答
1

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

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

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

于 2013-09-11T17:08:23.010 回答