Giter Club home page Giter Club logo

swiftshell's Introduction

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:

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 <name>. 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/<name>.

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 <name>.swift. Or add #!/usr/bin/env marathon run to the top of every script file and run them with ./<name>.swift.

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.

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.

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

swiftshell's People

Contributors

apbendi avatar beltex avatar darthmike avatar fengqiangboy avatar harlanhaskins avatar kareman avatar karlzeo avatar kenthinson avatar ponyboy47 avatar sohocoke avatar yasumoto 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

swiftshell's Issues

“No such module” when importing SwiftShell in a playground.

I'm installed "successfully" SwiftShell 2.2 but Xcode in playground says after stroke: import SwiftShell - no such module 'SwiftShell'. Framework installed in /Library/Frameworks/ under OS X El Capitan 10.11.5. Xcode 7.3.1.

Also tried old version 1.1 for Xcode 6 on OS X 10.9.5 with the same result. Installed framework to same path /Library/Frameworks/ (also tried on ~/Library/Frameworks), with same error - no such module.

What to do with that??

Is there a way to change the directory on a per thread basis instead of for the entire application?

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.

I'm not sure if this might be a problem, but i'm asking before i code it.
I might have multiple threads running, and they may change the directory before running other commands. It would be bad if changing the directory changes it for the entire application because there will be conflicts on different threads.

Is my concern valid?
Thanks.

process.terminate() has not been implemented on Linux

I'm receiving this error calling command.stop() on Linux:

Fatal error: terminate() is not yet implemented: file Foundation/Process.swift, line 464

I'll make the pull request, but would you recommend marking it as unavailable on Linux with:

@available(Linux, unavailable, message: "The terminate() function has not been implemented on Linux")

or to use a compiler flag:

#if os(macOS)
...
#endif

Swift package manager: No such module

Hi there, I'm having some difficulty getting a test project compiling with SwiftShell. I'm pretty new to Swift Package Manager so that doesn't help. I wonder if I'm missing something.

Here is my Package.swift file:

// swift-tools-version:4.0

import PackageDescription

let package = Package(
    name: "commandlinetest",
    dependencies: [
        .package(url: "https://github.com/kareman/SwiftShell.git", from: "4.0.0")
    ],
    targets: [
        .target(
            name: "commandlinetest",
            dependencies: []),
    ]
)

I have a very simple main.swift file that just tries to import SwiftShell, but fails when I try to build it:

Compile Swift Module 'commandlinetest' (1 sources)
/Users/…/commandlinetest/Sources/commandlinetest/main.swift:1:8: error: no such module 'SwiftShell'
import SwiftShell
^
error: terminated(1): /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-build-tool -f /Users/…/commandlinetest/.build/debug.yaml main

My test project (commandlinetest) was created using "swift package init --type executable". I haven't done anything custom at all yet, but I can't get past this step.

I feel like I must be missing something dumb or something. Any help would be appreciated. I've been banging my head over this for an hour or so.

Gatekeeper and SwiftShell

Idk if this is SwiftShell related or not but in any app I've used it in Gatekeeper rejects the app.
"
File /Users/jimmyhoughjr/Downloads/Ghostnote2-10.app/Contents/Resources/ghostengine.app/Contents/MacOS/../Frameworks/SwiftShell.framework/Versions/A/SwiftShell failed on rPathCmd /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx/libswiftCore.dylib

"

I'm not sure why this is happening though.

memory leak and too many open files

In a new project, I'm using a command line tool project. I'm using source code in my project

for i in 1...99999999 {
    run("ls")
}

It will crash at around 2000th iteration on my machine because the OS cannot open more files.
During its execution, the memory increased from 5MB to 50MB and it had more than 6000 open files before crashing.
Can anyone reproduce this? or am I using it incorrectly?

runAndPrint will not pass an interrupt

When the host program receives an interrupt, runAndPrint will still execute, while runAsync will pass the interrupt.

import SwiftShell

main.runAndPrint("yes") // this would indefinitely execute
main.otherRunAndPrint("yes") // this would pass interrupts

extension CommandRunning {
        func otherRunAndPrint(_ executable: String, _ args: Any ...) throws {
                let command = runAsync(executable, args)
                var stdout = context.stdout
                command.stdout.onOutput { $0.write(to: &stdout) }
                var stderror = context.stderror
                command.stderror.onOutput { $0.write(to: &stderror) }
                try command.finish()
        }
}

