SwiftShell - A Swift framework for shell scripting.

Overview

Run shell commands | Parse command line arguments | Handle files and directories


Swift 5.1 - 5.3 | Swift 4 | Swift 3 | Swift 2

SwiftShell logo

Platforms Swift Package Manager Carthage compatible Twitter: @nottoobadsw

SwiftShell

A library for creating command-line applications and running shell commands in Swift.

Features

  • run commands, and handle the output.
  • run commands asynchronously, and be notified when output is available.
  • access the context your application is running in, like environment variables, standard input, standard output, standard error, the current directory and the command line arguments.
  • create new such contexts you can run commands in.
  • handle errors.
  • read and write files.

See also

Table of Contents

Example

Print line numbers

#!/usr/bin/env swiftshell

import SwiftShell

do {
	// If there is an argument, try opening it as a file. Otherwise use standard input.
	let input = try main.arguments.first.map {try open($0)} ?? main.stdin

	input.lines().enumerated().forEach { (linenr,line) in 
		print(linenr+1, ":", line) 
	}

	// Add a newline at the end.
	print("")
} catch {
	exit(error)
}

Launched with e.g. cat long.txt | print_linenumbers.swift or print_linenumbers.swift long.txt this will print the line number at the beginning of each line.

Others

Context

All commands (a.k.a. processes) you run in SwiftShell need context: environment variables, the current working directory, standard input, standard output and standard error (standard streams).

public struct CustomContext: Context, CommandRunning {
	public var env: [String: String]
	public var currentdirectory: String
	public var stdin: ReadableStream
	public var stdout: WritableStream
	public var stderror: WritableStream
}

You can create a copy of your application's context: let context = CustomContext(main), or create a new empty one: let context = CustomContext(). Everything is mutable, so you can set e.g. the current directory or redirect standard error to a file.

Main context

The global variable main is the Context for the application itself. In addition to the properties mentioned above it also has these:

  • public var encoding: String.Encoding The default encoding used when opening files or creating new streams.
  • public let tempdirectory: String A temporary directory you can use for temporary stuff.
  • public let arguments: [String] The arguments used when launching the application.
  • public let path: String The path to the application.

main.stdout is for normal output, like Swift's print function. main.stderror is for error output, and main.stdin is the standard input to your application, provided by something like somecommand | yourapplication in the terminal.

Commands can't change the context they run in (or anything else internally in your application) so e.g. main.run("cd", "somedirectory") will achieve nothing. Use main.currentdirectory = "somedirectory" instead, this changes the current working directory for the entire application.

Example

Prepare a context similar to a new macOS user account's environment in the terminal (from kareman/testcommit):

import SwiftShell
import Foundation

extension Dictionary where Key:Hashable {
	public func filterToDictionary <C: Collection> (keys: C) -> [Key:Value]
		where C.Iterator.Element == Key, C.IndexDistance == Int {

		var result = [Key:Value](minimumCapacity: keys.count)
		for key in keys { result[key] = self[key] }
		return result
	}
}

// Prepare an environment as close to a new OS X user account as possible.
var cleanctx = CustomContext(main)
let cleanenvvars = ["TERM_PROGRAM", "SHELL", "TERM", "TMPDIR", "Apple_PubSub_Socket_Render", "TERM_PROGRAM_VERSION", "TERM_SESSION_ID", "USER", "SSH_AUTH_SOCK", "__CF_USER_TEXT_ENCODING", "XPC_FLAGS", "XPC_SERVICE_NAME", "SHLVL", "HOME", "LOGNAME", "LC_CTYPE", "_"]
cleanctx.env = cleanctx.env.filterToDictionary(keys: cleanenvvars)
cleanctx.env["PATH"] = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"

// Create a temporary directory for testing.
cleanctx.currentdirectory = main.tempdirectory

Streams

The protocols ReadableStream and WritableStream in Context above can read and write text from/to commands, files or the application's own standard streams. They both have an .encoding property they use when encoding/decoding text.

You can use let (input,output) = streams() to create a new pair of streams. What you write to input you can read from output.

WritableStream

When writing to a WritableStream you normally use .print which works exactly like Swift's built-in print function:

main.stdout.print("everything is fine")
main.stderror.print("no wait, something went wrong ...")

let writefile = try open(forWriting: path) // WritableStream
writefile.print("1", 2, 3/5, separator: "+", terminator: "=")

If you want to be taken literally, use .write instead. It doesn't add a newline and writes exactly and only what you write:

writefile.write("Read my lips:")

You can close the stream, so anyone who tries to read from the other end won't have to wait around forever:

writefile.close()

ReadableStream

When reading from a ReadableStream you can read everything at once:

let readfile = try open(path) // ReadableStream
let contents = readfile.read()

This will read everything and wait for the stream to be closed if it isn't already.

You can also read it asynchronously, that is read whatever is in there now and continue without waiting for it to be closed:

while let text = main.stdin.readSome() {
	// do something with ‘text’...
}

.readSome() returns String? - if there is anything there it returns it, if the stream is closed it returns nil, and if there is nothing there and the stream is still open it will wait until either there is more content or the stream is closed.

Another way to read asynchronously is to use the lines method which creates a lazy sequence of Strings, one for each line in the stream:

for line in main.stdin.lines() {
	// ...
}

Or instead of stopping and waiting for any output you can be notified whenever there is something in the stream:

main.stdin.onOutput { stream in
	// ‘stream’ refers to main.stdin
}

Data

In addition to text, streams can also handle raw Data:

let data = Data(...)
writer.write(data: data)
reader.readSomeData()
reader.readData() 

Commands

All Contexts (CustomContext and main) implement CommandRunning, which means they can run commands using themselves as the Context. ReadableStream and String can also run commands, they use main as the Context and themselves as .stdin. As a shortcut you can just use run(...) instead of main.run(...)

There are 4 different ways of running a command:

Run

The simplest is to just run the command, wait until it's finished and return the results:

let result1 = run("/usr/bin/executable", "argument1", "argument2")
let result2 = run("executable", "argument1", "argument2")

If you don't provide the full path to the executable, then SwiftShell will try to find it in any of the directories in the PATH environment variable.

run returns the following information:

/// Output from a `run` command.
public final class RunOutput {

	/// The error from running the command, if any.
	let error: CommandError?

	/// Standard output, trimmed for whitespace and newline if it is single-line.
	let stdout: String

	/// Standard error, trimmed for whitespace and newline if it is single-line.
	let stderror: String

	/// The exit code of the command. Anything but 0 means there was an error.
	let exitcode: Int

	/// Checks if the exit code is 0.
	let succeeded: Bool
}

For example:

let date = run("date", "-u").stdout
print("Today's date in UTC is " + date)

Print output

try runAndPrint("executable", "arg") 

This runs a command like in the terminal, where any output goes to the Context's (main in this case) .stdout and .stderror respectively. If the executable could not be found, was inaccessible or not executable, or the command returned with an exit code other than zero, then runAndPrint will throw a CommandError.

The name may seem a bit cumbersome, but it explains exactly what it does. SwiftShell never prints anything without explicitly being told to.

Asynchronous

let command = runAsync("cmd", "-n", 245).onCompletion { command in
	// be notified when the command is finished.
}
command.stdout.onOutput { stdout in 
	// be notified when the command produces output (only on macOS).	
}

// do something with ‘command’ while it is still running.

try command.finish() // wait for it to finish.

runAsync launches a command and continues before it's finished. It returns AsyncCommand which contains this:

    public let stdout: ReadableStream
    public let stderror: ReadableStream

    /// Is the command still running?
    public var isRunning: Bool { get }

    /// Terminates the command by sending the SIGTERM signal.
    public func stop()

    /// Interrupts the command by sending the SIGINT signal.
    public func interrupt()

    /// Temporarily suspends a command. Call resume() to resume a suspended command.
    public func suspend() -> Bool

    /// Resumes a command previously suspended with suspend().
    public func resume() -> Bool

    /// Waits for this command to finish.
    public func finish() throws -> Self

    /// Waits for command to finish, then returns with exit code.
    public func exitcode() -> Int

    /// Waits for the command to finish, then returns why the command terminated.
    /// - returns: `.exited` if the command exited normally, otherwise `.uncaughtSignal`.
    public func terminationReason() -> Process.TerminationReason

    /// Takes a closure to be called when the command has finished.
    public func onCompletion(_ handler: @escaping (AsyncCommand) -> Void) -> Self

You can process standard output and standard error, and optionally wait until it's finished and handle any errors.

If you read all of command.stderror or command.stdout it will automatically wait for the command to close its streams (and presumably finish running). You can still call finish() to check for errors.

runAsyncAndPrint does the same as runAsync, but prints any output directly and it's return type PrintedAsyncCommand doesn't have the .stdout and .stderror properties.

Parameters

The run* functions above take 2 different types of parameters:

(_ executable: String, _ args: Any ...)

If the path to the executable is without any /, SwiftShell will try to find the full path using the which shell command, which searches the directories in the PATH environment variable in order.

The array of arguments can contain any type, since everything is convertible to strings in Swift. If it contains any arrays it will be flattened so only the elements will be used, not the arrays themselves.

try runAndPrint("echo", "We are", 4, "arguments")
// echo "We are" 4 arguments

let array = ["But", "we", "are"]
try runAndPrint("echo", array, array.count + 2, "arguments")
// echo But we are 5 arguments
(bash bashcommand: String)

These are the commands you normally use in the Terminal. You can use pipes and redirection and all that good stuff:

output.txt") ">
try runAndPrint(bash: "cmd1 arg1 | cmd2 > output.txt")

Note that you can achieve the same thing in pure SwiftShell, though nowhere near as succinctly:

var file = try open(forWriting: "output.txt")
runAsync("cmd1", "arg1").stdout.runAsync("cmd2").stdout.write(to: &file)

Errors

If the command provided to runAsync could not be launched for any reason the program will print the error to standard error and exit, as is usual in scripts. The runAsync("cmd").finish() method throws an error if the exit code of the command is anything but 0:

let someCommand = runAsync("cmd", "-n", 245)
// ...
do {
	try someCommand.finish()
} catch let CommandError.returnedErrorCode(command, errorcode) {
	print("Command '\(command)' finished with exit code \(errorcode).")
}

