Entrée depuis le clavier dans une application de ligne de commande


92

J'essaie d'obtenir l'entrée clavier pour une application de ligne de commande pour le nouveau langage de programmation Apple Swift.

J'ai scanné les documents en vain.

import Foundation

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

Des idées?

Réponses:


136

La bonne façon de faire est d'utiliser readLine, à partir de la bibliothèque standard Swift.

Exemple:

let response = readLine()

Vous donnera une valeur facultative contenant le texte saisi.


2
Merci. y a-t-il un moyen d'obtenir une entrée de type mot de passe?
Abhijit Gaikwad

Pour moi, cela passe simplement par cette ligne avec response = nil. Une idée du problème?
Peter Webb

4
@PeterWebb - fonctionne bien dans le terminal xcode, tombe dans le terrain de jeu :)
aprofromindia

2
readLine a été ajouté après la plupart des réponses originales, donc l'attitude est un peu injustifiée à
mon humble avis

2
Corrigé pour Swift 3, SlimJim
Ezekiel Elin

62

J'ai réussi à le comprendre sans tomber en C:

Ma solution est la suivante:

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

Les versions plus récentes de Xcode nécessitent un typage explicite (fonctionne dans Xcode 6.4):

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

5
J'aime cela, aussi, il peut être compressé sur une ligne si vous ne voulez pas définir une fonction, comme si vous ne devez accepter l'entrée qu'une seule fois dans un programme:var input = NSString(data: NSFileHandle.fileHandleWithStandardInput().availableData, encoding:NSUTF8StringEncoding)
jcmiller11

3
Existe-t-il un moyen d'utiliser cela dans le Playground pour accepter les entrées utilisateur à la volée?
Rob Cameron

5
Vous ne pouvez pas l'utiliser dans la cour de récréation. Vous devez utiliser des variables là-dedans.
Chalkers

8
Notez que vous obtiendrez des nouvelles lignes dans la chaîne avec ceci. Strip them out withstring.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())
pk-nb

3
Vous obtiendrez également des caractères étranges si l'utilisateur supprime un caractère, utilise une touche fléchée, etc. AAAGGGGGHHHH POURQUOI, Swift, Pourquoi?
ybakos

13

Ce n'est en fait pas si simple, vous devez interagir avec l'API C. Il n'y a pas d'alternative à scanf. J'ai construit un petit exemple:

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);

Oh garçon - j'aurais aimé qu'il y ait quelque chose de plus trivial que ça! J'ai du mal à adapter cela à une chaîne de caractères.
Chalkers

1
Il serait préférable de créer un "UserInput.h" pour stocker les définitions de fonction, et d'inclure ce fichier dans "cliinput-Bridging-Header.h".
Alan Zhiliang Feng

7

edit Depuis Swift 2.2, la bibliothèque standard comprend readLine. Je noterai également que Swift est passé aux commentaires de document de démarque. Laissant ma réponse originale pour le contexte historique.

Juste pour être complet, voici une implémentation Swift que readlnj'utilise. Il a un paramètre facultatif pour indiquer le nombre maximal d'octets que vous souhaitez lire (qui peut ou non être la longueur de la chaîne).

Cela démontre également l'utilisation correcte des commentaires swiftdoc - Swift générera un fichier <project> .swiftdoc et Xcode l'utilisera.

///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) }
}

J'aime les commentaires swiftdoc, et le modificateur d'accès "public", je n'avais jamais vu ce genre de chose dans Swift auparavant, mais le CChar et le C-String semblent être un retour aux jeux de caractères C et 8 bits et Swift est tout au sujet du texte Unicode ... ou les outils de ligne de commande sont-ils tous ASCII ??
Kaydell

2
Je crois que getchar () renvoie ASCII. Les terminaux Unicode sont une chose dans laquelle je ne voulais pas entrer :)
russbishop

6

En général, la fonction readLine () est utilisée pour analyser les entrées de la console. Mais cela ne fonctionnera pas dans un projet iOS normal tant que vous n'aurez pas ajouté "outil de ligne de commande" .

La meilleure façon de tester, vous pouvez faire:

1. Créez un fichier macOS

entrez la description de l'image ici

2. Utilisez la fonction readLine () pour analyser la chaîne facultative depuis la console

 import Foundation

 print("Please enter some input\n")

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

Production :

Please enter some input

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

entrez la description de l'image ici


5

Une autre alternative est de lier libedit pour une édition correcte des lignes (touches fléchées, etc.) et une prise en charge facultative de l'historique. Je voulais cela pour un projet que je démarre et j'ai rassemblé un exemple de base pour la façon dont je l'ai mis en place .

Utilisation de swift

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

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

Wrapper ObjC pour exposer 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

3

Voici un exemple simple de prise d'entrée de l'utilisateur sur une application basée sur la console: Vous pouvez utiliser readLine (). Prenez l'entrée de la console pour le premier numéro, puis appuyez sur Entrée. Après cela, saisissez le deuxième numéro comme indiqué dans l'image ci-dessous:

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)

Production


2

Beaucoup de réponses dépassées à cette question. Depuis Swift 2+, la bibliothèque standard Swift contient la fonction readline () . Il renverra un Option mais il ne sera nul que si EOF a été atteint, ce qui ne se produira pas lors de l'obtention d'une entrée du clavier afin qu'il puisse être déballé en toute sécurité par la force dans ces scénarios. Si l'utilisateur n'entre rien, sa valeur (non emballée) sera une chaîne vide. Voici une petite fonction utilitaire qui utilise la récursivité pour inviter l'utilisateur jusqu'à ce qu'au moins un caractère ait été saisi:

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)")