Do you have an idea how to explain this behavior?

When trying this out, I could experience the error mentioned in #45
runAndPrint vs runAsync from https://github.com/xcodeswift/sake/pull/44#issuecomment-353865937

How do i download this library with cocoapods?

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'swiftShellTest' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for swiftShellTest
  pod 'SwiftShell'

end

This fails when i do a pod install

Output from ‘print’ comes after output from ‘main.stdout.writeln’ when script is redirected

#!/usr/bin/env swiftshell

import SwiftShell

print("print: line 1")
main.stdout.writeln("main.stdout.writeln: line 2")
print("print: line 3")

When script is launched from the terminal output is as expected:

~/Downloads> ./test.swift 
print: line 1
main.stdout.writeln: line 2
print: line 3

But when the output is redirected:

~/Downloads> ./test.swift | cat
main.stdout.writeln: line 2
print: line 1
print: line 3

Async Misunterstanding

Hi Kameran,

I'm having hard time understanding your Async implementation.
I'm not using SS in CLI but as part of a GUI app. I'm trying to bind the PID's output to the UI.

Before SwiftShell I used to chain a fileHandle to a Notification Center Observer in background which allowed me to update GUI or anything Async.

I'm trying to find the same paradigm in SS but all my attempts to get stdout block the program.
Is SS meant for such task or am I stuck with the Process>Pipe>FileHandle(in BG)>NCObserver

NSConcreteTask launchWithDictionary: crash

Crashed Thread: 1 Dispatch queue: com.find.timesync

Exception Type: EXC_BAD_INSTRUCTION (SIGILL)
Exception Codes: 0x0000000000000001, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY

Termination Signal: Illegal instruction: 4
Termination Reason: Namespace SIGNAL, Code 0x4
Terminating Process: exc handler [0]

Application Specific Backtrace 1:
0 CoreFoundation 0x00007fff84a482cb __exceptionPreprocess + 171
1 libobjc.A.dylib 0x00007fff9985348d objc_exception_throw + 48
2 CoreFoundation 0x00007fff84ac6c3d +[NSException raise:format:] + 205
3 Foundation 0x00007fff864712e6 -[NSConcreteTask launchWithDictionary:] + 2518
4 SwiftShell 0x0000000105ade1f9 TToFE10SwiftShellCSo7Process15launchThrowablyfzT_T + 41
5 SwiftShell 0x0000000105adea43 TFC10SwiftShell9RunOutputcfT6launchCS_12AsyncCommand_S0 + 147
6 SwiftShell 0x0000000105ae2874 _TTSf4gs_g_n_n___TTSg5C10SwiftShell11MainContextS0_S_14CommandRunningS____TFE10SwiftShellPS_14CommandRunning3runfTSSGSaP__13combineOutputSb_CS_9RunOutput + 820
7 SwiftShell 0x0000000105adbaf8 _TTSf4gs_n___TF10SwiftShell3runFT4bashSS13combineOutputSb_CS_9RunOutput + 264
8 SwiftShell 0x0000000105adb93f _TF10SwiftShell3runFT4bashSS13combineOutputSb_CS_9RunOutput + 15
9 find 0x0000000103587568 TTSf4d___TZFC4find10AppManager7syncNTPfT_GSqSd + 296
10 find 0x000000010348b79d TFC4find18FindTimeSyncServer11loopProcessfT_T + 301
11 find 0x000000010348bacb TFFFC4find18FindTimeSyncServer11loopProcessFT_T_U_FT_T_U_FT_T + 43
12 libdispatch.dylib 0x00007fff9a10b524 _dispatch_call_block_and_release + 12
13 libdispatch.dylib 0x00007fff9a1028fc _dispatch_client_callout + 8
14 libdispatch.dylib 0x00007fff9a1189a0 _dispatch_queue_serial_drain + 896
15 libdispatch.dylib 0x00007fff9a10b306 _dispatch_queue_invoke + 1046
16 libdispatch.dylib 0x00007fff9a111020 _dispatch_queue_override_invoke + 369
17 libdispatch.dylib 0x00007fff9a1046b5 _dispatch_root_queue_drain + 476
18 libdispatch.dylib 0x00007fff9a10448c _dispatch_worker_thread3 + 99
19 libsystem_pthread.dylib 0x00007fff9a3515a2 _pthread_wqthread + 1299
20 libsystem_pthread.dylib 0x00007fff9a35107d start_wqthread + 13

