【问题标题】:Swift: readLine with timeout斯威夫特:readLine 超时
【发布时间】:2021-05-28 15:23:16
【问题描述】:

我正在用 Swift 编写一个命令行工具,我想获得一些用户输入。我为此使用readLine()。但是如果用户在时间范围内没有响应,我想添加一个超时来选择默认选项。这可能吗?

> Do you want to proceed? [y/n, will continue automatically in 2:00]: _

(实际更新提示中剩余时间的奖励积分。????)

【问题讨论】:

    标签: swift command-line command-line-tool


    【解决方案1】:

    不是最漂亮的 Swift 代码,但它可以胜任。原始终端模式下的 aio(异步输入输出)低级系统 API https://stackoverflow.com/a/59795707/5329717 用于在不按回车的情况下读取用户输入。

    我们正在使用aio_read(通过关注aio_return 配对)进行多次读取,因为用户可能正在输入我们不想要的键。

    由于 Xcode 调试控制台不是标准控制台,请在独立终端中运行。

    我在这段代码中遇到的唯一警告是在时间用完的情况下aio_read 将终端标准输入设置为仍期待用户输入(例如,输入键以使 shell 再次出现)。我会设法绕过这个问题。

    import Foundation
    
    // https://stackoverflow.com/a/59000106/5329717
    extension TimeInterval{
        func stringFromTimeInterval() -> String {
            let time = NSInteger(self)
            let seconds = time % 60
            let minutes = (time / 60) % 60
            let hours = (time / 3600)
            var formatString = ""
            if hours == 0 {
                if(minutes < 10) {
                    formatString = "%2d:%0.2d"
                } else {
                    formatString = "%0.2d:%0.2d"
                }
                return String(format: formatString,minutes,seconds)
            } else {
                formatString = "%2d:%0.2d:%0.2d"
                return String(format: formatString,hours,minutes,seconds)
            }
        }
    }
    
    // https://stackoverflow.com/a/59795707/5329717
    extension FileHandle {
        func enableRawMode() -> termios {
            var raw = termios()
            tcgetattr(self.fileDescriptor, &raw)
            
            let original = raw
            raw.c_lflag &= ~UInt(ECHO | ICANON)
            tcsetattr(self.fileDescriptor, TCSADRAIN, &raw)
            return original
        }
        
        func restoreRawMode(originalTerm: termios) {
            var term = originalTerm
            tcsetattr(self.fileDescriptor, TCSADRAIN, &term)
        }
    }
    
    let bufferForReadSize = 100
    var bufferForRead: UnsafeMutableRawPointer = UnsafeMutableRawPointer.allocate(byteCount: bufferForReadSize, alignment: 1)
    
    //Give the user slightly bit more than 2 minutes so that the 2:00 countdown initial value can be seen 
    let endTime = Date().addingTimeInterval(TimeInterval(exactly: 120.5)!)
    //struct for using aio_ calls
    var aio: aiocb = aiocb(aio_fildes: FileHandle.standardInput.fileDescriptor,
                           aio_offset: 0,
                           aio_buf: bufferForRead,
                           aio_nbytes: bufferForReadSize,
                           aio_reqprio: 0,
                           aio_sigevent: sigevent(),
                           aio_lio_opcode: 0)
    var userChoice: Bool?
    print()
    let originalTermios = FileHandle.standardInput.enableRawMode()
    withUnsafeMutablePointer(to: &aio) {
        while userChoice == nil {
            let timeLeft = endTime.timeIntervalSince(Date())
            print("\u{1B}[A" + //rewind to previous line +
                "Hello, World? (y/n)" + timeLeft.stringFromTimeInterval())
    
            let inputString = String(cString: bufferForRead.bindMemory(to: Int8.self, capacity: bufferForReadSize))
            if inputString.starts(with: "y") || inputString.starts(with: "Y") {
                userChoice = true
                break
            } else if inputString.starts(with: "n") || inputString.starts(with: "N") {
                userChoice = false
                break
            }
            
            if timeLeft <= 0 {
                userChoice = true
                break
            } else {
                //Async IO read
                aio_read($0)
                CFRunLoopRunInMode(CFRunLoopMode.defaultMode,
                                   0.5, //choose the interval value depending on the fps you need
                                   false)
                //Async IO return
                aio_return($0)
            }
        }
    }
    FileHandle.standardInput.restoreRawMode(originalTerm: originalTermios)
    userChoice! ? print("Thanks for choosing YES. Bye") : print("Thanks for choosing NO. Bye")
    

    【讨论】:

      猜你喜欢
      • 2017-07-22
      • 2017-03-24
      • 1970-01-01
      • 1970-01-01
      • 2016-02-08
      • 2017-11-13
      • 2015-08-19
      • 2014-09-11
      • 1970-01-01
      相关资源
      最近更新 更多