Git Product home page Git Product logo

shellout's Introduction

🐚 ShellOut

Welcome to ShellOut, a simple package that enables you to easily “shell out” from a Swift script or command line tool.

Even though you can accomplish most of the tasks you need to do in native Swift code, sometimes you need to invoke the power of the command line from a script or tool - and this is exactly what ShellOut makes so simple.

Usage

Just call shellOut(), and specify what command you want to run, along with any arguments you want to pass:

let output = try shellOut(to: "echo", arguments: ["Hello world"])
print(output) // Hello world

You can also easily run a series of commands at once, optionally at a given path:

try shellOut(to: ["mkdir NewFolder", "echo \"Hello again\" > NewFolder/File"], at: "~/CurrentFolder")
let output = try shellOut(to: "cat File", at: "~/CurrentFolder/NewFolder")
print(output) // Hello again

In case of an error, ShellOut will automatically read STDERR and format it nicely into a typed Swift error:

do {
    try shellOut(to: "totally-invalid")
} catch {
    let error = error as! ShellOutError
    print(error.message) // Prints STDERR
    print(error.output) // Prints STDOUT
}

Pre-defined commands

Another way to use ShellOut is by executing pre-defined commands, that enable you to easily perform common tasks without having to construct commands using strings. It also ships with a set of such pre-defined commands for common tasks, such as using Git, manipulating the file system and using tools like Marathon, CocoaPods and fastlane.

Use Git

try shellOut(to: .gitInit())
try shellOut(to: .gitClone(url: repositoryURL))
try shellOut(to: .gitCommit(message: "A scripted commit!"))
try shellOut(to: .gitPush())
try shellOut(to: .gitPull(remote: "origin", branch: "release"))
try shellOut(to: .gitSubmoduleUpdate())
try shellOut(to: .gitCheckout(branch: "my-feature"))

Handle files, folders and symlinks

try shellOut(to: .createFolder(named: "folder"))
try shellOut(to: .createFile(named: "file", contents: "Hello world"))
try shellOut(to: .moveFile(from: "path/a", to: "path/b"))
try shellOut(to: .copyFile(from: "path/a", to: "path/b"))
try shellOut(to: .openFile(at: "Project.xcodeproj"))
try shellOut(to: .readFile(at: "Podfile"))
try shellOut(to: .removeFile(from: "path/a"))
try shellOut(to: .createSymlink(to: "target", at: "link"))
try shellOut(to: .expandSymlink(at: "link"))

For a more powerful and object-oriented way to handle Files & Folders in Swift, check out Files

try shellOut(to: .runMarathonScript(at: "~/scripts/MyScript", arguments: ["One", "Two"]))
try shellOut(to: .updateMarathonPackages())
try shellOut(to: .createSwiftPackage(withType: .executable))
try shellOut(to: .updateSwiftPackages())
try shellOut(to: .generateSwiftPackageXcodeProject())
try shellOut(to: .buildSwiftPackage())
try shellOut(to: .testSwiftPackage())
try shellOut(to: .runFastlane(usingLane: "appstore"))
try shellOut(to: .updateCocoaPods())
try shellOut(to: .installCocoaPods())

Don't see what you're looking for in the list above? You can easily define your own commands using ShellOutCommand. If you've made a command you think should be included among the built-in ones, feel free to open a PR!

Installation

For scripts

  • Install Marathon.
  • Add ShellOut to Marathon using $ marathon add https://github.com/JohnSundell/ShellOut.git.
  • Alternatively, add https://github.com/JohnSundell/ShellOut.git to your Marathonfile.
  • Write your script, then run it using $ marathon run yourScript.swift.

For command line tools

  • Add .package(url: "https://github.com/JohnSundell/ShellOut.git", from: "2.0.0") to your Package.swift file's dependencies.
  • Update your packages using $ swift package update.

