在 Swift 中,如何查看 Process() 传递给 shell 的字符串?
In Swift, how can I see the string that Process() passes to the shell?
我正在尝试使用 Process()
在 Swift 中启动任务,但我需要查看发送到 shell 的内容以进行调试。
我正在尝试发送以下命令:
gswin64c.exe -q -dNODISPLAY -dNOSAFER -c "(input.pdf) (r) file runpdfbegin pdfpagecount = quit"
如果我在使用 UNIX shell(bash、zsh 等)的环境中 运行 使用完全相同的命令,它 运行 没问题。然而,在 Windows 中使用 cmd.exe,它失败了,给出了以下错误信息:
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 发送到 shell 的内容。
但是,我能够解决眼前的问题。发送代码到 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([=13=])}) {
return arg
}
var quoted = "\""
var unquoted = arg.unicodeScalars
while !unquoted.isEmpty {
guard let firstNonBackslash = unquoted.firstIndex(where: { [=13=] != "\" }) 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: " ")
}
你可以 copy-paste 把它变成游乐场,和它一起玩。结果你的字符串变成了:
"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 机器)
我正在尝试使用 Process()
在 Swift 中启动任务,但我需要查看发送到 shell 的内容以进行调试。
我正在尝试发送以下命令:
gswin64c.exe -q -dNODISPLAY -dNOSAFER -c "(input.pdf) (r) file runpdfbegin pdfpagecount = quit"
如果我在使用 UNIX shell(bash、zsh 等)的环境中 运行 使用完全相同的命令,它 运行 没问题。然而,在 Windows 中使用 cmd.exe,它失败了,给出了以下错误信息:
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 发送到 shell 的内容。
但是,我能够解决眼前的问题。发送代码到 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([=13=])}) {
return arg
}
var quoted = "\""
var unquoted = arg.unicodeScalars
while !unquoted.isEmpty {
guard let firstNonBackslash = unquoted.firstIndex(where: { [=13=] != "\" }) 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: " ")
}
你可以 copy-paste 把它变成游乐场,和它一起玩。结果你的字符串变成了:
"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 机器)