我试图使用Process()在Swift中启动一个任务,但我需要查看为了调试目的发送到shell的内容.

我正在try 发送以下命令:

gswin64c.exe -q -dNODISPLAY -dNOSAFER -c "(input.pdf) (r) file runpdfbegin pdfpagecount = quit"

如果我在使用UNIX shell(bash、zsh等)的环境中运行相同的命令,那么它运行良好.在Windows中使用cmd.但是,它失败,并给出以下错误消息:

Error: /undefined in ".

我怀疑Swift 正在插入斜杠作为"转义"字符.有没有办法查看Swift发送到shell的字符串?

以下是一个示例:

import Foundation

let inputFile = URL(fileURLWithPath: "input.pdf")
let task = Process()

// In MacOS or Linux, obviously, we would use the appropriate path to 'gs'.
// Use gswin32c.exe if you have the 32-bit version of Ghostscript in Windows.
task.executableURL = URL(fileURLWithPath: #"C:\Program Files\gs\gs9.56.1\bin\gswin64c.exe"#)

// The following works when the shell is bash, zsh, or similar, but not with cmd
task.arguments = ["-q",
                  "-dNODISPLAY",
                  "-dNOSAFER",
                  "-c",
                  "\"(\(inputFile.path)) (r) file runpdfbegin pdfpagecount = quit\""]

let stdout = Pipe()
let stderr = Pipe()
task.standardOutput = stdout
task.standardError = stderr

do {
    try task.run()
} catch {
    print(error)
    exit(1)
}

task.waitUntilExit()

extension String {
    init?(pipe: Pipe) {
        guard let data = try? pipe.fileHandleForReading.readToEnd() else {
            return nil
        }
        
        guard let result = String(data: data, encoding: .utf8) else {
            return nil
        }
        
        self = result
    }
}

if let stdoutText = String(pipe: stdout) {
    print(stdoutText)
}

if let stderrText = String(pipe: stderr) {
    print(stderrText)
}

接下来,该命令是否可以用Swift编写,以便正确地传递给GhostScript?


跟进:

似乎没有一种直接的方法可以看出Swift 向壳牌发送了什么.

然而,我还是解决了眼前的问题.似乎是将代码发送到Windows命令shell的消毒剂在空格前插入了斜杠.我可以通过删除PostScript指令两侧的引号(事实证明它们不是必需的)并将每个元素放置在数组的一个单独成员中来解决这个问题:

task.arguments = [ "-q",
               "-dNODISPLAY",
               "-dNOSAFER",
               "-c",
               "(\(inputFile.path))",
               "(r)",
               "file",
               "runpdfbegin",
               "pdfpagecount",
               "=",
               "quit" ]

或者,如果您希望看到整个工作示例:

import Foundation

let inputFile = URL(fileURLWithPath: "input.pdf")
let task = Process()

// In MacOS or Linux, obviously, we would use the appropriate path to 'gs'.
// Use gswin32c.exe if you have the 32-bit version of Ghostscript in Windows.
task.executableURL = URL(fileURLWithPath: #"C:\Program Files\gs\gs9.56.1\bin\gswin64c.exe"#)

print(inputFile.path)

task.arguments = [ "-q",
                   "-dNODISPLAY",
                   "-dNOSAFER",
                   "-c",
                   "(\(inputFile.path))",
                   "(r)",
                   "file",
                   "runpdfbegin",
                   "pdfpagecount",
                   "=",
                   "quit" ]

let stdout = Pipe()
let stderr = Pipe()
task.standardOutput = stdout
task.standardError = stderr

do {
    try task.run()
} catch {
    print(error)
    exit(1)
}

task.waitUntilExit()

extension String {
    init?(pipe: Pipe) {
        guard let data = try? pipe.fileHandleForReading.readToEnd() else {
            return nil
        }
        
        guard let result = String(data: data, encoding: .utf8) else {
            return nil
        }
        
        self = result
    }
}

if let stdoutText = String(pipe: stdout) {
    print(stdoutText)
}

if let stderrText = String(pipe: stderr) {
    print(stderrText)
}

推荐答案

在判断了swift-corelibs-foundation中的代码之后,我想我找到了它是如何修改Windows的参数的.

Process.run中,它首先构造了一个command: [String](Line 495):

    var command: [String] = [launchPath]
    if let arguments = self.arguments {
      command.append(contentsOf: arguments)
    }

在你的情况下,应该是:

let command = [#"C:\Program Files\gs\gs9.56.1\bin\gswin64c.exe"#, "-q",
              "-dNODISPLAY",
              "-dNOSAFER",
              "-c",
              "\"(\(inputFile.path)) (r) file runpdfbegin pdfpagecount = quit\""]

然后在完成一系列代码后,它调用quoteWindowsCommandLine为Windows shell(Line 656)创建一个命令:

    try quoteWindowsCommandLine(command).withCString(encodedAs: UTF16.self) { wszCommandLine in
      try FileManager.default._fileSystemRepresentation(withPath: workingDirectory) { wszCurrentDirectory in
        try szEnvironment.withCString(encodedAs: UTF16.self) { wszEnvironment in
          if !CreateProcessW(nil, UnsafeMutablePointer<WCHAR>(mutating: wszCommandLine),

quoteWindowsCommandLine被宣布为here(为了简洁起见,我删除了注释):

private func quoteWindowsCommandLine(_ commandLine: [String]) -> String {
    func quoteWindowsCommandArg(arg: String) -> String {
        if !arg.contains(where: {" \t\n\"".contains($0)}) {
            return arg
        }
        var quoted = "\""
        var unquoted = arg.unicodeScalars

        while !unquoted.isEmpty {
            guard let firstNonBackslash = unquoted.firstIndex(where: { $0 != "\\" }) else {
                let backslashCount = unquoted.count
                quoted.append(String(repeating: "\\", count: backslashCount * 2))
                break
            }
            let backslashCount = unquoted.distance(from: unquoted.startIndex, to: firstNonBackslash)
            if (unquoted[firstNonBackslash] == "\"") {
                quoted.append(String(repeating: "\\", count: backslashCount * 2 + 1))
                quoted.append(String(unquoted[firstNonBackslash]))
            } else {
                quoted.append(String(repeating: "\\", count: backslashCount))
                quoted.append(String(unquoted[firstNonBackslash]))
            }
            unquoted.removeFirst(backslashCount + 1)
        }
        quoted.append("\"")
        return quoted
    }
    return commandLine.map(quoteWindowsCommandArg).joined(separator: " ")
}

你可以把它复制粘贴到操场上,然后玩.结果你的绳子变成了:

"C:\Program Files\gs\gs9.56.1\bin\gswin64c.exe" -q -dNODISPLAY -dNOSAFER -c "\"(/currentdir/input.pdf) (r) file runpdfbegin pdfpagecount = quit\""

显然,最后一个论点不需要在Windows上引用.quoteWindowsCommandLine已经为你报价了.如果你只是说:

let command = [#"C:\Program Files\gs\gs9.56.1\bin\gswin64c.exe"#, "-q",
              "-dNODISPLAY",
              "-dNOSAFER",
              "-c",
              "(\(inputFile.path)) (r) file runpdfbegin pdfpagecount = quit"]
print(quoteWindowsCommandLine(command))

不引用最后一个论点似乎也适用于macOS.

另一个错误是使用inputFile.path,它总是生成/的路径(参见this).您应该使用URL的"文件系统表示法":

inputFile.withUnsafeFileSystemRepresentation { pointer in
    task.arguments = ["-q",
                      "-dNODISPLAY",
                      "-dNOSAFER",
                      "-c",
                      "(\(String(cString: pointer!)) (r) file runpdfbegin pdfpagecount = quit"]
}

然后它似乎产生了一些看起来正确的东西:

"C:\Program Files\gs\gs9.56.1\bin\gswin64c.exe" -q -dNODISPLAY -dNOSAFER -c "(/currentdir/input.pdf) (r) file runpdfbegin pdfpagecount = quit"

(我没有Windows电脑)

Swift相关问答推荐

在列表项内的NavigationLink中的按钮无法正常工作

@ MainActor + Combine不一致的编译器错误

SwiftUI同心圆,通过逐渐出现来显示进度

Swift通过设置变量B自动设置变量A的值

减小SF符号的边框宽度

Xcode 15 Beta中如何使用@Observable?

从一个范围减go 多个范围

如何在 SceneKit 上制作毛玻璃效果

找不到目标AAA的 SPM 工件 - 仅限 Xcode 13.3

VStack SwiftUI 中的动态内容高度

单元测试和私有变量

将项目引用添加到 Swift iOS XCode 项目并调试

如何为多个类 Swift 进行扩展

Swift中switch 盒的详尽条件

如何在 Swift 中将 UILabel 居中?

如何使用 swift 将 Images.xcassets 中的图像加载到 UIImage 中

在 unwind segue 之后执行 push segue

枚举大小写的原始值必须是文字

滑动侧边栏菜单 IOS 8 Swift

如何在 Swift 中从字符串创建类的实例