Help, feedback or suggestions?

  • Open an issue if you need help, if you found a bug, or if you want to discuss a feature request.
  • Open a PR if you want to make some change to ShellOut.
  • Contact @johnsundell on Twitter for discussions, news & announcements about ShellOut & other projects.

shellout's People

Contributors

finestructure avatar harlanhaskins avatar helje5 avatar hisaac avatar johnsundell avatar krausefx avatar niklassaers avatar pixyzehn avatar stevebarnegren avatar timowaelischidealo avatar yageek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shellout's Issues

Question

How do import ShellOut or even any module within a single executable file in a workspace folder? Current methods require you building the whole project but I just want to run one swift file from command line thats within a folder of my workspace Source folder. When I try "swift myFile.swift" from command line in the directory I get the error

error: no such module 'ShellOut'

Any help here would be nice. Thanks!

Cut a new tag?

Hi. I use ShellOut for my app and this is so useful!😄
I've been using my own fork of ShellOut in order to use the latest master via SwiftPM.
Are you going to push a new tag?

Data races when concurrently running shellOut commands

I've noticed, while trying to add parallel execution to Lite, if you try to execute shellOut on multiple dispatch queues it causes data races inside Process.launchBash. I'm not quite sure how to work around this, but I can try to take a look at a potential fix.

==================
WARNING: ThreadSanitizer: Swift access race (pid=12148)
  Read of size 8 at 0x7b0c0003dc90 by thread T1:
  * #0 Process.launchBash(with:outputHandle:errorHandle:) ShellOut.swift:400 (ShellOut:x86_64+0x445a)
    #1 shellOut(to:arguments:at:outputHandle:errorHandle:) ShellOut.swift:35 (ShellOut:x86_64+0x21ee)
    #2 TestRunner.run(file:) TestRunner.swift:203 (LiteSupport:x86_64+0x27d1b)
    #3 closure #2 in TestRunner.run() TestRunner.swift:90 (LiteSupport:x86_64+0x1d28a)
    #4 partial apply for closure #2 in TestRunner.run() TestRunner.swift (LiteSupport:x86_64+0x1d5b0)
    #5 thunk for @callee_owned () -> (@owned [TestResult]) TestRunner.swift (LiteSupport:x86_64+0x1d72b)
    #6 partial apply for thunk for @callee_owned () -> (@owned [TestResult]) TestRunner.swift (LiteSupport:x86_64+0x1d810)
    #7 closure #1 in ParallelExecutor.addTask(_:) ParallelExecutor.swift:39 (LiteSupport:x86_64+0x5834)
    #8 partial apply for closure #1 in ParallelExecutor.addTask(_:) ParallelExecutor.swift (LiteSupport:x86_64+0x5940)
    #9 thunk for @callee_owned () -> () ParallelExecutor.swift (LiteSupport:x86_64+0x4c3c)
    #10 __wrap_dispatch_group_async_block_invoke <null>:1056912 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x67327)
    #11 _dispatch_client_callout <null>:1056912 (libdispatch.dylib:x86_64+0x1d1e)

  Previous modifying access at 0x7b0c0003dc90 by thread T17:
  * #0 closure #1 in Process.launchBash(with:outputHandle:errorHandle:) ShellOut.swift:364 (ShellOut:x86_64+0xfc2c)
    #1 partial apply for closure #1 in Process.launchBash(with:outputHandle:errorHandle:) ShellOut.swift (ShellOut:x86_64+0xfe2b)
    #2 thunk for @callee_owned (@owned FileHandle) -> () ShellOut.swift (ShellOut:x86_64+0xfec4)
    #3 __33-[NSConcreteFileHandle _monitor:]_block_invoke <null>:1056912 (Foundation:x86_64+0x144ec7)
    #4 _dispatch_client_callout <null>:1056912 (libdispatch.dylib:x86_64+0x1d1e)

  Issue is caused by frames marked with "*".

  Location is heap block of size 40 at 0x7b0c0003dc80 allocated by thread T1:
    #0 malloc <null>:1056944 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x48b3a)
    #1 swift_slowAlloc <null>:1056944 (libswiftCore.dylib:x86_64+0x344478)
    #2 Process.launchBash(with:outputHandle:errorHandle:) ShellOut.swift (ShellOut:x86_64+0x2c13)
    #3 shellOut(to:arguments:at:outputHandle:errorHandle:) ShellOut.swift:35 (ShellOut:x86_64+0x21ee)
    #4 TestRunner.run(file:) TestRunner.swift:203 (LiteSupport:x86_64+0x27d1b)
    #5 closure #2 in TestRunner.run() TestRunner.swift:90 (LiteSupport:x86_64+0x1d28a)
    #6 partial apply for closure #2 in TestRunner.run() TestRunner.swift (LiteSupport:x86_64+0x1d5b0)
    #7 thunk for @callee_owned () -> (@owned [TestResult]) TestRunner.swift (LiteSupport:x86_64+0x1d72b)
    #8 partial apply for thunk for @callee_owned () -> (@owned [TestResult]) TestRunner.swift (LiteSupport:x86_64+0x1d810)
    #9 closure #1 in ParallelExecutor.addTask(_:) ParallelExecutor.swift:39 (LiteSupport:x86_64+0x5834)
    #10 partial apply for closure #1 in ParallelExecutor.addTask(_:) ParallelExecutor.swift (LiteSupport:x86_64+0x5940)
    #11 thunk for @callee_owned () -> () ParallelExecutor.swift (LiteSupport:x86_64+0x4c3c)
    #12 __wrap_dispatch_group_async_block_invoke <null>:1056944 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x67327)
    #13 _dispatch_client_callout <null>:1056944 (libdispatch.dylib:x86_64+0x1d1e)

  Thread T1 (tid=7782461, running) is a GCD worker thread

  Thread T17 (tid=7782485, running) is a GCD worker thread

