0

コマンドを実行し、データをそのstdinにフィードし、そのstdoutから読み取ろうとしています。MacRubyで公開されているNSTaskだけでなくRubyのOpen3#popen3を使ってみました。私が書いているプログラムのソースはここにあります。私はこれをXcodeとMacRubyで行っています。

ここにいくつかの選択コードがあります:

エントリポイント。2つの方法を簡単に切り替えることができます。

def do_gpg_cmd cmd
  do_gpg_cmd_nstask cmd
end

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  

NSTaskを使用したCocoaの方法。

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

問題は、パイプにバッファ サイズがあることです。バッファーがいっぱいになると、もう一方の端がデータを読み取って新しいデータ用のスペースを作るまで、書き込みコマンドはブロックされます。

コードは最初にすべてのデータをコマンドの stdin に書き込もうとします。コマンドが何らかのデータを読み取り、出力を stdout に書き込み、stdin からの読み取りを続行すると仮定します。通過するデータが大量にある場合、コマンドの stdout パイプのバッファーがいっぱいになることがあります。コマンドは、誰かが stdout パイプからデータを読み取るまでブロックします。ただし、Ruby コードは標準入力へのデータの書き込みをまだ完了しておらず、標準入力パイプがいっぱいになるまで書き込みを続けます。現在、デッドロックが発生しています。

解決策は、データを stdin に書き込み、同時に stdout からデータを読み取ることです。同時に、または単純にブロックごとに (ブロック サイズがパイプのバッファー サイズより大きくならないようにします)。

于 2013-09-11T17:08:23.010 に答える