The runAndPrint command can also throw this error, in addition to this one if the command could not be launched:

} catch CommandError.inAccessibleExecutable(let path) {
	// ‘path’ is the full path to the executable
}

Instead of dealing with the values from these errors you can just print them:

} catch {
	print(error)
}

... or if they are sufficiently serious you can print them to standard error and exit:

} catch {
	exit(error)
}

When at the top code level you don't need to catch any errors, but you still have to use try.

Setup

Stand-alone project

If you put Misc/swiftshell-init somewhere in your $PATH you can create a new project with swiftshell-init . This creates a new folder, initialises a Swift Package Manager executable folder structure, downloads the latest version of SwiftShell, creates an Xcode project and opens it. After running swift build you can find the compiled executable at .build/debug/.

Script file using Marathon

First add SwiftShell to Marathon:

marathon add https://github.com/kareman/SwiftShell.git

Then run your Swift scripts with marathon run .swift. Or add #!/usr/bin/env marathon run to the top of every script file and run them with ./.swift.

Swift Package Manager

Add .package(url: "https://github.com/kareman/SwiftShell", from: "5.1.0") to your Package.swift:

// swift-tools-version:5.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "ProjectName",
    platforms: [.macOS(.v10_13)],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        .package(url: "https://github.com/kareman/SwiftShell", from: "5.1.0")
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "ProjectName",
            dependencies: ["SwiftShell"]),
    ]
)

and run swift build.

Carthage

Add github "kareman/SwiftShell" >= 5.1 to your Cartfile, then run carthage update and add the resulting framework to the "Embedded Binaries" section of the application. See Carthage's README for further instructions.

CocoaPods

Add SwiftShell to your Podfile.

pod 'SwiftShell', '>= 5.1.0'

Then run pod install to install it.

License

Released under the MIT License (MIT), https://opensource.org/licenses/MIT

Kåre Morstøl, NotTooBad Software