SUMMARY: ThreadSanitizer: Swift access race ShellOut.swift:400 in Process.launchBash(with:outputHandle:errorHandle:)
==================

How to enter my password?

for example:

  • I need to enter the password when running a command with sudo.
  • I need to enter [y/N] when running a command that needs to be confirmed.
  • ...

How can I do this?

Standard output delay

Hi,
Great tool for swift scripting... I'm seeing an issue where command output is not written to standard output / terminal until the command is done execution even though there were intermediate logs produced by sub-commands.

Is there a way to log/print output to terminal as it is being produced?

Thank you!

I get an error when I execute the pod command

I get an error when I execute the command

let _ = try shellOut(to: .installCocoaPods())
Error: ShellOut encountered an error
Status code: 127
Message: "/bin/bash: pod: command not found"
Output: ""
Program ended with exit code: 1

Running shellOut on concurrentPerform

Hi, I'm trying to use shellOut inside DispatchQueue.concurrentPerform but the script got hang. Can we run shellOut on different thread simultaneously?

ShellOut make error?

I was trying to make Publish, and it seems to stop at ShellOut:

Fetching https://github.com/johnsundell/plot.git
Fetching https://github.com/johnsundell/shellout.git
Fetching https://github.com/johnsundell/codextended.git
Fetching https://github.com/johnsundell/sweep.git
Fetching https://github.com/johnsundell/ink.git
Fetching https://github.com/johnsundell/files.git
https://github.com/johnsundell/shellout.git @ 2.3.0: error: manifest parse error(s):
<module-includes>:29:9: note: in file included from <module-includes>:29:
#import "string.h"
        ^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/usr/include/string.h:180:10: note: in file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/usr/include/string.h:180:
#include "strings.h"
         ^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/usr/include/strings.h:92:10: note: in file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/usr/include/strings.h:92:
#include <string.h>
         ^
/usr/local/include/string.h:25:10: error: 'plist/Node.h' file not found
#include <plist/Node.h>
         ^
