windows - 你如何让 getLine 接受 unicode 字符?
问题描述
运行以下代码时
do line <- getLine
putStrLn line
或者,
getLine >>= putStrLn
之后
getLine >>= putStrLn
进入
µ
遇到此输出:
⠀
现在,我之前已经试过chcp 65001
了,还是不行,编码stdin
是utf8
.
putStrLn
没有表演的考试:
getLine
µ
'\NIL'
我的环境:
Windows 10 Version 10.0.17134 Build 17134
Lenovo ideapad 510-15IKB
BIOS Version LENOVO 3JCN30WW
GHCi v 8.2.2
如何解决?
编辑:具体来说,以下一系列操作会导致此问题:
- 打开
cmd
- 类型
chcp 65001
- 类型
ghci
- 类型
getLine >>= putStrLn
- 类型
µ
但是,以下内容不会:
- 搜索
ghci
- 营业时间
ghci.exe
_%PROGRAMS%\Haskell Platform\8.2.2\bin
- 重复 4-5。
注意:%PROGRAMS%
不是真正的环境变量。
编辑:根据要求,输出GHC.IO.Encoding.getLocaleEncoding
:
UTF-8
此外,输出System.IO.hGetEncoding stdin
:
Just UTF-8
(使用时chcp 65001
)
编辑:字符是 U+00B5。我正在使用德语键盘,系统语言环境德国,语言设置英语,键盘语言 ENG 与德语布局。
解决方案
控制台输入/输出在 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 程序员,但在我的脑海中浮现出一些需要更改的内容,然后才能在实际程序中使用该代码:
- 最重要的部分是代码只能使用控制台句柄,这可以通过
GetConsoleMode
API 调用来确定。 - 对于其他类型的句柄,上面的代码将什么也不做,例如。如果与管道或文件句柄一起使用,它在编码方面有其自身的问题,但这是一个完全独立的问题。
- 不考虑 API 调用失败。因此,我们必须通过查看返回的 来检查调用是否成功
BOOL
,并在不使用GetLastError
时向用户报告错误。 - 上面实现的功能非常有限,没有检查它们实际上从缓冲区读取/写入了多少。出于这个原因
hwGetStrN
,只能处理n
字符,因此需要递归调用才能获得类似于hGetLine
- 做所有的健全性检查,例如。
DWORD
isWord32
,所以fromIntegral len
call 容易发生整数溢出,这既不正确也不安全。 - FFI 调用必须
stdcall
在 32 位操作系统上,而ccall
对于x86_64
,则需要一些 CPP
推荐阅读
- laravel - Laravel 导入/导出。我正在导入的 csv 文件不起作用
- python - 如何将来自两个不同数据帧的数据与pyspark结合起来?
- java - 我的代码似乎跳过了 return 语句
- laravel - Laravel Socialite 谷歌登录再次转移到登录页面
- python - 如何在涉及类名称空间的 selenium python 中刮取 li 标签?
- python - Stripe AttributeError:“str”对象没有属性“objects”(自定义用户模型)
- react-native - ClipPath 的动画不适用于 react-native-svg
- ios - 如何将参数添加到通用 UITapGestureRecognizer?
- user-interface - 在 xamarin 表单中编辑 NavigationPage 后退按钮大小
- python - 无法使用 python docker image 使用已安装的包