Can this framework call xctool cmd?

I want use this for call xctool to build my project and then uplaod to my sever. Could you give me some suggestion or a demo for me. Thanks!

I'm having trouble building with Carthage

The error maybe related to swiftlint problems? I have swiftlint version 0.26.0.

Here's the error that I see:

*** Building scheme "SwiftShell" in SwiftShell.xcodeproj
Build Failed
	Task failed with exit code 65:
	/usr/bin/xcrun xcodebuild -project /Users/jessegrosjean/Documents/github/Workspace/Carthage/Checkouts/SwiftShell/SwiftShell.xcodeproj -scheme SwiftShell -configuration Release -derivedDataPath /Users/jessegrosjean/Library/Caches/org.carthage.CarthageKit/DerivedData/9.4.1_9F2000/SwiftShell/4.1.0 ONLY_ACTIVE_ARCH=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= CARTHAGE=YES archive -archivePath /var/folders/kl/mq456jxs2hl13xn41v0pl9qh0000gn/T/SwiftShell SKIP_INSTALL=YES GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=NO CLANG_ENABLE_CODE_COVERAGE=NO STRIP_INSTALLED_PRODUCT=NO (launched in /Users/jessegrosjean/Documents/github/Workspace/Carthage/Checkouts/SwiftShell)

This usually indicates that project itself failed to compile. Please check the xcodebuild log for more details: /var/folders/kl/mq456jxs2hl13xn41v0pl9qh0000gn/T/carthage-xcodebuild.cGhLjB.log

And then in carthage-xcodebuild.cGhLjB.log I see:

...
/Users/jessegrosjean/Documents/github/Workspace/Carthage/Checkouts/SwiftShell/Sources/SwiftShell/Command.swift:274: warning: Line Length Violation: Line should be 120 characters or less: currently 143 characters (line_length)
/Users/jessegrosjean/Documents/github/Workspace/Carthage/Checkouts/SwiftShell/Sources/SwiftShell/Command.swift:284: warning: Line Length Violation: Line should be 120 characters or less: currently 157 characters (line_length)
/Users/jessegrosjean/Documents/github/Workspace/Carthage/Checkouts/SwiftShell/Sources/SwiftShell/Command.swift:294: error: Line Length Violation: Line should be 120 characters or less: currently 252 characters (line_length)
/Users/jessegrosjean/Documents/github/Workspace/Carthage/Checkouts/SwiftShell/Sources/SwiftShell/Command.swift:385: warning: Line Length Violation: Line should be 120 characters or less: currently 134 characters (line_length)
/Users/jessegrosjean/Documents/github/Workspace/Carthage/Checkouts/SwiftShell/Sources/SwiftShell/Command.swift:449: warning: Line Length Violation: Line should be 120 characters or less: currently 133 characters (line_length)
/Users/jessegrosjean/Documents/github/Workspace/Carthage/Checkouts/SwiftShell/Sources/SwiftShell/Command.swift:466: warning: File Line Length Violation: File should contain 400 lines or less: currently contains 466 (file_length)
Done linting! Found 245 violations, 34 serious in 22 files.

** ARCHIVE FAILED **


The following build commands failed:
	PhaseScriptExecution Run\ Script /Users/jessegrosjean/Library/Caches/org.carthage.CarthageKit/DerivedData/9.4.1_9F2000/SwiftShell/4.1.0/Build/Intermediates.noindex/ArchiveIntermediates/SwiftShell/IntermediateBuildFilesPath/SwiftShell.build/Release/SwiftShell.build/Script-BA50D29D1E9714C7002BA4A5.sh
(1 failure)

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)

FileSmith not found when using swift package manager

My Package.swift looks like this:

import PackageDescription

let package = Package(
    name: "Wrench",
    products: [
    .executable(name: "wrench", targets:["Wrench"]),
               ],
    dependencies: [
        .package(url: "https://github.com/kareman/FileSmith", from: "0.2.0"),
        .package(url: "https://github.com/apple/swift-package-manager.git", from: "0.1.0"),
    ],
    targets: [
        .target(
            name: "Wrench",
            dependencies: ["WrenchCore"]
        ),
        .target(
            name: "WrenchCore",
            dependencies: ["FileSmith", "Utility"]
        ),
    ]
)