<unknown>:0: error: could not build Objective-C module 'Darwin'
make: *** [install] Error 1

Is is a ShellOut problem, or a Publish problem?

Add sample Xcode project?

Running

pod try https://github.com/JohnSundell/ShellOut

should clone the repo and open a sample project, but produces this error:

Unable to find any project in the source files of the Pod

This is because it can't find a sample project.

It would be nice to create a simple command line project which just includes ShellOut and some examples, and/or maybe a playground file.

Can't load framework in Command Line Tool

dyld: Library not loaded: @rpath/ShellOut.framework/Versions/A/ShellOut Referenced from: /Users/MYUSER/Library/Developer/Xcode/DerivedData/MyAppName-ekiclknclczpksfxqtybkeebjynw/Build/Products/Debug/SecurityHelper Reason: image not found (lldb)

Binary with Library is Linked

ShellOut without Marathon

The readme suggests installing Marathon, but the Marathon project says to use SPM instead. Perhaps the ShellOut readme should be updated with SPM instructions instead of Marathon?

How to access ssh key

When i use shellout to exccute git command i got an error like
"
load pubkey "/Users/***/.ssh/id_rsa": Operation not permitted
Host key verification failed.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
"

How to fix it?

Uncaught NSRangeException on waitUntilExit()

Hi everyone. I'm getting a strange error using ShellOut. My app crashes with an NSRangeException on the waitUntilExit() call in the launchBash function (ShellOut:428). This seems to mainly be happening when ShellOut is used in an onAppear or onReceive function using SwiftUI. I am calling ShellOut using syntax let shellOutput = try shellOut(to: command). This seems to work outside of these SwiftUI functions.

*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty NSArray' terminating with uncaught exception of type NSException

Does anyone have an idea what could be going on here? Thanks!

dynamic library

For compatibility with Xcode 11.4 the library needs to become dynamic?

modify package.swift:

.library(name: "ShellOut", type: .dynamic, targets: ["ShellOut"])

How to properly open a terminal editor?

Hello 👋
Given this small piece of code:

import ArgumentParser
import ShellOut


@main
struct MyCliTool: ParsableCommand {

    mutating func run() throws {

        print("Hello, world!")
        try shellOut(to: "nano /tmp/somefile.txt") // or (neo)vim
    }

}

I would expect it to open up nano with the specified file. No such thing happens though:

screenshot

I experienced similar behaviour for other packages and for using Process directly.
I haven't used Swift beforehand and thought I could write my next small little CLI tool in it, this keeps me from doing so though.
Is there any way to properly start a terminal editor with ShellOut or should I search somewhere else?

Thank you :)

Change shell from bash to zsh

Since MacOS now ships with zsh I think it would make sense to change the shell used by ShellOut to match. I was just bitten by an issue where file globbing behaviours were different between the two and debugging it made no sense since it worked when run manually.

Alternatively the shell could be made configurable. Happy to make the change, just wanted to start the discussion first.

localizedDescription for ShellOutError

Hi, Thanks for the helpful library.

I think ShellOutError needs to improve printing.
Currently, ShellOutError prints following.

let error: ShellOutError
print(error)
ShellOutError(terminationStatus: 1, errorData: 32 bytes, outputData: 0 bytes)

I want a more helpful description.
I think to convert to string from errorData.

What do you think?

Async output?

I've skimmed the code and it appears you don't provide any way to get the output of a command as it comes through the pipe. Is this something you'd consider adding? I guess it would provide a "data received" callback argument or something?

inconsistency with builds

In a Swift scripts folder under my project-directory , I have a few swift files that generate build errors (from the error: "statements are not allowed at the top level") when I run "swift build" in the project folder. However when running "marathon run myfile.swift" in the Swift script folder , it runs and generates the proper output.

Error on Linux

