从键盘输入命令行应用程序

我正在尝试获取新的苹果编程语言 Swift 的命令行应用程序的键盘输入。

我扫描了文件,没有结果。

import Foundation


println("What is your name?")
???

有什么想法吗?

123551 次浏览

It's actually not that easy, you have to interact with the C API. There is no alternative to scanf. I've build a little example:

main.swift

import Foundation


var output: CInt = 0
getInput(&output)


println(output)


UserInput.c

#include <stdio.h>


void getInput(int *output) {
scanf("%i", output);
}


cliinput-Bridging-Header.h

void getInput(int *output);

I managed to figure it out without dropping down in to C:

My solution is as follows:

func input() -> String {
var keyboard = NSFileHandle.fileHandleWithStandardInput()
var inputData = keyboard.availableData
return NSString(data: inputData, encoding:NSUTF8StringEncoding)!
}

More recent versions of Xcode need an explicit typecast (works in Xcode 6.4):

func input() -> String {
var keyboard = NSFileHandle.fileHandleWithStandardInput()
var inputData = keyboard.availableData
return NSString(data: inputData, encoding:NSUTF8StringEncoding)! as String
}

Another alternative is to link libedit for proper line editing (arrow keys, etc.) and optional history support. I wanted this for a project I'm starting and put together a basic example for how I set it up.

Usage from swift

let prompt: Prompt = Prompt(argv0: C_ARGV[0])


while (true) {
if let line = prompt.gets() {
print("You typed \(line)")
}
}

ObjC wrapper to expose libedit

#import <histedit.h>


char* prompt(EditLine *e) {
return "> ";
}


@implementation Prompt


EditLine* _el;
History* _hist;
HistEvent _ev;


- (instancetype) initWithArgv0:(const char*)argv0 {
if (self = [super init]) {
// Setup the editor
_el = el_init(argv0, stdin, stdout, stderr);
el_set(_el, EL_PROMPT, &prompt);
el_set(_el, EL_EDITOR, "emacs");


// With support for history
_hist = history_init();
history(_hist, &_ev, H_SETSIZE, 800);
el_set(_el, EL_HIST, history, _hist);
}


return self;
}


- (void) dealloc {
if (_hist != NULL) {
history_end(_hist);
_hist = NULL;
}


if (_el != NULL) {
el_end(_el);
_el = NULL;
}
}


- (NSString*) gets {


// line includes the trailing newline
int count;
const char* line = el_gets(_el, &count);


if (count > 0) {
history(_hist, &_ev, H_ENTER, line);


return [NSString stringWithCString:line encoding:NSUTF8StringEncoding];
}


return nil;
}


@end

edit As of Swift 2.2 the standard library includes readLine. I'll also note Swift switched to markdown doc comments. Leaving my original answer for historical context.

Just for completeness, here is a Swift implementation of readln I've been using. It has an optional parameter to indicate the maximum number of bytes you want to read (which may or may not be the length of the String).

This also demonstrates the proper use of swiftdoc comments - Swift will generate a <project>.swiftdoc file and Xcode will use it.

///reads a line from standard input
///
///:param: max specifies the number of bytes to read
///
///:returns: the string, or nil if an error was encountered trying to read Stdin
public func readln(max:Int = 8192) -> String? {
assert(max > 0, "max must be between 1 and Int.max")


var buf:Array<CChar> = []
var c = getchar()
while c != EOF && c != 10 && buf.count < max {
buf.append(CChar(c))
c = getchar()
}


//always null terminate
buf.append(CChar(0))


return buf.withUnsafeBufferPointer { String.fromCString($0.baseAddress) }
}

I swear to God.. the solution to this utterly basic problem eluded me for YEARS. It's SO simple.. but there is so much vague / bad information out there; hopefully I can save someone from some of the bottomless rabbit holes that I ended up in...

So then, lets's get a "string" from "the user" via "the console", via stdin, shall we?

[NSString.alloc initWithData:
[NSFileHandle.fileHandleWithStandardInput availableData]
encoding:NSUTF8StringEncoding];

if you want it WITHOUT the trailing newline, just add...

[ ... stringByTrimmingCharactersInSet:
NSCharacterSet.newlineCharacterSet];

Ta Da! ♥ ⱥᏪℯⅩ

I just wanted to comment (I have not enough reps) on xenadu's implementation, because CChar in OS X is Int8, and Swift does not like at all when you add to the array when getchar() returns parts of UTF-8, or anything else above 7 bit.

I am using an array of UInt8 instead, and it works great and String.fromCString converts the UInt8 into UTF-8 just fine.

However this is how I done it

func readln() -> (str: String?, hadError: Bool) {
var cstr: [UInt8] = []
var c: Int32 = 0
while c != EOF {
c = getchar()
if (c == 10 || c == 13) || c > 255 { break }
cstr.append(UInt8(c))
}
cstr.append(0)
return String.fromCStringRepairingIllFormedUTF8(UnsafePointer<CChar>(cstr))
}


while true {
if let mystring = readln().str {
println(" > \(mystring)")
}
}

I have now been able to get Keyboard input in Swift by using the following:

In my main.swift file I declared a variable i and assigned to it the function GetInt() which I defined in Objective C. Through a so called Bridging Header where I declared the function prototype for GetInt I could link to main.swift. Here are the files:

main.swift:

var i: CInt = GetInt()
println("Your input is \(i) ");

Bridging Header:

#include "obj.m"


int GetInt();