I did swift package update which checked out FileSmith into the .build/checkouts directory. But when I try to update the project with swift package generate-xcodeproj, I get this error:

Code-cutter:Wrench derekclarkson$ swift package generate-xcodeproj
'Wrench' /Users/derekclarkson/Library/Mobile Documents/com~apple~CloudDocs/Projects/Wrench: error: product dependency 'FileSmith' not found
warning: dependency 'FileSmith' is not used by any target

Any ideas? Everything looks fine to me.

Automatically download and build dependencies

If a script begins with a special comment block containing a list of os x frameworks it uses, the swiftshell bash script can download and build those that are not currently in any of the folders where the script looks for frameworks.

Use Carthage.

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

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

FileHandle.read() should have a discardable result

In #54 The fix to #52 was to call .read() on the pipes before calling .finish(). When manually managing an AsyncCommand, the fixit is to call .read() "even if you're not going to use the result". I think that this should warrant the .read() function being marked with the @discardableResult attribute.

Currently in my code, before calling command.exitcode() I have a line that looks like this:

command.stdout.read()

This line generates a warning when building.

I'm unsure if the readSome(), readData(), and readSomeData() variants should be marked as @discardableResult as well.

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

Minimum deployment target issue on 10.12?

Running 10.12. Framework installed in ~/Library/Frameworks/. Whenever I run a swift script importing SwiftShell, I get the following error:

./swifttest:4:8: error: module file's minimum deployment target is OS X v10.11: /Users/xxxxx/Library/Frameworks/SwiftShell.framework/Modules/SwiftShell.swiftmodule/x86_64.swiftmodule

I have also tried setting the deployment target on the framework specifically to 10.12 and rebuilding, but it gives the same error, just substituting 10.12.

I realize this could be user error, but I am not sure what at this point. Any guidance would be appreciated.

TIming out a command

I’m looking for a way to abort execution of a command based on a timeout, and ideally, a more generic condition that is periodically checked.

Since it appears this is not currently possible, I’d like to ask what interface change to add this feature would be best.

Off the cuff, I can think of changing #run(…) to have a closure argument () -> Bool that defaults to { return false }. I could then implement a timeout using some suitable timer primitive, ideally available across platforms (so probably not NSTimer / GCD?).

Before I roll up my sleeves I’d love to hear how this sounds.

SwiftShell_Speed_Tests unit tests never finish.

They work fine when compiled with debug settings, but running speed tests with that doesn't make any sense. None of the unit tests ever finish when compiled with release settings. not sure if this happened with Xcode 6.1 beta 1 or 2.

output not printed out

Hello, first of all congrats on this package, really useful!
The problem I'm facing it to print the git output from a run function.
run(bash: "git clone [...]")
I can't find a way to let the run be verbose. what ends up being instead is nothing till the repo has been cloned.

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.

How do we pipe?

let result = run("ps", "-ax", "|", "grep", "Simulator.app")

I need to capture the result so that i can run some further regex on it
:/

Is there a way to interrupt a shell cmd?

I am creating a CLI tool and need to call out to various shell commands but still want to give the user the ability to interrupt the external cmd that is running to quit the currently running script

Dynamically generated argument arrays

SwiftShell uses variadic arguments all over the place, making it unfrienfly for dynamically generated commands since Swift currently doesn't support splatting arrays. The only workaround is to build up a string and use run(bash:).

It'd be nice if SwiftShell also supported array arguments for all methods that currently accept only variadic. Thanks!

How to observe `AsyncShellTask` for user input required

What is the best way to observing an AsyncShellTask to understand if it's waiting for user input?

Example

An external script is launched via SwiftShell by using runAsync such that the execution can return immediately. Although, the script could require some user input during its execution. How can we receive a callback for this event such that the application can properly ask to the user the required input?

running a shell script

Is it possible to run a shell script with this library?

I tried this -> run("/Users/Shared/.apex/temp/update.sh")

but I was getting a Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Couldn't posix_spawn: error 8'