With Swift 3.1.1 on Ubuntu, I get the following error:

  • swift test
    Compile Swift Module 'ShellOut' (1 sources)
    /var/lib/jenkins/workspace/ShellOut SPM Linux/Sources/ShellOut.swift:96:60: error: cannot assign to property: 'readabilityHandler' is a get-only property
    outputPipe.fileHandleForReading.readabilityHandler = stdoutHandler
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
    /var/lib/jenkins/workspace/ShellOut SPM Linux/Sources/ShellOut.swift:100:59: error: cannot assign to property: 'readabilityHandler' is a get-only property
    errorPipe.fileHandleForReading.readabilityHandler = stderrHandler
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
    /var/lib/jenkins/workspace/ShellOut SPM Linux/Sources/ShellOut.swift:108:60: error: cannot assign to property: 'readabilityHandler' is a get-only property
    outputPipe.fileHandleForReading.readabilityHandler = nil
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
    /var/lib/jenkins/workspace/ShellOut SPM Linux/Sources/ShellOut.swift:109:59: error: cannot assign to property: 'readabilityHandler' is a get-only property
    errorPipe.fileHandleForReading.readabilityHandler = nil
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
    :0: error: build had 1 command failures

No such file or directory error using "at:" property

Today I tried to use the at property to one of my script, but the result was always:
/bin/bash: line 0: cd: ~/foldername: No such file or directory

So I copied&pasted the example from the README.md file, just to figure out my error, but the result is the same:

import ShellOut

do {
  try shellOut(to: ["mkdir NewFolder", "echo \"Hello again\" > NewFolder/File"])
  let output = try shellOut(to: "cat File", at: "~/NewFolder")
  print(output) // Hello again
}
catch {
  let error = error as! ShellOutError
  print(error.message) // Prints STDERR
  print(error.output) // Prints STDOUT
}

/bin/bash: line 0: cd: ~/NewFolder: No such file or directory.

I'm missing something?
My marathon is update (also ShellOut) and I use it a lot without any others issues :)

Thanks!

Add Compatibility with Commands like "vi"

Please add a feature where you can run interactive commands and not get stuck on a black screen...

A lot of commands require you to put information as the command runs. Some examples are:

  • vi
  • npm init
  • sudo
  • My Project

So please add an option to use interactive commands as well as normal ones.

try shellOut(to: "vi README.md", interactive: true)

This is similar to #35, but with a little more context.

ShellOut "swift package update" not working in some scenarios

I'm working on using Swift in some pre-commit scripts. For some reason when I attempted to run various versions of "swift package update" as a script for git pre-commit I would get a failure.

// swift script in ./.git/hooks/pre-commit
shellOut(to: "swift package update")

produces the following error:

xcrun --sdk macosx --show-sdk-path
xcrun --sdk macosx --show-sdk-platform-path
xcrun --find clang
sandbox-exec -p '(version 1)
(deny default)
(import "system.sb")
(allow file-read*)
(allow process*)
(allow sysctl*)
(allow file-write*
    (regex #"^/private/var/tmp/org\.llvm\.clang.*")
    (regex #"^/var/folders/h8/2l1nf2ss5qqfblyby0tnl7sm0000gn/T/org\.llvm\.clang.*")
    (regex #"^/private/var/folders/h8/2l1nf2ss5qqfblyby0tnl7sm0000gn/T/org\.llvm\.clang.*")
    (regex #"^/private/var/folders/h8/2l1nf2ss5qqfblyby0tnl7sm0000gn/C/org\.llvm\.clang.*")
)
' /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc --driver-mode=swift -L /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4 -lPackageDescription -suppress-warnings -swift-version 4 -I /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk ~redacted~/b4/cache/SwiftPreCommitHooks/Package.swift -fileno 5
sandbox-exec -p '(version 1)
(deny default)
(import "system.sb")
(allow file-read*)
(allow process*)
(allow sysctl*)
(allow file-write*
    (regex #"^/private/var/tmp/org\.llvm\.clang.*")
    (regex #"^/var/folders/h8/2l1nf2ss5qqfblyby0tnl7sm0000gn/T/org\.llvm\.clang.*")
    (regex #"^/private/var/folders/h8/2l1nf2ss5qqfblyby0tnl7sm0000gn/T/org\.llvm\.clang.*")
    (regex #"^/private/var/folders/h8/2l1nf2ss5qqfblyby0tnl7sm0000gn/C/org\.llvm\.clang.*")
)
' /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc --driver-mode=swift -L /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4 -lPackageDescription -suppress-warnings -swift-version 4 -I /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk /var/folders/h8/2l1nf2ss5qqfblyby0tnl7sm0000gn/T/TemporaryFile.TJUETQ.swift -fileno 6
git -C /Users/cavellebenjamin/Development/b4/cache/SwiftPreCommitHooks/.build/repositories/PathKit.git--1865447967743163058 rev-parse --is-bare-repositorygit -C ~redacted~/b4/cache/SwiftPreCommitHooks/.build/repositories/SwiftCLI.git-1996021284977509885 rev-parse --is-bare-repositorygit -C ~redacted~/b4/cache/SwiftPreCommitHooks/.build/repositories/ShellOut.git--4208804319273938067 rev-parse --is-bare-repository


git -C /Users/cavellebenjamin/Development/b4/cache/SwiftPreCommitHooks/.build/repositories/ShellOut.git--4208804319273938067 remote update -p
git -C /Users/cavellebenjamin/Development/b4/cache/SwiftPreCommitHooks/.build/repositories/SwiftCLI.git-1996021284977509885 remote update -p
Updating https://github.com/JohnSundell/ShellOut.git
Updating https://github.com/jakeheis/SwiftCLI.git
Updating https://github.com/kylef/PathKit.git
git -C ~redacted~/b4/cache/SwiftPreCommitHooks/.build/repositories/PathKit.git--1865447967743163058 remote update -p
error: terminated(128): git -C ~redacted~/b4/cache/SwiftPreCommitHooks/.build/repositories/PathKit.git--1865447967743163058 remote update -p output:

Appears to correct itself if I add Environment variables to the script that changes the PATH variable.

let updated = try bash( 
    command: .swiftPackageUpdate(),
    at: "\(FileManager.default.homeDirectoryForCurrentUser.path)/Development/b4/cache/SwiftPreCommitHooks",
    env: ["PATH":"/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"]
"PATH":"/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"]
  )

thoughts on a pull request? or am I overthinking the situation and should use shellOut as is with additional arguments?

New Async/Streaming Output API.

As part of #32 we've talked about adding a new public API for streaming output.

One suggestion would be:

public func shellOut(to command: String,
                     arguments: [String] = [],
                     at path: String = ".",
                     output: (String) -> Void) throws {
   ...
}

/// Usage
shellOut(to: "docker build") { output in
  /// This block will be called multiple times
  print(output)
}

We'd have to check if the output closure would need to be escaping or not. Ideally it wouldn't need to be, but I'm not sure if that is achievable.

What do other people think?

Closes Filehandle.standardOutput

Hi John,

I'm not familiar with the FileHandle api and usage, so forgive me if I'm using it incorrectly here!

I'm trying to 'shell out' to a command that prints asynchronously, and I'd like to see that output in realtime, just as if I'd called it from Terminal.

I can pass in FileHandle.standardOutput to achieve this, like so:

try shellOut(to: "some command", outputHandle: FileHandle.standardOutput)

It works great, but I can't see any output, for instance from print() after ShellOut returns.

It looks like this is because ShellOut calls outputHandle?.closeFile()

Is there a currently supported way to achieve this? If not, would you accept a PR to add this functionality?

FileHandle crash if you run multiple shellout calls

Hi 👋
I am developing this tool https://github.com/f-meloni/Rocket, and i'm using ShellOut for some of the available steps.
I would like to redirect the output of each step on the standard output, but if i do it this is what happens.

ShellOut method call:

func launchScript(withContent content: String) throws {
        try shellOut(to: ["export VERSION=\(version)", content], outputHandle: FileHandle.standardOutput)
}

Example steps:

steps:
  - script:
      content: echo "releasing $VERSION"
  - script:
      content: echo "releasing $VERSION"

Now if i run it with for example with version 0.3.0
This is what happens:

releasing 0.3.0
2018-12-05 08:10:03.537 Rocket[6079:92461] *** Terminating app due to uncaught exception 'NSFileHandleOperationException', reason: '*** -[_NSStdIOFileHandle writeData:]: unknown error'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff36a9ae65 __exceptionPreprocess + 256
	1   libobjc.A.dylib                     0x00007fff62af6720 objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff36a9ac97 +[NSException raise:format:] + 201
	3   Foundation                          0x00007fff38d701f2 -[NSConcreteFileHandle writeData:] + 74
	4   Rocket                              0x0000000103f484fc $SSo6NSTaskC8ShellOutE10launchBash33_839723A297212BDF262C1834C3E29C1FLL4with12outputHandle05errorN0S2S_So06NSFileN0CSgAKtKFyAJcfU_yycfU_ + 332
	5   Rocket                              0x0000000103f49a05 $SSo6NSTaskC8ShellOutE10launchBash33_839723A297212BDF262C1834C3E29C1FLL4with12outputHandle05errorN0S2S_So06NSFileN0CSgAKtKFyAJcfU_yycfU_TA + 21
	6   Rocket                              0x0000000103f4855d $SIeg_IeyB_TR + 45
	7   libdispatch.dylib                   0x00007fff63b75d53 _dispatch_call_block_and_release + 12
	8   libdispatch.dylib                   0x00007fff63b76dcf _dispatch_client_callout + 8
	9   libdispatch.dylib                   0x00007fff63b7d124 _dispatch_lane_serial_drain + 618
	10  libdispatch.dylib                   0x00007fff63b7dbdc _dispatch_lane_invoke + 388
	11  libdispatch.dylib                   0x00007fff63b86090 _dispatch_workloop_worker_thread + 603
	12  libsystem_pthread.dylib             0x00007fff63db663c _pthread_wqthread + 409
	13  libsystem_pthread.dylib             0x00007fff63db6435 start_wqthread + 13
)
libc++abi.dylib: terminating with uncaught exception of type NSException
[1]    6079 abort      ./.build/x86_64-apple-macosx10.10/debug/Rocket 0.3.0

The problem is the standard file handle close call solved on #23 (i tried this and it solves the problem work)

Is there any known workaround that i can use to redirect the output on the stdoutput in the meantime while that PR or maybe #32 gets reviewed an (if they are ok) merged? :)

Cannot build SwiftUI preview

I have a project with SwiftUI and since I introduced ShellOut via Swift Package Manager the canvas preview does not build although the app compiles fine if you run it on a simulator or device. It's just the preview that fails when building with the following error:

SchemeBuildError: Failed to build the scheme "MyApp"

use of undeclared type 'Process'

Compile /Users/Pau/Library/Developer/Xcode/DerivedData/MyApp-ctrdkueooaaxgiatjsycywzfdcql/SourcePackages/checkouts/ShellOut/Sources/ShellOut.swift:
/Users/Pau/Library/Developer/Xcode/DerivedData/MyApp-ctrdkueooaaxgiatjsycywzfdcql/SourcePackages/checkouts/ShellOut/Sources/ShellOut.swift:34:14: error: use of undeclared type 'Process'
    process: Process = .init(),

I believe that the problem may be that Process is only available on macOS, not on iOS. I'm using ShellOut as a dependency of SwiftyMocky so when I run the tests on my mac it works fine but for some reason when Xcode compiles SwiftUI previews it's also compiling the test target and the dependencies and tries to compile ShellOut in iOS

Incomplete standard output

I have come across a scenario when not all standard output is returned. This seems to be happening on a non zero exit code, successful termination may or may not be affected.

Background
I was using shellOut to execute fastlane and it was failing for version requirements not being read.

Investigation
I did some tinkering and it seems to be related to how this function reads the output.

private extension Process {
    @discardableResult func launchBash(with command: String, outputHandle: FileHandle? = nil, errorHandle: FileHandle? = nil) throws -> String {
        launchPath = "/bin/bash"
        arguments = ["-c", command]

        var outputData = Data()
        var errorData = Data()

        let outputPipe = Pipe()
        standardOutput = outputPipe

        let errorPipe = Pipe()
        standardError = errorPipe

        #if !os(Linux)
        outputPipe.fileHandleForReading.readabilityHandler = { handler in
            let data = handler.availableData
            outputData.append(data)
            outputHandle?.write(data)
        }

        errorPipe.fileHandleForReading.readabilityHandler = { handler in
            let data = handler.availableData
            errorData.append(data)
            errorHandle?.write(data)
        }
        #endif

        launch()

        #if os(Linux)
        outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
        errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
        #endif

        waitUntilExit()

        outputHandle?.closeFile()
        errorHandle?.closeFile()

        #if !os(Linux)
        outputPipe.fileHandleForReading.readabilityHandler = nil
        errorPipe.fileHandleForReading.readabilityHandler = nil
        #endif

        if terminationStatus != 0 {
            throw ShellOutError(
                terminationStatus: terminationStatus,
                errorData: errorData,
                outputData: outputData
            )
        }

        return outputData.shellOutput()
    }
}

This is the output when using the default implementation

[19:08:21]: �[32m-------------------------------------------------�[0m
[19:08:21]: �[32m--- Step: Verifying required fastlane version ---�[0m
[19:08:21]: �[32m-------------------------------------------------�[0m

I noticed the linux only variation and thought I would try it out on macos, I hacked the function as follows.

private extension Process {
    @discardableResult func launchBash(with command: String, outputHandle: FileHandle? = nil, errorHandle: FileHandle? = nil) throws -> String {
        launchPath = "/bin/bash"
        arguments = ["-c", command]

        var outputData = Data()
        var errorData = Data()

        let outputPipe = Pipe()
        standardOutput = outputPipe

        let errorPipe = Pipe()
        standardError = errorPipe

//        #if !os(Linux)
//        outputPipe.fileHandleForReading.readabilityHandler = { handler in
//            let data = handler.availableData
//            outputData.append(data)
//            outputHandle?.write(data)
//        }

//        errorPipe.fileHandleForReading.readabilityHandler = { handler in
//            let data = handler.availableData
//            errorData.append(data)
//            errorHandle?.write(data)
//        }
//        #endif

        launch()

//        #if os(Linux)
        outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
        errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
//        #endif

        waitUntilExit()

        outputHandle?.closeFile()
        errorHandle?.closeFile()

        #if !os(Linux)
        outputPipe.fileHandleForReading.readabilityHandler = nil
        errorPipe.fileHandleForReading.readabilityHandler = nil
        #endif

        if terminationStatus != 0 {
            throw ShellOutError(
                terminationStatus: terminationStatus,
                errorData: errorData,
                outputData: outputData
            )
        }

        return outputData.shellOutput()
    }
}

Which produces the following output

[19:04:50]: �[32m-------------------------------------------------�[0m
[19:04:50]: �[32m--- Step: Verifying required fastlane version ---�[0m
[19:04:50]: �[32m-------------------------------------------------�[0m
�[31m
[!] The Fastfile requires a fastlane version of >= 2.47.0. You are on 1.91.0. Please update using `sudo gem update fastlane`.�[0m

Assuming I am not using this wrong it looks like the output is getting cut off early.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.