Comments
  • Error while installing (PartialSourceLazySplitSequence)

    Error while installing (PartialSourceLazySplitSequence)

    When I try to install SwiftShell, it gives me this error: "Use of undeclared type 'PartialSourceLazySplitSequence'" On line 42 of Stream.swift (For all branches, master, SwiftShell2, and issue-19)

    opened by tanmayb123 13
  • Wait forever when running an unzip file command

    Wait forever when running an unzip file command

    we have this as the command.

    run(bash: "unzip /User/myName/Desktop/myZipFile.zip -d /User/myName/Desktop/)
    

    This is just supposed to unzip a rather large zip file (100Mb) But the Swift shell freezes the app.

    We traced it down to where control is frozen, it is in this file Command.swift

    public func finish() throws {
       self.waitUntilExit() // <-- It freezes here
       guard self.terminationStatus == 0 else {
          throw CommandError.returnedErrorCode(command: commandAsString()!, errorcode: Int(self.terminationStatus))
       }
    }
    

    We are forced to downgrade back to 3.0.0Beta in the mean time. Thanks for your work. Awesome library.

    opened by patchthecode 12
  • Final released Swift 5.3 crashes on compile

    Final released Swift 5.3 crashes on compile

    I'm just starting to look in to this. But, I'm posting here for additional comments in case someone else finds a work around before I do.

    Using public App Store release of Xcode 12.0 (12A7209) and Swift 5.3, The compiler crashes while processing SwiftShell.

    1.	Apple Swift version 5.3 (swiftlang-1200.0.29.2 clang-1200.0.30.1)
    2.	While evaluating request TypeCheckSourceFileRequest(source_file "/<redacted>/.build/checkouts/SwiftShell/Sources/SwiftShell/Stream/Stream.swift")
    3.	While type-checking 'FileHandleStream' (at /<redacted>/.build/checkouts/SwiftShell/Sources/SwiftShell/Stream/Stream.swift:207:8)
    4.	While type-checking protocol conformance to 'TextOutputStreamable' (in module 'Swift') for type 'FileHandleStream' (declared at [/<redacted>/.build/checkouts/SwiftShell/Sources/SwiftShell/Stream/Stream.swift:207:8 - line:215:1] RangeText="class FileHandleStream: ReadableStream, WritableStream {
    	public let filehandle: FileHandle
    	public var encoding: String.Encoding
    
    	public init(_ filehandle: FileHandle, encoding: String.Encoding) {
    		self.filehandle = filehandle
    		self.encoding = encoding
    	}
    ")
    0  swift                    0x0000000105743865 llvm::sys::PrintStackTrace(llvm::raw_ostream&) + 37
    1  swift                    0x0000000105742865 llvm::sys::RunSignalHandlers() + 85
    2  swift                    0x0000000105743e1f SignalHandler(int) + 111
    3  libsystem_platform.dylib 0x00007fff7017b5fd _sigtramp + 29
    4  libsystem_platform.dylib 000000000000000000 _sigtramp + 18446603338635561504
    5  swift                    0x0000000101fea0fb swift::constraints::ConstraintSystem::simplifyRestrictedConstraint(swift::constraints::ConversionRestrictionKind, swift::Type, swift::Type, swift::constraints::ConstraintKind, swift::OptionSet<swift::constraints::ConstraintSystem::TypeMatchFlags, unsigned int>, swift::constraints::ConstraintLocatorBuilder) + 779
    6  swift                    0x0000000101fd9c80 swift::constraints::ConstraintSystem::matchTypes(swift::Type, swift::Type, swift::constraints::ConstraintKind, swift::OptionSet<swift::constraints::ConstraintSystem::TypeMatchFlags, unsigned int>, swift::constraints::ConstraintLocatorBuilder) + 14960
    7  swift                    0x0000000101fe4ec1 swift::constraints::ConstraintSystem::repairFailures(swift::Type, swift::Type, swift::constraints::ConstraintKind, llvm::SmallVectorImpl<swift::constraints::RestrictionOrFix>&, swift::constraints::ConstraintLocatorBuilder) + 12529
    8  swift                    0x0000000101fd9c15 swift::constraints::ConstraintSystem::matchTypes(swift::Type, swift::Type, swift::constraints::ConstraintKind, swift::OptionSet<swift::constraints::ConstraintSystem::TypeMatchFlags, unsigned int>, swift::constraints::ConstraintLocatorBuilder) + 14853
    9  swift                    0x0000000101fda039 swift::constraints::ConstraintSystem::matchTypes(swift::Type, swift::Type, swift::constraints::ConstraintKind, swift::OptionSet<swift::constraints::ConstraintSystem::TypeMatchFlags, unsigned int>, swift::constraints::ConstraintLocatorBuilder) + 15913
    10 swift                    0x0000000101ffeaa0 swift::constraints::ConstraintSystem::addConstraintImpl(swift::constraints::ConstraintKind, swift::Type, swift::Type, swift::constraints::ConstraintLocatorBuilder, bool) + 96
    11 swift                    0x0000000101fd5b42 swift::constraints::ConstraintSystem::addConstraint(swift::constraints::ConstraintKind, swift::Type, swift::Type, swift::constraints::ConstraintLocatorBuilder, bool) + 66
    12 swift                    0x00000001022069bd llvm::Optional<swift::RequirementMatch> llvm::function_ref<llvm::Optional<swift::RequirementMatch> (swift::Type, swift::Type)>::callback_fn<swift::matchWitness(llvm::DenseMap<std::__1::pair<swift::GenericSignatureImpl const*, swift::ClassDecl const*>, swift::RequirementEnvironment, llvm::DenseMapInfo<std::__1::pair<swift::GenericSignatureImpl const*, swift::ClassDecl const*> >, llvm::detail::DenseMapPair<std::__1::pair<swift::GenericSignatureImpl const*, swift::ClassDecl const*>, swift::RequirementEnvironment> >&, swift::ProtocolDecl*, swift::ProtocolConformance*, swift::DeclContext*, swift::ValueDecl*, swift::ValueDecl*)::$_1>(long, swift::Type, swift::Type) + 93
    13 swift                    0x00000001021e7dca swift::matchWitness(swift::DeclContext*, swift::ValueDecl*, swift::ValueDecl*, llvm::function_ref<std::__1::tuple<llvm::Optional<swift::RequirementMatch>, swift::Type, swift::Type> ()>, llvm::function_ref<llvm::Optional<swift::RequirementMatch> (swift::Type, swift::Type)>, llvm::function_ref<swift::RequirementMatch (bool, llvm::ArrayRef<swift::OptionalAdjustment>)>) + 8778
    14 swift                    0x00000001021e9903 swift::matchWitness(llvm::DenseMap<std::__1::pair<swift::GenericSignatureImpl const*, swift::ClassDecl const*>, swift::RequirementEnvironment, llvm::DenseMapInfo<std::__1::pair<swift::GenericSignatureImpl const*, swift::ClassDecl const*> >, llvm::detail::DenseMapPair<std::__1::pair<swift::GenericSignatureImpl const*, swift::ClassDecl const*>, swift::RequirementEnvironment> >&, swift::ProtocolDecl*, swift::ProtocolConformance*, swift::DeclContext*, swift::ValueDecl*, swift::ValueDecl*) + 1187
    15 swift                    0x00000001021ea452 swift::WitnessChecker::findBestWitness(swift::ValueDecl*, bool*, swift::NormalProtocolConformance*, llvm::SmallVectorImpl<swift::RequirementMatch>&, unsigned int&, unsigned int&, bool&) + 946
    16 swift                    0x00000001021f4ac3 swift::ConformanceChecker::resolveWitnessViaLookup(swift::ValueDecl*) + 643
    17 swift                    0x00000001021ef9a5 swift::MultiConformanceChecker::checkIndividualConformance(swift::NormalProtocolConformance*, bool) + 14773
    18 swift                    0x00000001021eba93 swift::MultiConformanceChecker::checkAllConformances() + 147
    19 swift                    0x00000001021fb617 swift::TypeChecker::checkConformancesInContext(swift::DeclContext*, swift::IterableDeclContext*) + 8295
    20 swift                    0x00000001021ab89a (anonymous namespace)::DeclChecker::visitClassDecl(swift::ClassDecl*) + 8138
    21 swift                    0x00000001021a3d93 (anonymous namespace)::DeclChecker::visit(swift::Decl*) + 2739
    22 swift                    0x000000010226b621 swift::TypeCheckSourceFileRequest::evaluate(swift::Evaluator&, swift::SourceFile*) const + 753
    23 swift                    0x000000010226e4a9 llvm::Expected<swift::TypeCheckSourceFileRequest::OutputType> swift::Evaluator::getResultUncached<swift::TypeCheckSourceFileRequest>(swift::TypeCheckSourceFileRequest const&) + 953
    24 swift                    0x000000010226b1a4 swift::TypeCheckSourceFileRequest::OutputType swift::evaluateOrDefault<swift::TypeCheckSourceFileRequest>(swift::Evaluator&, swift::TypeCheckSourceFileRequest, swift::TypeCheckSourceFileRequest::OutputType) + 164
    25 swift                    0x00000001013e655b swift::CompilerInstance::performSemaUpTo(swift::SourceFile::ASTStage_t) + 6859
    26 swift                    0x00000001012a72fd swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 6845
    27 swift                    0x000000010122bc07 main + 1255
    28 libdyld.dylib            0x00007fff6ff82cc9 start + 1
    29 libdyld.dylib            0x000000000000003c start + 18446603338637628276
    
    
    opened by briancordanyoung 10
  • gradlew taking forever when run from terminal

    gradlew taking forever when run from terminal

    This is a Sample Project for this issue: https://github.com/salmatarzi/SampleProject/

    Expected Behaviour

    Running commandlinetool clean from the terminal from the directory of the Android project to run correctly and terminate.

    Actual Behaviour

    The command takes forever when running the command from the terminal, however when running it from XCode, it runs perfectly fine, and cleans the Android project

    Steps to reproduce

    Run commandlinetool clean from the terminal from the directory of the Android project

    opened by salmatarzi 7
  • Mark the AsyncCommand stop() function as unavailable on linux and set…

    Mark the AsyncCommand stop() function as unavailable on linux and set…

    … up a test for it on macOS

    Unfortunately the @available attribute doesn't actually have a Linux qualifier. So I had to mix it in with compiler flags.

    Should resolve #59 though.

    opened by Ponyboy47 7
  • Starting command later

    Starting command later

    I want to create an AsyncCommand, but not have it be started immediately. Currently, due to the internal protection on the createProcess function, I have to manually create my Process() object to pass to the AsyncCommand's init(unlaunched...).

    It seems silly to have that initializer if we can't really even take advantage of it. Making createProcess public should allow the functionality I desire

    opened by Ponyboy47 7
  • Cancel with Ctrl-C

    Cancel with Ctrl-C

    I have a list of long running bash commands that I execute using runAndPrint(bash: ) in a for loop. I try to terminate the execution using Ctrl-C but somehow the new command gets executed again. I assume that I can only terminate some of the subcommands but not the whole process. Any idea how can I listen for Ctrl-C in my code and cancel the whole process programmatically?

    opened by antranapp 6
  • Cyclic Dependency

    Cyclic Dependency

    When building a new instance of SwiftShell using the init script, I received an error about a cyclic dependency.

    Creating executable package: SwiftShell Creating Package.swift Creating README.md Creating .gitignore Creating Sources/ Creating Sources/SwiftShell/main.swift Creating Tests/ Creating Tests/LinuxMain.swift Creating Tests/SwiftShellTests/ Creating Tests/SwiftShellTests/SwiftShellTests.swift Creating Tests/SwiftShellTests/XCTestManifests.swift error: cyclic dependency declaration found: SwiftShell -> SwiftShell 'SwiftShell' /Users/username/Documents/SwiftShell: error: cyclic dependency declaration found: SwiftShell -> SwiftShell

    opened by scifiman 6
  • AsyncCommand use main.stdout / main.stderr

    AsyncCommand use main.stdout / main.stderr

    Right now, AsyncCommand will always change the process's stdout and stderr to a Pipe (https://github.com/kareman/SwiftShell/blob/master/Sources/SwiftShell/Command.swift#L291).

    There should be a way to start an AsyncCommand which still directs output to main.stdout and main.stderr. Simply forwarding the output using something like:

    command.stdout.onOutput { $0.write(to: &stdout) }
    

    is not sufficient, as pipes are block buffered but main.stdout is line buffered, so the command's output will not be written to stdout in the expected way.

    I think this could potentially be solved by changing AsyncCommand.init to something like:

    init(unlaunched process: Process, stdout: WritableStream? = nil, stderr: WritableStream? = nil)
    
    opened by jakeheis 6
  • SwiftShell is listed as available for iOS on CocoaPods.org

    SwiftShell is listed as available for iOS on CocoaPods.org

    Found this on cocoapods.org, searching for a swift application on iOS for running shell applications.

    Updated Podfile, ran pod install, SwiftShell is visible in the Pods location in Xcode, and can import SwiftShell in a single ViewController app. Attempting to invoke run() function is not recognized. Running with Xcode 9.2 and swift 4.

    bug 
    opened by syvertsj 6
  • Very weird issue (using

    Very weird issue (using "run()" vs. "runAndPrint()")

    I'm encountering a very weird issue here:

    I'm using SwiftShell2, and when I do this: let res = run("python", "ngrammine.py", "source.txt", "destination.txt"); print(res) The app hangs. Completely. Just frozen.

    However, when I do this: runAndPrint("python", "ngrammine.py", "source.txt", "destination.txt") It prints the result, and stops the app.

    Why does the app hang in the former example?

    opened by tanmayb123 6
  • The interactive command is suspended at the terminal

    The interactive command is suspended at the terminal

    I call the git command with SwiftShell. The interactive command "git log" hangs, and "git status" works normally. "git log" can work on the console in Xcode. As shown below:

    console in Xcode: 截屏2022-09-29 09 32 27

    At the terminal: 截屏2022-09-29 09 33 59 截屏2022-09-29 09 34 32

    opened by misanyrock 0
  • How to execute an ncurses-based command?

    How to execute an ncurses-based command?

    When executing an ncurses-based (such as vim), the output does not appear to update correctly.

    For example:

    try runAndPrint("/usr/bin/vim")
    

    I do not see the vim UI at all. When I hit Ctrl-C, I see a flash in the terminal. So this implies that the text controls were sent but perhaps are being buffered. I looked through the code but I don't see an obvious way to control the stdout buffer size.

    As a separate issue, it would be nice if there was a way to pass-thru ctrl-c and other job control keys to the executing process.

    opened by dannys42 2
  • `waitUntilExit` cause slow execution

    `waitUntilExit` cause slow execution

    I benchmark the duration of executing a simple process. On other languages (ruby, go, bash), the result is about 1ms, but on Swift, it takes about 60ms.

    The test code is

    let s = Date()
    SwiftShell.run("/bin/cp")
    print(Date().IntervalSince(s))
    

    I also test with raw Process and get same result

    let s2 = Date()
    let process = Process()
    process.executableURL = URL(fileURLWithPath: "/bin/cp")
    try process.run()
    process.waitUntilExit()
    print(Date().IntervalSince(s2))
    

    After digging, I found the it may cause by the Process.waitUntilExit:

     "it polls the current run loop using NSDefaultRunLoopMode until the task completes"
    

    https://developer.apple.com/documentation/foundation/nstask/1415808-waituntilexit

    When changing it to terminationHandler, it's back to normal.

    let s2 = Date()
    let process = Process()
    process.executableURL = URL(fileURLWithPath: "/bin/cp")
    process.terminationHandler = { t in
        print(2, Date().timeIntervalSince(s2))
    }
    try process.run()
    process.waitUntilExit()
    print(1, Date().timeIntervalSince(s2))
    

    The output:

    2 0.007783055305480957
    1 0.06369709968566895
    
    enhancement help wanted 
    opened by leavez 1
  • runAsync(bash

    runAsync(bash"") cannot write nothing on onStringOutput on mac osx app standalone

    HI,

    I did app mac osx in BigSur that send Process().run and write in textEditor the logs. It' a process async that poll some https, and I need logs all inside my apps. It works perfect whe run on xcode but standlone don't write nothing. So I looking for also the xcode forum, the point is for process async that Pipe() cannot buffering, and we can do force it.

    I think that yo have the same issue also in this sdk.

    That's it's real case, I use upload-symbols to send a 50 symbols, so 10 minute to finish it all.

    let asyncCommand = runAsync(bash: /Carthage_FirebaseCrashlytics_scripts/upload-symbols  -gsp /AppHouse/resources/GoogleService-Info-PROD.plist -p ios //Downloads/appDsyms ").onCompletion { command in
               debugPrintOnTerminal(string: " command is \(String(describing: command))")
           }
           asyncCommand.stdout.onStringOutput {
               print($0)
               debugPrintOnTerminal(string: " output is \(String(describing: $0))")
           }
    

    I cannot use the command.finish(), because lock the app.

    Thanks all.

    Jonny

    opened by pjcau 2
  • How to pipe with different contexts?

    How to pipe with different contexts?

    Thanks for the great library! Is there a way to do the equivalent of "(cd srcdir && tar cf - . ) | (cd dstdir && tar xf - )" ?

    I'd like to do this following this pattern:

    var file = try open(forWriting: "output.txt")
    runAsync("cmd1", "arg1").stdout.runAsync("cmd2").stdout.write(to: &file)
    

    Rather than simply doing bash execution to prevent unintended string manipulation. Do you have a recommendation on how to approach this?

    opened by dannys42 0
Releases(5.1.0)
  • 5.1.0(Sep 25, 2020)

    • Test with Swift 5.3 and Xcode 12 (#93)

      Use xcpretty-actions-formatter

    • Update Xcode project. (#94)

      As suggested by Xcode 12. Also update Swift version.

    • Release 5.1 (#95)

      • Update README.md

      • Update podspec.

    Source code(tar.gz)
    Source code(zip)
  • 5.1.0-beta.1(Jul 13, 2020)

    • Compile on Swift 5.3

    • Remove "support" for iOS. Running shell commands has never worked on iOS, it was just a hack to enable kareman/FileSmith (which does work on iOS) and SwiftShell to use the same *Stream protocols.

    • Readme: Remove mentions of FileSmith. FileSmith and SwiftShell no longer cooperate specifically.

    • Add GitHub CI tests (#85)

    • Set Swift version to 5.2.4.

    • Remove custom nullDevice. Turns out my implementation of nullDevice in SwiftFoundation was merged four years ago and I forgot about it.

    • Remove Tests/LinuxMain.swift, use '--enable-test-discovery' on Linux instead.

    • Use built-in XCTest error testing.

    • Remove Process extensions for Linux. It seems they have been added to Swift Foundation.

    • Remove missing files from Xcode project.

    • Reformat code using swiftformat 0.40.14 Because it is the last version of swiftformat that does indentation and alignment correctly (tabs for indentation, spaces for alignment).

    • Test on Swift 5.1 too. (#86)

    • Readme: supports Swift 5.1 - 5.3

    • Readme: remove Travis badge

    • Xcode project: set deployment target, remove unused iOS stuff. (#89)

    • CI: Separate out Xcode and cocapods tests (#88)

    Source code(tar.gz)
    Source code(zip)
  • 5.0.1(Oct 3, 2019)

  • 5.0.0(Mar 27, 2019)

    • Update to Swift 5
    • Set minimum macOS version to 10.13, because of deprecated methods in Foundation.Process.
    • swiftshell-init uses Swift 5 in it's generated Package.swift.
    • The 'exit' functions now only print filename and line number in debug builds.
    Source code(tar.gz)
    Source code(zip)
  • 4.1.2(Oct 3, 2018)

  • 4.1.1(Jul 31, 2018)

  • 4.1.0(Apr 18, 2018)

    • Update to Swift 4.1.
    • Add AsyncCommand.stop, interrupt, suspend and resume. Also for Linux (#59, #60).
    • Add runAsyncAndPrint command (#61).
    • Improve documentation and formatting. Use present tense in documentation. Add missing documentation.

    Bug fixes

    • Make main.stdout.encoding default to main.encoding.
    • run: do not read both standard output and standard error if they are the same.

    Miscellaneous

    • Mark stream's readData() with @discardableResult
    • Make testIntsLazySplit_NoEmptySlices actually do something.
    • Xcode 9.3: update to recommended project settings.
    • Readme: update with new functionality. Also fix some other stuff.
    Source code(tar.gz)
    Source code(zip)
  • 4.0.2(Mar 4, 2018)

  • 4.0.1(Jan 30, 2018)

    • Remove deprecated uses of .characters, and thereby get rid of all the warnings.
    • Get swiftshell-init to work with new Package.swift layout.
    • Add missing Linux unit test.
    • Fix bug in ReadableStream.onOutput where the callback would crash if the ReadableStream instance had ceased to exist (#45)
    Source code(tar.gz)
    Source code(zip)
  • 4.0.0(Jan 30, 2018)

  • 3.0.1(May 12, 2017)

  • 3.0.0(Apr 29, 2017)

    The readme says it all.

    Most notable recent changes:

    • the run(...) functions now return RunOutput instead of a String.

    • many things were renamed to follow standard Swift naming guidelines.

    • Streams can read and write raw data.

    • Support for iOS – but only for Streams (running shell commands is not possible on iOS). This way other projects, like FileSmith, can use Streams. Streams should be their own module, but then they couldn't implement CommandRunning. This will be possible in Swift 4 I believe.

    Source code(tar.gz)
    Source code(zip)
  • 3.0.0-beta.12(Mar 24, 2017)

  • 2.1(May 8, 2016)

    • Have main.tempdirectory use the name of the script/application.

      Fall back to ‘SwiftShell’ if main.path is empty (running in a playground).

    • Allow passing Ints as error codes to "exit".

    • Actually print error messages in release builds.

      Workaround for a compiler bug where passing an NSError to exit in release builds would not print the actual error message.

    • Fix #16 by having WriteableStream use ‘print’ when writing to standard output.

      This way print's buffering when standard output is not a terminal does not rearrange the order in which output is printed when using both 'print' and 'main.stdout.writeln'. See also https://bugs.swift.org/browse/SR-1127 and http://stackoverflow.com/a/13933741/96587 .

    • Add stop func to stop an AsyncShellTask early.

    • Add async callbacks for ReadableStream.onOutput and AsyncShellTask.onCompletion - allows for getting callbacks with data as the AsyncShellTask is running.

    • Add AsyncShellTask.onStringOutput.

    Thanks to @kenthinson for implementing the 3 last ones.

    Source code(tar.gz)
    Source code(zip)
  • 2.0.2(Mar 22, 2016)

  • 2.0.1(Mar 21, 2016)

    • Fix #15: run("cat","longtext.txt") never finishes for large files.
    • Add AsyncShellTask.exitcode().
    • Make sure the test script finds the SwiftShell launcher script.
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0(Feb 24, 2016)

  • v2.0b2(Dec 18, 2015)

    • Enable installation of framework to ~/Library/Frameworks by running 'xcodebuild install'.
    • Add CollectionType.splitOnce and LazySplitGenerator.
    • Add LazyCollectionType.split .
    • Add ReadableStream.lines() for lazily splitting output into lines.
    Source code(tar.gz)
    Source code(zip)
  • v1.1(Dec 18, 2015)

    Use $SWIFTSHELL_FRAMEWORK_PATH instead of $DYLD_FRAMEWORK_PATH in the swiftshell launcher script.

    Because of this change in El Capitan:

    Spawning children processes of processes restricted by System Integrity Protection, such as by launching a helper process in a bundle with NSTask or calling the exec(2) command, resets the Mach special ports of that child process. Any dynamic linker (dyld) environment variables, such as DYLD_LIBRARY_PATH, are purged when launching protected processes.

    https://developer.apple.com/library/prerelease/mac/documentation/Security/Conceptual/System_Integrity_Protection_Guide/RuntimeProtections/RuntimeProtections.html

    Source code(tar.gz)
    Source code(zip)
  • v0.1(Feb 14, 2015)

Owner
Kare Morstol
Software Developer living in Norway.
Kare Morstol
📋A hand-curated collection of useful and informative Swift Scripting materials.

Articles • Videos Articles Creating iOS Application Icons with SwiftUI by Eneko Alonso An interesting way to use SwiftUI with CLI for icon generation.

Artem Novichkov 196 Jan 7, 2023
Util for executing shell commands, and getting the results easily(data, string, and any decodable).

ShellExecutor Util for executing shell commands, and getting the results easily(data, string, and any decodable). Requirements Xcode 14.0+ Swift 5.7+

Yozone Wang 2 Jul 30, 2022
Guaka - Smart and beautiful POSIX compliant CLI framework for Swift.

Guaka - Smart and beautiful POSIX compliant CLI framework for Swift. It helps you create modern and familiar CLI apps in the vein of widely used proje

Omar Abdelhafith 1.1k Dec 24, 2022
SwiftCLI - A powerful framework for developing CLIs in Swift

SwiftCLI A powerful framework for developing CLIs, from the simplest to the most complex, in Swift.

Jake Heiser 793 Jan 4, 2023
Linenoise-Swift A pure Swift implementation of the Linenoise library. A minimal, zero-config readline replacement.

Linenoise-Swift A pure Swift implementation of the Linenoise library. A minimal, zero-config readline replacement. Supports Mac OS and Linux Line edit

Andy Best 114 Dec 14, 2022
Swift tool to generate Module Interfaces for Swift projects.

ModuleInterface Swift tool to generate Module Interfaces for Swift projects. What is a Module Interface A Module Interface is what we commonly get usi

Jorge Revuelta 75 Dec 21, 2022
Swift-cli - Example of building command-line tools in Swift

swift-cli Example of building command-line tools in Swift Step 1: Create CLI wit

Noah Gift 2 Jan 17, 2022
Compose beautiful command line interfaces in Swift

Commander is a small Swift framework allowing you to craft beautiful command line interfaces in a composable way. Usage Simple Hello World i

Kyle Fuller 1.5k Dec 29, 2022
CommandLineKit - A pure Swift library for creating command-line interfaces

CommandLineKit A pure Swift library for creating command-line interfaces. Note: This project is no longer maintained. It's preserved here for historic

Ben Gollmer 1.1k Dec 1, 2022
Progress.swift ⌛ Add beautiful progress bars to your loops.

Progress.swift ⌛ Just wrap the SequenceType in your loop with the Progress SequenceType and you'll automatically get beautiful progress bars. Updating

Justus Kandzi 304 Dec 1, 2022
Straightforward, type-safe argument parsing for Swift

Swift Argument Parser Usage Begin by declaring a type that defines the information that you need to collect from the command line. Decorate each store

Apple 2.9k Jan 7, 2023
SwiftyTextTable - A lightweight Swift library for generating text tables

SwiftyTextTable A lightweight Swift library for generating text tables. Swift Language Support SwiftyTextTable is now Swift 4.0 compatible! The last r

Scott Hoyt 283 Dec 23, 2022
Swift utilities for running commands.

Swift Commands Swift utilities for running commands. The Commands module allows you to take a system command as a string and return the standard outpu

Phil 41 Jan 2, 2023
A CLI too powered by Swift to provision environments using an up.toml manifest file

tuist-up tuist up was originally a Tuist built-in command to provision environments by reading the requirements in a manifest file Setup.swift. Althou

Tuist 5 Mar 31, 2022
Terminal string styling for Swift.

Terminal string styling for Swift. Integration Swift Package Manager (SPM) You can use The Swift Package Manager to install ColorizeSwift by adding it

Michał Tynior 281 Dec 22, 2022
Simple & Elegant Command Line Interfaces in Swift

An elegant pure Swift library for building command line applications. Features Tons of class, but no classes. 100% organic pure value types. Auto gene

hypertalk 52 Nov 9, 2022
✏️Expressive styling on terminal string. (chalk for swift)

Chalk Expressive styling on terminal string. Highlights Expressive API 256/TrueColor support Nest styles Auto downgrading to terminal supported color

Luo Xiu 59 Jun 10, 2022
A starting point to create CLI utilities with swift

cli tuist template A starting point to create CLI utilities with swift Installation Just create a Tuist folder and a Templates folder inside it. Creat

humdrum 6 May 3, 2022
A Tuist Template to quickly create CLI apps in Swift

macOS CLI Template Motivation I'm writing more and more Swift CLI apps these days. And as I solve more problems with this litte tools, I find that I'm

Diego Freniche 21 Dec 28, 2022