Консольный вывод байтовых строк - удаляет первый символ следующей строки

Отвечая на /questions/29315527/haskell-konvertiruet-yunikodnuyu-posledovatelnost-v-utf-8 я натолкнулся на странное поведение ByteString.putStrLn

{-# LANGUAGE OverloadedStrings #-}

module Main where

import           Data.Text (Text)
import           Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as B


inputB, inputB' :: ByteString
inputB = "ДЕЖЗИЙКЛМНОПРСТУФ"
inputB' = "test"


main :: IO ()
main = do putStr "B.putStrLn inputB: "; B.putStrLn inputB
          putStr "print inputB: "; print inputB
          putStr "B.putStrLn inputB': "; B.putStrLn inputB'
          putStr "print inputB': "; print inputB'

который дает

B.putStrLn inputB:
rint inputB: "\DC4\NAK\SYN\ETB\CAN\EM\SUB\ESC\FS\GS\RS\US !\"#$"
B.putStrLn inputB': test
print inputB': "test"

то, что я не понимаю, здесь - почему отсутствует первая строка вывода и отсутствует буква p во второй строке.

Я предполагаю, что это как-то связано с русскими буквами, приводящими к неправильному вводу. Потому что с простым случаем "теста" это просто работает.


редактировать

  • Платформа: Linux Mint 17.3
  • кодировка файла: UTF-8
  • терминал: гном-терминал /tmux/zsh
  • GHC: 7,10,3
  • стек: 1.0.4

выход xxd

> stack exec -- unicode | xxd
00000000: 422e 7075 7453 7472 4c6e 2069 6e70 7574  B.putStrLn input
00000010: 423a 2014 1516 1718 191a 1b1c 1d1e 1f20  B: ............
00000020: 2122 2324 0a70 7269 6e74 2069 6e70 7574  !"#$.print input
00000030: 423a 2022 5c44 4334 5c4e 414b 5c53 594e  B: "\DC4\NAK\SYN
00000040: 5c45 5442 5c43 414e 5c45 4d5c 5355 425c  \ETB\CAN\EM\SUB\
00000050: 4553 435c 4653 5c47 535c 5253 5c55 5320  ESC\FS\GS\RS\US
00000060: 215c 2223 2422 0a42 2e70 7574 5374 724c  !\"#$".B.putStrL
00000070: 6e20 696e 7075 7442 273a 2074 6573 740a  n inputB': test.
00000080: 7072 696e 7420 696e 7075 7442 273a 2022  print inputB': "
00000090: 7465 7374 220a                           test".

библиотеки

> stack exec -- ghc-pkg list
/opt/ghc/7.10.3/lib/ghc-7.10.3/package.conf.d
   Cabal-1.22.5.0
   array-0.5.1.0
   base-4.8.2.0
   bin-package-db-0.0.0.0
   binary-0.7.5.0
   bytestring-0.10.6.0
   containers-0.5.6.2
   deepseq-1.4.1.1
   directory-1.2.2.0
   filepath-1.4.0.0
   ghc-7.10.3
   ghc-prim-0.4.0.0
   haskeline-0.7.2.1
   hoopl-3.10.0.2
   hpc-0.6.0.2
   integer-gmp-1.0.0.0
   pretty-1.1.2.0
   process-1.2.3.0
   rts-1.0
   template-haskell-2.10.0.0
   terminfo-0.4.0.1
   time-1.5.0.1
   transformers-0.4.2.0
   unix-2.7.1.0
   xhtml-3000.2.1
/home/epsilonhalbe/.stack/snapshots/x86_64-linux/lts-5.5/7.10.3/pkgdb
   text-1.2.2.0
/home/epsilonhalbe/programming/unicode/.stack-work/install/x86_64-linux/lts-5.5/7.10.3/pkgdb

и язык

> locale
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC=de_AT.UTF-8
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY=de_AT.UTF-8
LC_MESSAGES="en_US.UTF-8"
LC_PAPER=de_AT.UTF-8
LC_NAME=de_AT.UTF-8
LC_ADDRESS=de_AT.UTF-8
LC_TELEPHONE=de_AT.UTF-8
LC_MEASUREMENT=de_AT.UTF-8
LC_IDENTIFICATION=de_AT.UTF-8
LC_ALL=

2 ответа

Решение

Это не терминальная проблема, скорее, проблема возникает в начале преобразования в ByteString. Помните, потому что вы использовали OverloadedStrings

inputB = "ДЕЖЗИЙКЛМНОПРСТУФ"

действительно сокращение для

inputB = fromString "ДЕЖЗИЙКЛМНОПРСТУФ"::ByteString

который не конвертируется в строку байтов с использованием UTF8.

Если вместо этого вы хотите, чтобы строка байтов содержала символы в кодировке utf8, используйте

import qualified Data.ByteString.UTF8 as BU

inputB = BU.fromString "ДЕЖЗИЙКЛМНОПРСТУФ"

тогда это будет работать

B.putStrLn inputB

Почему "р" на второй строке отсутствует?

Я не буду вдаваться в подробности (потому что я их не знаю), но поведение ожидается... Потому что ваш терминал ожидает UTF8, а русская строка не UTF8.

UTF8 использует кодировки байтов переменной длины.... В зависимости от первого байта в символе, он может ожидать большего. Очевидно, что последний байт в русской строке запустил кодировку UTF8, для которой требовалось больше байтов, и в этот символ было прочитано "p". Ваш терминал, кажется, просто игнорирует символы, которые он не может распечатать (мой выводит мусор), поэтому и русская строка, и следующий символ были потеряны.

Вы заметите, что "р" находится в выводе xxd.... Терминал просто считает его частью неизвестных символов и не печатает его.

Цитирование из документации Data.ByteString.Char8 (акцент мой)

Управляйте ByteStrings, используя операции Char. Все символы будут усечены до 8 бит. Можно ожидать, что эти функции будут работать с той же скоростью, что и их эквиваленты в Word8 в Data.ByteString.

Более конкретно, эти байтовые строки считаются находящимися в подмножестве Unicode, покрытом кодовыми точками 0-255. Это охватывает Unicode Basic Latin, Latin-1 Supplement и C0+C1 Controls.

Кириллица не выделяется в кодовых точках 0x00-0xFF, поэтому следует ожидать проблем с кодированием.

Я бы рекомендовал против Data.ByteString.Char8 если вы не имеете дело с простым ASCII. Даже если текст в кодировке латинского алфавита 1 может работать в определенных условиях, кодировка латинского алфавита устарела и должна умереть.

Для обработки общих строк используйте Data.Text вместо. Функции преобразования из ByteString с Text и наоборот, предоставляются. Конечно, эти функции должны зависеть от некоторой кодировки.

Другие вопросы по тегам