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

Tony Parker anthony.parker at apple.com
Mon Nov 27 17:50:37 CST 2017


Hi Nick,

Thanks for this survey of other languages, it’s very useful.

I think if we were to add something, I would prefer to keep it simple. Just one class method on Process which is a fairly straightforward wrapper of the other functionality already there.

I was thinking of perhaps a completion handler version, with the expectation that once async/await style completions land it would be pretty easy to use in a kind of straight-line mechanism.

- Tony

> On Nov 20, 2017, at 4:37 AM, Nick Keets <nick.keets at gmail.com> wrote:
> 
> 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 <mailto: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 <mailto: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 <mailto:swift-corelibs-dev at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-corelibs-dev <https://lists.swift.org/mailman/listinfo/swift-corelibs-dev>
> 
> _______________________________________________
> swift-corelibs-dev mailing list
> swift-corelibs-dev at swift.org <mailto:swift-corelibs-dev at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-corelibs-dev <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/20171127/449037d2/attachment.html>


More information about the swift-corelibs-dev mailing list