obj.m:

#import <Foundation/Foundation.h>
#import <stdio.h>
#import <stdlib.h>


int GetInt()
{
int i;
scanf("%i", &i);
return i;
}

In obj.m it is possible to include the c standard output and input, stdio.h, as well as the c standard library stdlib.h which enables you to program in C in Objective-C, which means there is no need for including a real swift file like user.c or something like that.

Hope I could help,

Edit: It is not possible to get String input through C because here I am using the CInt -> the integer type of C and not of Swift. There is no equivalent Swift type for the C char*. Therefore String is not convertible to string. But there are fairly enough solutions around here to get String input.

Raul

Since there were no fancy solutions to this problem, I made a tiny class to read and parse the standard input in Swift. You can find it here.

Example

To parse:

+42 st_ring!
-0.987654321 12345678900
.42

You do:

let stdin = StreamScanner.standardInput


if
let i: Int = stdin.read(),
let s: String = stdin.read(),
let d: Double = stdin.read(),
let i64: Int64 = stdin.read(),
let f: Float = stdin.read()
{
print("\(i) \(s) \(d) \(i64) \(f)")  //prints "42 st_ring! -0.987654321 12345678900 0.42"
}

The correct way to do this is to use readLine, from the Swift Standard Library.

Example:

let response = readLine()

Will give you an Optional value containing the entered text.

Here is simple example of taking input from user on console based application: You can use readLine(). Take input from console for first number then press enter. After that take input for second number as shown in the image below:

func solveMefirst(firstNo: Int , secondNo: Int) -> Int {
return firstNo + secondNo
}


let num1 = readLine()
let num2 = readLine()


var IntNum1 = Int(num1!)
var IntNum2 = Int(num2!)


let sum = solveMefirst(IntNum1!, secondNo: IntNum2!)
print(sum)

Output

Before

enter image description here

*******************.

Correction

enter image description here

This works in xCode v6.2, I think that's Swift v1.2

func input() -> String {
var keyboard = NSFileHandle.fileHandleWithStandardInput()
var inputData = keyboard.availableData
return NSString(data: inputData, encoding:NSUTF8StringEncoding)! as String
}

If you want to read space separated string, and immediately split the string into an array, you can do this:

var arr = readLine()!.characters.split(" ").map(String.init)

eg.

print("What is your full name?")


var arr = readLine()!.characters.split(" ").map(String.init)


var firstName = ""
var middleName = ""
var lastName = ""


if arr.count > 0 {
firstName = arr[0]
}
if arr.count > 2 {
middleName = arr[1]
lastName = arr[2]
} else if arr.count > 1 {
lastName = arr[1]
}


print("First Name: \(firstName)")
print("Middle Name: \(middleName)")
print("Last Name: \(lastName)")

When readLine() function is run on Xcode, the debug console waits for input. The rest of the code will be resumed after input is done.

    let inputStr = readLine()
if let inputStr = inputStr {
print(inputStr)
}

In general readLine() function is used for scanning input from console. But it will not work in normal iOS project until or unless you add "command-line tool".

The best way for testing, you can do :

1. Create an macOS file

enter image description here

2. Use the readLine() func to scan optional String from console

 import Foundation


print("Please enter some input\n")


if let response = readLine() {
print("output :",response)
} else {
print("Nothing")
}

Output :

Please enter some input


Hello, World
output : Hello, World
Program ended with exit code: 0

enter image description here

The top ranked answer to this question suggests using the readLine() method to take in user input from the command line. However, I want to note that you need to use the ! operator when calling this method to return a string instead of an optional:

var response = readLine()!

Lots of outdated answers to this question. As of Swift 2+ the Swift Standard Library contains the readline() function. It will return an Optional but it will only be nil if EOF has been reached, which will not happen when getting input from the keyboard so it can safely be unwrapped by force in those scenarios. If the user does not enter anything its (unwrapped) value will be an empty string. Here's a small utility function that uses recursion to prompt the user until at least one character has been entered:

func prompt(message: String) -> String {
print(message)
let input: String = readLine()!
if input == "" {
return prompt(message: message)
} else {
return input
}
}


let input = prompt(message: "Enter something!")
print("You entered \(input)")

Note that using optional binding (if let input = readLine()) to check if something was entered as proposed in other answers will not have the desired effect, as it will never be nil and at least "" when accepting keyboard input.

This will not work in a Playground or any other environment where you does not have access to the command prompt. It seems to have issues in the command-line REPL as well.

Swift 5 : If you continuously want input from keyboard , without ending the program, like a stream of input, Use below steps:

  1. Create new project of type comnnad line tool Command line project

    1. Add below code in main.swift file:

      var inputArray = [String]()
      
      
      while let input = readLine() {
      
      
      guard input != "quit" else {
      
      
      break
      
      
      }
      
      
      inputArray.append(input)
      
      
      print("You entered: \(input)")
      
      
      print(inputArray)
      
      
      print("Enter a word:")
      }
      
    2. RunThe project and click the executable under Products folder in Xcode and open in finder
    3. Double click the executable to open it.
    4. Now enter your Inputs. Terminal will look something like this: enter image description here
var a;
scanf("%s\n", n);

I tested this out in ObjC, and maybe this will be useful.

import Foundation
print("Enter a number")
let number : Int = Int(readLine(strippingNewline: true)!) ?? 0
if(number < 5)
{
print("Small")
}else{
print("Big")
}
for i in 0...number{
print(i)
}