I looked around on stack overflow and it seems like something with setting the launch path? -> https://stackoverflow.com/questions/25559608/running-shell-script-with-nstask-causes-posix-spawn-error

I was wondering if i was missing something?

Use within a swift project

How would I use this within a swift project?
Before your latest update, I setup SwiftShell like a standalone project, built it, then embedded the framework in my Xcode project. After updating SwiftShell, my project is now crashing with a message that it can't find a symbol in the framework.
dyld: Symbol not found: __T010SwiftShell12AsyncCommandC12onCompletionACyACcF
Referenced from: /Users/user/Desktop/myApp.app/Contents/MacOS/myApp
Expected in: /Users/user/Desktop/myApp.app/Contents/MacOS/../Frameworks/SwiftShell.framework/Versions/A/SwiftShell

It's possible I don't have something right with the code, but the only difference was the compiled SwiftShell.
I'm running this:
let var1 = runAsync(bash: myCmd).onCompletion {
command in
}
Grab the output by assigning var1.stdout.read() to a variable.
Then a do, try to call .finish() and catch any errors.

Thanks

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.

ReadableStreamType.readSome crashes with error "6555 Illegal instruction: 4" when using a Release build of SwiftShell.

Very strange.

iMacen:Scripts karemorstol$ env DYLD_FRAMEWORK_PATH=../../build/Release/ ./stream_out.swift
0 swift 0x00000001073a6eb8 llvm::sys::PrintStackTrace(_sFILE) + 40
1 swift 0x00000001073a73a4 SignalHandler(int) + 452
2 libsystem_platform.dylib 0x00007fff8e7465aa _sigtramp + 26
3 CoreFoundation 0x00007fff7bb59160 _block_literal_global43 + 22976
4 SwiftShell 0x0000000109eda44e TTWCSo12NSFileHandle10SwiftShell18ReadableStreamTypeFS1_8readSomeUS1___fRQPS1_FT_GSqSS + 46
5 SwiftShell 0x0000000109ed9d35 TF10SwiftShelloi2ogFTPS_18ReadableStreamType_PS_19WriteableStreamType__T + 69
6 SwiftShell 0x000000010b4590db TF10SwiftShelloi2ogFTPS_18ReadableStreamType_PS_19WriteableStreamType__T + 22541291
7 swift 0x0000000106a1c019 llvm::JIT::runFunction(llvm::Function
, std::__1::vector<llvm::GenericValue, std::_1::allocatorllvm::GenericValue > const&) + 329
8 swift 0x0000000106cc8403 llvm::ExecutionEngine::runFunctionAsMain(llvm::Function
, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits, std::__1::allocator >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits, std::1::allocator > > > const&, char const const) + 1523
9 swift 0x00000001069053ba swift::RunImmediately(swift::CompilerInstance&, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits, std::__1::allocator >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits, std::1::allocator > > > const&, swift::IRGenOptions&, swift::SILOptions const&) + 1066
10 swift 0x00000001066fb183 frontend_main(llvm::ArrayRef, char const
, void
) + 5107
11 swift 0x00000001066f864d main + 1677
12 libdyld.dylib 0x00007fff95cc75fd start + 1
Stack dump:
0. Program arguments: /Applications/Xcode-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift -frontend -interpret ./stream_out.swift -target x86_64-apple-darwin13.3.0 -target-cpu core2 -sdk /Applications/Xcode-Beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk -F ../../build/Release/ -F /Users/karemorstol/Library/Frameworks -F /Library/Frameworks -color-diagnostics -module-name stream_out
/Users/karemorstol/Data/bin/swiftshell: line 21: 6623 Illegal instruction: 4 xcrun swift $SWIFT_FRAMEWORK_ARGUMENTS "$@"

But these work:

iMacen:Scripts karemorstol$ env DYLD_FRAMEWORK_PATH=../../build/Release/ ./print_arguments.swift 1
[./print_arguments.swift, 1]

iMacen:Scripts karemorstol$ env DYLD_FRAMEWORK_PATH=../../build/Debug ./stream_out.swift
3

iMacen:Scripts karemorstol$ xcrun swift -F ../../build/Release/ stream_out.swift
3

Changing current directory

Hey, i get that i should use main.currentdirectory instead of run("pwd") but how do i change the current directory so that when my terminal app is exited the user will end up in a place he selected from inside the app?

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?

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.