首页 > 解决方案 > 你如何让 getLine 接受 unicode 字符?

问题描述

运行以下代码时

do line <- getLine
   putStrLn line

或者,

getLine >>= putStrLn

之后

 getLine >>= putStrLn

进入

µ

遇到此输出:

现在,我之前已经试过chcp 65001了,还是不行,编码stdinutf8.

putStrLn没有表演的考试:

 getLine
µ
'\NIL'

我的环境:
Windows 10 Version 10.0.17134 Build 17134
Lenovo ideapad 510-15IKB
BIOS Version LENOVO 3JCN30WW
GHCi v 8.2.2

如何解决?

编辑:具体来说,以下一系列操作会导致此问题:

  1. 打开cmd
  2. 类型chcp 65001
  3. 类型ghci
  4. 类型getLine >>= putStrLn
  5. 类型µ

但是,以下内容不会:

  1. 搜索ghci
  2. 营业时间ghci.exe_%PROGRAMS%\Haskell Platform\8.2.2\bin
  3. 重复 4-5。

注意:%PROGRAMS%不是真正的环境变量。

编辑:根据要求,输出GHC.IO.Encoding.getLocaleEncoding

UTF-8

此外,输出System.IO.hGetEncoding stdin

Just UTF-8

(使用时chcp 65001

编辑:字符是 U+00B5。我正在使用德语键盘,系统语言环境德国,语言设置英语,键盘语言 ENG 与德语布局。

标签: windowshaskellunicodeencodingconsole

解决方案


控制台输入/输出在 Windows 上完全损坏,并且已经有一段时间了。这是在 Windows 上跟踪与 IO 相关的所有问题的最高票: https ://ghc.haskell.org/trac/ghc/ticket/11394

我相信,这两张票最能描述您所遇到的行为:

目前唯一的解决方法是手动使用 Windows API 来处理控制台输出/输入,这本身就是一种痛苦。

编辑

所以,为了它,我决定忍受一些痛苦。:)

下面是代码的输出:

====
Input: µ
Output: µ
====

这绝不是一个完全正确或安全的解决方案,但它确实有效:

module Main where

import Control.Monad
import System.IO
import Foreign.Ptr
import Foreign.ForeignPtr
import Foreign.C.String
import Foreign.C.Types
import Foreign.Storable

import System.Win32
import System.Win32.Types
import Graphics.Win32.Misc

foreign import ccall unsafe "windows.h WriteConsoleW"
  c_WriteConsoleW :: HANDLE -> LPWSTR -> DWORD -> LPDWORD -> LPVOID -> IO BOOL

foreign import ccall unsafe "windows.h ReadConsoleW"
  c_ReadConsoleW :: HANDLE -> LPWSTR -> DWORD -> LPDWORD -> LPVOID -> IO BOOL

-- | Read n characters from a handle, which should be a console stdin
hwGetStrN :: Int -> Handle -> IO String
hwGetStrN maxLen hdl = do
  withCWStringLen (Prelude.replicate maxLen '\NUL') $ \(cstr, len) -> do
    lpNumberOfCharsWrittenForeignPtr <- mallocForeignPtr
    withHandleToHANDLE hdl $ \winHANDLE ->
      withForeignPtr lpNumberOfCharsWrittenForeignPtr $ \lpNumberOfCharsRead -> do
        c_ReadConsoleW winHANDLE cstr (fromIntegral len) lpNumberOfCharsRead nullPtr
        numWritten <- peek lpNumberOfCharsRead
        peekCWStringLen (cstr, fromIntegral numWritten)

-- | Write a string to a handle, which should be a console stdout or stderr.
hwPutStr :: Handle -> String -> IO ()
hwPutStr hdl str = do
  void $ withCWStringLen str $ \(cstr, len) -> do
    lpNumberOfCharsWrittenForeignPtr <- mallocForeignPtr
    withHandleToHANDLE hdl $ \winHANDLE ->
      withForeignPtr lpNumberOfCharsWrittenForeignPtr $ \ lpNumberOfCharsWritten ->
      c_WriteConsoleW winHANDLE cstr (fromIntegral len) lpNumberOfCharsWritten nullPtr

main :: IO ()
main = do
  hwPutStr stdout "====\nInput: "
  str <- hwGetStrN 10 stdin
  hwPutStr stdout "Output: "
  hwPutStr stdout str
  hwPutStr stdout "====\n"

编辑 2

@dfeuer 要求我列出该答案不安全、不正确或不完整的内容。我只在 Linux 上编写代码,所以我不是 Windows 程序员,但在我的脑海中浮现出一些需要更改的内容,然后才能在实际程序中使用该代码:

  • 最重要的部分是代码只能使用控制台句柄,这可以通过GetConsoleModeAPI 调用来确定。
  • 对于其他类型的句柄,上面的代码将什么也不做,例如。如果与管道或文件句柄一起使用,它在编码方面有其自身的问题,但这是一个完全独立的问题。
  • 不考虑 API 调用失败。因此,我们必须通过查看返回的 来检查调用是否成功BOOL,并在不使用GetLastError时向用户报告错误。
  • 上面实现的功能非常有限,没有检查它们实际上从缓冲区读取/写入了多少。出于这个原因hwGetStrN,只能处理n字符,因此需要递归调用才能获得类似于hGetLine
  • 做所有的健全性检查,例如。DWORDis Word32,所以fromIntegral lencall 容易发生整数溢出,这既不正确也不安全。
  • FFI 调用必须stdcall在 32 位操作系统上,而ccall对于x86_64,则需要一些 CPP

推荐阅读