[swift-corelibs-dev] Better integration with UNIX tools

Nick Keets nick.keets at gmail.com
Mon Nov 20 06:37:43 CST 2017


Looking at what Python (subprocess) and Go (os.exec) do, it looks like they
agree on the following:
 - executable and arguments are merged in one array
 - they don't require full path for the executable
 - they don't expand tildes
 - blocking calls are the default
 - they are more explicit about stdin, stdout, stderr

Some example scenarios based on that, with possible swift code:

1) Run a command and ignore output

Python:
    subprocess.run(["sleep", "1"], stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)

Go:
    cmd := exec.Command("sleep", "1")
    err := cmd.Run()

Possible Swift:
    try Process.run(["sleep", "1"])


2) Run a command and capture stdout

Python:
    out = subprocess.check_output(["ls", "-l"])

Go:

    cmd := exec.Command("ls", "-l")
    out, err := cmd.Output()

Possible Swift:

    let proc = try Process.run(["ls", "-l"])
    let out = proc.stdout.read() // proc.stdout is OutputStream, assumes
read() exists and returns Data
    // stderr available at proc.stderr


3) Run a command and capture both stdout and stder together

Python:
    out = subprocess.check_output(["ls", "-l"], stderr=subprocess.STDOUT)

Go:
    cmd := exec.Command("ls", "-l")
    out, err := cmd.CombinedOutput()

Possible Swift:
    let proc = try Process.run(["ls", "-l"], combinedOutput: true)
    let out = proc.stdout.read()


4) Shell out

Python:
    subprocess.check_output(["ls", "-l"], stderr=subprocess.STDOUT,
shell=True)

Go:
    cmd := exec.Command("sh", "-c", "ls -l")
    out, err := cmd.CombinedOutput()

Possible Swift:
    let proc = try Process.run(["sh", "-c", "ls -l"], combinedOutput: true)
    let out = proc.stdout.read()


5) Pipe to stdin

Python:
    p = subprocess.Popen(["wc"], stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
    p.stdin.write(b'blah')
    p.stdin.close()
    out = p.stdout.read()

Go:
    cmd := exec.Command("wc")
    stdin, err := cmd.StdinPipe()
    io.WriteString(stdin, "blah")
    out = cmd.CombinedOutput()

Possible Swift:
    let stdin = InputStream(data: "blah".data(using: .utf8))
    let proc = try Process.run(["wc"], stdin: stdin, combinedOutput: true)
    let out = proc.stdout.read()

6) Async

Python:
    p = subprocess.Popen(["sleep", "5"])
    p.wait()

Go:
    cmd := exec.Command("sleep", "5")
    err := cmd.Start()
    err2 := cmd.Wait()

Possible Swift:
    let proc = Process(["sleep", "5"])
    try proc.start()
    try proc.wait()


On Fri, Nov 17, 2017 at 9:34 PM, Tony Parker via swift-corelibs-dev <
swift-corelibs-dev at swift.org> wrote:

> Hi Abhi,
>
> It does seem like there is a possibility of some better convenience API
> here.
>
> Any ideas on what form it would take? A class method on Process that
> returns the output, maybe?
>
> - Tony
>
> On Nov 16, 2017, at 3:34 PM, Abhi Beckert via swift-corelibs-dev <
> swift-corelibs-dev at swift.org> wrote:
>
> Swift is a great shell scripting language except for it's lack of any API
> to execute UNIX commands. Compare these two shell scripts:
>
> #!/usr/bin/php
> <?
>
> $files = `find ~/Desktop -name *.png`;
>
> foreach (explode("\n", $files) as $file) {
>   // do something with $file
> }
>
>
> -
>
> #!/usr/bin/swift
>
> import Foundation
>
> let process = Process()
> process.launchPath = "/usr/bin/find"
> process.arguments = [
>   NSString(string:"~/Desktop").expandingTildeInPath,
>   "-name",
>   "*.png"
> ]
>
> let output = Pipe()
> process.standardOutput = output
>
> process.launch()
>
> let files: String
> if let filesUtf8 = NSString(data: output.fileHandleForReading.readDataToEndOfFile(),
> encoding: String.Encoding.utf8.rawValue) {
>   files = filesUtf8 as String
> } else {
>   files = NSString(data: output.fileHandleForReading.readDataToEndOfFile(),
> encoding: String.Encoding.isoLatin1.rawValue) as NSString! as String
> }
>
> files.enumerateLines { file, _ in
>   // do something with file
> }
>
>
> It's a contrived example, I could have used NSFileManager, but I run into
> this all the time integrating with more complex tools such as rsync.
>
> Adding my own high level wrapper around the Process command isn't an
> option since there is no good way to import code from another file when
> executing swift asa shell script. All your code needs to be in one file.
>
> - Abhi
> _______________________________________________
> swift-corelibs-dev mailing list
> swift-corelibs-dev at swift.org
> https://lists.swift.org/mailman/listinfo/swift-corelibs-dev
>
>
>
> _______________________________________________
> swift-corelibs-dev mailing list
> swift-corelibs-dev at swift.org
> https://lists.swift.org/mailman/listinfo/swift-corelibs-dev
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-corelibs-dev/attachments/20171120/456cbdbc/attachment.html>


More information about the swift-corelibs-dev mailing list