2
votes

I'm trying to write a Swift program that

  • Accepts either a single argument or piped data from standard input, and prints it
  • Prints a usage message if both are missing.

Assuming the code is contained in main.swift, we have four cases:

  1. swift main.swift should output "Please provide some input"
  2. swift main.swift argument should output "argument"
  3. echo | swift main.swift should output "Please provide some input"
  4. echo argument | swift main.swift should output "argument"

In the case of a compound echo argument1 | swift main.swift argument2, argument2 takes precedence.

Satisfying 1-3 is simple:

import Foundation

var input: String? = nil

if CommandLine.arguments.count > 1 {
    input = CommandLine.arguments[1]
}

guard let input = input else {
    print("Please provide some input")
    exit(0)
}

print(input)

However, echo argument | swift main.swift obviously prints the usage message, as there are no arguments. Adding some code,

import Foundation

var input: String? = nil

if CommandLine.arguments.count > 1 {
    input = CommandLine.arguments[1]
} else {
    while let line = readLine() {
        if input == nil {
            if line.isEmpty { break }
            input = line
        } else {
            input! += "\n" + line
        }
    }
}

guard let input = input else {
    print("Please provide some input")
    exit(0)
}

print(input)

Now cases 2-4 work as expected, but case 1 is problematic. The readLine() causes execution to pause, waiting for input. If you press return without input, the proper message is returned, but I'd like to avoid the necessity to manually enter a blank line.

How can I read enable reading from stdin, while not causing a pause when stdin is blank and there are no arguments?

2

2 Answers

1
votes

I believe that the problem is that readLine() is going to just wait for some sort of input, even if it's just using ctrl-d to send an end of line to it. For your use, would it be possible to add a parameter that tells the program that it should wait for input? Otherwise, it can just assume that it shouldn't wait around for anything?

For example:

import Foundation

func readInput () -> String? {
    var input:String?

    while let line = readLine() {
        if input == nil {
            if line.isEmpty { break }
            input = line
        } else {
            input! += "\n" + line
        }
    }

    return input
}

var input: String? = nil

if CommandLine.arguments.count > 1 {
    input = CommandLine.arguments[1]

    if (input == "-i") {
        input = readInput()
    }
}

guard let input = input else {
    print("Please provide some input")
    exit(0)
}

print("input: \(input)")

Then you end up with usage and output like so:

Screenshot of program output.

0
votes

Josh Buhler provided the missing piece — passing a flag to enable reading stdin. Here's my final, simplified solution for completeness.

import Foundation

guard CommandLine.arguments.count > 1 else {
    print("Please provide an argument, or pass - to read stdin")
    exit(0)
}

var input = CommandLine.arguments[1]

if input == "-" {
    input = AnyIterator { readLine() }.joined(separator: "\n")
}

// do something with input
print(input)