Notez que l'utilisation de la liaison optionnelle (si let input = readLine ()) pour vérifier si quelque chose a été saisi comme proposé dans d'autres réponses n'aura pas l'effet souhaité, car il ne sera jamais nul et au moins "" lors de l'acceptation d'une saisie au clavier.

Cela ne fonctionnera pas dans un Playground ou dans tout autre environnement où vous n'avez pas accès à l'invite de commande. Il semble également avoir des problèmes dans la ligne de commande REPL.


1

Je jure devant Dieu ... la solution à ce problème tout à fait fondamental m'a échappé pendant des ANNÉES. C'est tellement simple ... mais il y a tellement d'informations vagues / mauvaises là-bas; j'espère pouvoir sauver quelqu'un de certains des terriers sans fond dans lesquels je me suis retrouvé ...

Alors, obtenons une "chaîne" de "l'utilisateur" via "la console", via stdin, d' accord ?

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

si vous le voulez SANS la nouvelle ligne de fin, ajoutez simplement ...

[ ... stringByTrimmingCharactersInSet:
                       NSCharacterSet.newlineCharacterSet];

Ta Da! ♥ ⱥ ᏪℯⅩ


4
Swift, le PO dit Swift.
HenryRootTwo

1
Il a dit osx. Tout se résume à la même chose! Embrassez votre ObjC intérieur!
Alex Gray

2
Il y a des gens qui écrivent en Swift et n'apprendront jamais l'Objectif C. Il ne s'agit pas de ce à quoi il se compile.
HenryRootTwo

0

Comme il n'y avait pas de solution sophistiquée à ce problème, j'ai créé une petite classe pour lire et analyser l'entrée standard dans Swift. Vous pouvez le trouver ici .

Exemple

Pour analyser:

+42 st_ring!
-0.987654321 12345678900
.42

Tu fais:

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"
}

Comment importer la classe StreamScanner?
Timothy Swan

@TimothySwan, comme expliqué dans la section Installation de Readme ( github.com/shoumikhin/StreamScanner#installation ). Donc, fondamentalement, vous pouvez simplement ajouter le fichier StreamScanner.swift à votre projet.
shoumikhin


0

Cela fonctionne dans xCode v6.2, je pense que c'est Swift v1.2

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

0

Si vous souhaitez lire une chaîne séparée par des espaces et diviser immédiatement la chaîne en un tableau, vous pouvez le faire:

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

par exemple.

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)")

0

Lorsque la fonction readLine () est exécutée sur Xcode, la console de débogage attend l'entrée. Le reste du code reprendra une fois la saisie terminée.

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

0

La réponse la mieux classée à cette question suggère d'utiliser la méthode readLine () pour prendre en compte les entrées de l'utilisateur à partir de la ligne de commande. Cependant, je tiens à noter que vous devez utiliser le! opérateur lors de l'appel de cette méthode pour renvoyer une chaîne au lieu d'un optionnel:

var response = readLine()!

Pourquoi le déballage forcé, readLine()retourne optionnel pour une raison, il est donc dangereux de forcer le déballage et n'ajoute vraiment rien à l'exemple.
Camsoft le

0

Swift 5: Si vous voulez en permanence une entrée depuis le clavier, sans terminer le programme, comme un flux d'entrée, utilisez les étapes ci-dessous:

  1. Créer un nouveau projet de type outil de ligne comnnad Projet en ligne de commande

    1. Ajoutez le code ci-dessous dans le fichier main.swift:

      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. Exécutez le projet et cliquez sur l'exécutable sous le dossier Produits dans Xcode et ouvrez dans le Finder
    3. Double-cliquez sur l'exécutable pour l'ouvrir.
    4. Entrez maintenant vos entrées. Le terminal ressemblera à ceci: entrez la description de l'image ici

0
var a;
scanf("%s\n", n);

J'ai testé cela dans ObjC, et peut-être que ce sera utile.


-1

Je voulais juste commenter (je n'ai pas assez de représentants) sur l'implémentation de xenadu, car CCharsous OS X est Int8, et Swift n'aime pas du tout lorsque vous ajoutez au tableau lorsque vous getchar()retournez des parties de UTF-8, ou toute autre chose au-dessus de 7 bits.

J'utilise un tableau de à la UInt8place, et cela fonctionne très bien et String.fromCStringconvertit très bien le UInt8en UTF-8.

Cependant c'est comme ça que je l'ai fait

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)")
    }
}

-1

J'ai maintenant pu obtenir une entrée clavier dans Swift en utilisant ce qui suit:

Dans mon fichier main.swift, j'ai déclaré une variable i et lui ai assigné la fonction GetInt () que j'ai définie dans l'Objectif C. Grâce à ce qu'on appelle un en-tête de pont où j'ai déclaré le prototype de fonction pour GetInt, je pouvais créer un lien vers main.swift. Voici les fichiers:

main.swift:

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

En-tête de pontage:

#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;
}

Dans obj.m, il est possible d'inclure la sortie et l'entrée standard c, stdio.h, ainsi que la bibliothèque standard c stdlib.h qui vous permet de programmer en C dans Objective-C, ce qui signifie qu'il n'est pas nécessaire d'inclure un vrai fichier rapide comme user.c ou quelque chose comme ça.

J'espère que je pourrais aider

Edit: Il n'est pas possible d'obtenir une entrée String via C car ici j'utilise le CInt -> le type entier de C et non de Swift. Il n'y a pas de type Swift équivalent pour le caractère C *. Par conséquent, String n'est pas convertible en chaîne. Mais il y a assez de solutions ici pour obtenir une entrée String.

Raul

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.