Swift: буферизует очень длинный аргумент `Process` с помощью pipe

Эта проблема

Я пытаюсь вызвать функцию Python из моего исполняемого файла Swift.

Это работало отлично, пока аргументы не стали слишком длинными, и я начал получать ошибку:

неисследованное исключение NSInternalInconsistencyException
причина: "Не удалось posix_spawn: ошибка 7"

что по сути означает, что данные, которые я отправляю для обработки в коде python (мой аргумент), слишком длинные.

Последняя альтернатива

Наиболее очевидным решением является запись данных в файл и отправка скрипту пути к этому файлу, содержащему данные. Это идет с очевидными ограничениями производительности.

Потенциально лучшая альтернатива

Это то, что мне нужно помочь: я подумал, что можно было бы буферизовать эти данные, а не отправлять их все одним куском.

Я попытался исследовать это, используя множество различных комбинаций ключевых слов, и за последний день читал много статей в Интернете, но ни одна из них не ответила на мой вопрос: можно ли буферизовать данные, чтобы обойти ядро? ARG_MAX ограничение около 260000 байт с использованием Processэс и Pipes?

Код

Поскольку это всегда может помочь увидеть код, который я сейчас использую для вызова функций Python, вот что у меня есть:

@discardableResult
public static func shell(_ args: String...) -> String {

    let task = Process()
    task.launchPath = "/usr/bin/env"
    task.arguments = args
    let pipe = Pipe()
    task.standardOutput = pipe
    task.launch()
    task.waitUntilExit()

    // return task.terminationStatus
    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output: String = String(data: data, encoding: String.Encoding.utf8)!

    return output
}   
//  public static func shell(_ args: String...) -> String {}

И чуть позже:

//
//  Call classifier on data
//
let pyStcriptPath = "\(Bundle.main.bundlePath)/Classification/****Classify.py"

//
// The fonction I'm calling here, 'shell', is the one defined just above in my question
//
let pyResult = shell("python", pyStcriptPath, "\(featureVector)", "\(signalType)")

1 ответ

Вместо этого вы можете отправить свою большую полезную нагрузку, используя канал на стандартный ввод скрипта. Предполагая, конечно, только один аргумент вызывает вашу проблему (т. Е. Все остальное, что вы можете продолжать отправлять, используя обычные аргументы командной строки).

Например:

func shell(stdin input: String, _ args: String...) -> String {
    let task = Process()
    task.launchPath = "/usr/bin/env"
    task.arguments = args

    let inPipe = Pipe()
    task.standardInput = inPipe
    inPipe.fileHandleForWriting.write(input.data(using: .utf8)!)
    inPipe.fileHandleForWriting.closeFile()

    let outPipe = Pipe()
    task.standardOutput = outPipe

    task.launch()
    task.waitUntilExit()

    let data = outPipe.fileHandleForReading.readDataToEndOfFile()
    let output = String(data: data, encoding: .utf8)!

    return output
}

Быстрый тест:

func bigString() -> String {
    var str = ""
    for i in 0..<50_000 {
        str.append("\(i % 10)")
    }
    return str
}

let script = "/PATH/TO/SCRIPT/echo.py"
let input = bigString()
let result = shell(stdin: input, "python", script, "some arg")
print(result)

печатает:

ARG: ABC
STDIN: 012345678901234567890123...

где echo.py скрипт просто:

import sys
print "ARG:", sys.argv[1]
print "STDIN:", sys.stdin.read()
Другие вопросы по тегам