Introducción a la codificación de texto en Windows - Mojibake al integrar con Linux

· Actualizado el: · · Windows, Mojibake, UTF-8, CP932, Linux, PowerShell, Unicode

El mojibake de Windows no se produce porque el japonés sea difícil. En la mayoría de los casos, la causa es haber leído la misma secuencia de bytes con un código de caracteres distinto o haber guardado el resultado de una lectura errónea con otro código de caracteres.

Esto se agrava especialmente al cruzar entre Windows y Linux: en el lado de Windows conviven varios contextos —CP932, UTF-8, UTF-16, la página de códigos de la consola, las diferencias entre versiones de PowerShell—, mientras que en el lado de Linux casi todo se procesa bajo el supuesto de UTF-8. Esa combinación hace que desajustes de suposiciones normalmente invisibles salgan a la luz de golpe.

Más que una cuestión de lo complicado que es procesar japonés, esto es una cuestión de si se logra alinear bajo qué suposición se están tratando los bytes. En este artículo ordenamos la codificación de caracteres de Windows desde el punto de vista de «por qué se produce el mojibake» y resumimos, con un enfoque práctico, los puntos donde aumentan los incidentes al combinarlo con Linux.

Este artículo está dirigido a quienes generan archivos CSV, registros o archivos de configuración en Windows y los pasan al lado de Linux (o hacen lo contrario), y quieren aprender a diagnosticar y recuperarse del mojibake por sí mismos. No se presupone conocimiento de ningún lenguaje o framework en particular. Los ejemplos de comandos usan iconv y PowerShell.

1. Lo primero que hay que entender

Antes de entrar en detalle, estos son los seis puntos clave:

  • El mojibake no es un problema de «caracteres», sino de «cómo se interpretó la secuencia de bytes».
  • En Windows coexisten el mundo Unicode y el de las páginas de código heredadas (legacy), de modo que incluso dentro de un mismo equipo las suposiciones cambian según el contexto.
  • En el lado de Linux predomina fuertemente el supuesto de UTF-8, por lo que mezclar CP932 o UTF-16 procedentes de Windows tiende a provocar incidentes.
  • Conviene distinguir entre la etapa en la que solo se ha deteriorado la visualización y la etapa en la que ya se ha guardado un contenido dañado.
  • Lo más seguro es elegir UTF-8 como primera opción para los textos nuevos y mantener los archivos legacy existentes tal cual hasta que exista una tarea explícita de migración.
  • La codificación del archivo, la codificación del editor, la página de códigos de la consola y el formato interno de cadenas de la aplicación son cosas distintas. Confundirlas hace que la investigación se pierda.

Decir simplemente «se produjo mojibake en Windows» no permite identificar la causa. Como mínimo hay que determinar cuál de los siguientes elementos está desalineado:

  • El código de caracteres del propio archivo
  • El código de caracteres usado al guardar
  • La interpretación del editor
  • La página de códigos de entrada/salida de la consola
  • El formato interno de cadenas de la aplicación
  • El locale del lado de Linux y la codificación asumida

1.1 Términos que aparecen a continuación

A continuación resumimos brevemente las abreviaturas que aparecerán en el texto sin más explicación.

Término Nombre completo Significado en este artículo
BOM Byte Order Mark Marca de unos pocos bytes colocada al inicio del archivo. Indica al lector qué codificación Unicode se está usando y, en UTF-16, también el orden de los bytes. En UTF-8 es opcional
code page (página de códigos) - Mecanismo mediante el cual Windows identifica con un número «con qué código de caracteres legacy interpretar el texto». CP932, usado en el Windows japonés, es uno de esos números
ANSI - Forma en que Windows se refiere a la «página de códigos activa en ese momento». Su identidad real varía según el entorno; en un entorno japonés es CP932
locale - Configuración que agrupa los valores predeterminados de idioma, región y código de caracteres. En Linux se especifica mediante LANG o LC_ALL e incluye la codificación, como en ja_JP.UTF-8
WSL Windows Subsystem for Linux Mecanismo para ejecutar Linux sobre Windows. Como las suposiciones de Windows y de Linux conviven dentro de un mismo equipo, es un lugar propenso a los incidentes descritos en este artículo
ETL Extract / Transform / Load Proceso que extrae datos, los transforma y los vuelve a escribir. Como en el camino se vuelve a leer y a guardar el archivo, es un punto donde la codificación puede cambiar

2. Qué es realmente el mojibake

En el fondo, el mojibake es bastante simple.

  1. Una cadena de texto se codifica (encode) con algún código de caracteres para convertirla en una secuencia de bytes
  2. Esa secuencia de bytes se decodifica (decode) con algún código de caracteres para volver a convertirla en una cadena de texto
  3. Si las suposiciones usadas al codificar y al decodificar no coinciden, el resultado se lee como una cadena de texto distinta

Por ejemplo, si guardamos en UTF-8, la secuencia de bytes resultante es la siguiente.

E3 81 82

Si esta secuencia de bytes se lee como UTF-8, se obtiene , pero si se lee en el contexto de CP932 aparece como una cadena distinta, algo como 縺�. Esto es el mojibake.

Lo importante es que lo que ocurre aquí no es que «el japonés se haya roto», sino simplemente que la interpretación de los mismos bytes se ha desalineado.

Veamos también el caso inverso. Si guardamos en CP932, la secuencia de bytes queda así.

82 A0

Si intentamos leer esta secuencia de bytes como UTF-8, ni 0x82 ni 0xA0 son válidos como primer byte de una secuencia UTF-8, por lo que ambos se convierten en caracteres de sustitución y el resultado se ve como ��. Desde el punto de vista de UTF-8, en realidad ni siquiera llega a constituir un carácter.

Un ejemplo un poco más largo muestra mejor la particularidad. Si guardamos 日本語 en CP932, la secuencia de bytes es la siguiente.

93 FA 96 7B 8C EA

Al leer esto como UTF-8 se obtiene algo como ���{��. Lo que conviene observar aquí es que solo el cuarto byte, 0x7B, pasa sin cambios como el carácter ASCII {. Como el segundo byte de un carácter CP932 a veces cae dentro del rango ASCII, en el resultado del mojibake se cuelan símbolos como { o \.

En otras palabras, los síntomas difieren según la dirección.

Secuencia de bytes real Suposición del lector Aspecto resultante
UTF-8 CP932 Aparecen kanji o katakana que parecen plausibles, como
CP932 UTF-8 El texto queda plagado del carácter de sustitución , con algún símbolo ASCII como { mezclado ocasionalmente

Esta asimetría es útil de recordar porque permite hacer una primera hipótesis: «si aparecen kanji ilegibles, probablemente se está leyendo UTF-8 como CP932»; «si el texto está lleno de caracteres de sustitución, probablemente se está leyendo CP932 como UTF-8». Conocerla acelera el diagnóstico.

2.1 Si solo se ha deteriorado la visualización, a veces todavía se puede recuperar

El mojibake tiene una etapa en la que aún es reversible. Por ejemplo, si los bytes originales no han cambiado, en algunos casos basta con volver a abrir el archivo con la codificación correcta para recuperarlo.

Por el contrario, lo peligroso es un flujo como el siguiente.

  1. Se lee erróneamente un archivo UTF-8 como si fuera CP932
  2. En pantalla aparece algo como 縺�
  3. Se guarda tal cual «la cadena que se ve en pantalla»
  4. Se pierden los bytes UTF-8 originales

Una vez alcanzada esta etapa, ya no se trata de un simple deterioro visual, sino de corrupción de datos.

2.2 Es aún más peligroso reducir «caracteres irrepresentables» a una página de códigos estrecha

Otro incidente típico ocurre al convertir una cadena Unicode a una página de códigos legacy como CP932.

Por ejemplo, si el texto contiene un carácter que no existe en la página de códigos de destino, puede ocurrir lo siguiente:

  • se sustituye por ?
  • se inserta el carácter de sustitución
  • se convierte a otro carácter parecido
  • la conversión falla directamente

Este tipo de incidente no debe evaluarse solo por si el texto se puede leer o no, sino por si, al convertirlo de ida y vuelta, se recupera el original. Un carácter que ya se ha perdido no se puede restaurar aunque después se conozca la codificación correcta.

3. Por qué Windows tiende a complicarse tanto

Windows no es complicado simplemente porque sea antiguo. Lo es porque el mundo de Unicode y el mundo de las páginas de código legacy siguen coexistiendo hoy en día.

3.1 En la API de Windows coexisten la familia Unicode y la familia de páginas de código

La API de Windows se divide, a grandes rasgos, en dos familias:

  • Familia W: wide character. La familia que trata Unicode mediante UTF-16
  • Familia A: la familia de páginas de código, llamada ANSI

Es decir, dentro de Windows existen desde el principio dos caminos: «tratar el texto como Unicode» y «tratarlo según la página de códigos activa en ese momento». Por eso, incluso dentro de un mismo Windows, las suposiciones cambian según qué API o qué herramienta se haya usado.

3.2 «El japonés de Windows» no es uno solo

En el entorno del japonés en Windows, en la práctica suelen mezclarse estos cuatro elementos:

  • CP932: aparece con frecuencia en textos legacy del Windows japonés
  • UTF-8: cada vez más presente en activos de texto nuevos, en la web y en entornos multiplataforma
  • UTF-16LE: sigue apareciendo con normalidad en el contexto de herramientas y API de Windows
  • La página de códigos de la consola: una capa aparte que afecta a la entrada/salida de cmd.exe y de algunas herramientas de consola

Lo importante aquí es que ejecutar chcp 65001 no convierte automáticamente el archivo en UTF-8. Cambiar la página de códigos de la consola y saber qué bytes tiene realmente un archivo existente son cuestiones distintas.

Por cierto, es habitual llamar de forma imprecisa «Shift_JIS» al texto legacy del Windows japonés, pero en la práctica es mejor tener presente el nombre CP932, porque así la conversación se desvía menos. Como mínimo, deja claro que se está hablando de una codificación legacy japonesa originada en Windows.

3.3 El nombre del archivo y su contenido son cuestiones distintas

Cuando en Windows el nombre de archivo en japonés se ve con normalidad, es fácil pensar que «entonces el contenido también estará bien». Ahí está el peligro.

  • La capa que trata las rutas y los nombres de archivo
  • La capa que lee el contenido del archivo
  • La capa que lo muestra en la consola

Estas tres capas son distintas.

Por ejemplo, aunque una ruta en japonés se maneje sin problemas, si el contenido del archivo se guardó en CP932 y se lee en el lado de Linux como UTF-8, se dañará. A la inversa, aunque el contenido del archivo esté en UTF-8, si la página de códigos de la consola no coincide, solo se deteriorará la visualización.

Si representamos la relación entre estas capas en un diagrama, queda así.

Capas de interpretación entre quien escribe el archivo y quien lo leeDiagrama que muestra cómo la secuencia de bytes del archivo es el único hecho objetivo, y cómo cuatro lectores independientes (editor, consola, aplicación interna y lado Linux) pueden interpretarla de forma distinta, produciendo cada uno un síntoma diferenteQuien escribeaplicación / editor / scriptSecuencia de bytes del archivoesto es lo único objetivoLector A: editordetección automática o encoding indicadoLector B: consolapágina de códigos de entrada/salidaLector C: interior de la aplicaciónencoding predeterminado de la libreríaLector D: lado Linuxsupone UTF-8 según el localeSolo se deteriora la visualizaciónse convierte en corrupción al volver a guardarSolo se deteriora la visualizaciónel archivo está intactoEl resultado del procesamiento se dañase propaga aguas abajoError de decodificación o carácter de sustitución

Hay dos puntos que conviene observar. El primero es distinguir si lo que está dañado es la secuencia de bytes central o el lector de la derecha. El segundo es que los cuatro lectores de la derecha son independientes entre sí, de modo que confirmar uno no garantiza los otros tres. Por esta estructura no se sostiene la idea de que «si se leyó bien en la consola, también estará bien en el editor».

3.4 Tampoco están alineados los valores predeterminados de PowerShell y herramientas relacionadas

Algo que aumenta los incidentes en Windows de forma poco visible es que aunque se crea estar «escribiendo el mismo texto», los bytes de salida difieren según la ruta usada.

Conviene prestar especial atención a lo siguiente:

  • Windows PowerShell 5.1 no tiene una codificación predeterminada coherente
  • Algunos cmdlets y redirecciones generan UTF-16LE
  • Otras rutas usan la página de códigos ANSI activa
  • A partir de PowerShell 7, UTF-8 no BOM es el valor predeterminado

En otras palabras, decir solo «texto generado con PowerShell» no determina la codificación. Hay que fijarse hasta en qué versión, qué cmdlet y qué ruta de escritura se usó.

Qué ruta produce qué bytes está documentado en about_Character_Encoding de Microsoft Learn. Si extraemos solo las más usadas, queda así:

Ruta de escritura Predeterminado en Windows PowerShell 5.1 Predeterminado en PowerShell 7
Out-File, >, >> UTF-16LE (con BOM) UTF-8 no BOM
Set-Content, Add-Content (archivo nuevo o vacío) ANSI = página de códigos activa. En un entorno japonés, CP932 UTF-8 no BOM
Export-Csv ASCII. Los caracteres no ASCII se pierden UTF-8 no BOM
Export-Clixml, New-ModuleManifest UTF-16LE UTF-8 no BOM
New-Item -Type File -Value UTF-8 no BOM UTF-8 no BOM
Start-Transcript UTF-8 con BOM UTF-8 no BOM

También hay diferencias en el lado de la lectura. Al leer un archivo sin BOM, Get-Content en 5.1 lo trata como ANSI, mientras que Import-Csv y Select-String lo tratan como UTF-8. Es decir, las suposiciones se dividen incluso dentro de una misma sesión.

En la práctica, lo que más suele doler es que aunque se «escriba el mismo texto», Out-File produce UTF-16LE mientras que Set-Content produce CP932. El «texto de aspecto binario con muchos bytes NUL mezclados» que se menciona en 4.3 casi siempre proviene del comportamiento predeterminado de > o de Out-File.

Otro detalle: en 5.1, incluso especificando -Encoding UTF8, el resultado lleva BOM. Si en 5.1 se quiere escribir UTF-8 sin BOM, hay que hacerlo desde el lado de .NET.

# Escribir UTF-8 no BOM en Windows PowerShell 5.1
$text = "日本語を含む本文"
[System.IO.File]::WriteAllText(
    "C:\work\output.txt", $text,
    (New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false))

Poner el argumento de UTF8Encoding en $false es lo que indica no añadir BOM. En PowerShell 7, se obtiene el mismo resultado con -Encoding utf8NoBOM.

4. Incidentes típicos al combinar con Linux

No es raro que algo que funcionaba más o menos bien en Windows por sí solo se rompa en cuanto se introduce Linux en el proceso. La razón es sencilla: en el lado de Linux predomina fuertemente el supuesto de UTF-8.

4.1 Linux lee como UTF-8 un texto que Windows guardó en CP932

Es el incidente más común.

  • Una aplicación legacy de Windows o un flujo de operación antiguo escribe CSV, TXT o logs en CP932
  • El script o la herramienta del lado de Linux los lee bajo el supuesto de UTF-8, siguiendo el locale
  • El resultado es un error de decodificación, o cadenas sin sentido

En este caso, la herramienta del lado de Linux no tiene la culpa: la causa raíz es que los bytes recibidos no llevaban adjunto un acuerdo sobre la codificación.

4.2 Windows interpreta como ANSI un UTF-8 no BOM creado en Linux o VS Code

También existe el incidente en la dirección opuesta.

  • Se crea un script, un archivo de configuración o un texto en UTF-8 no BOM desde Linux o VS Code
  • Windows PowerShell 5.1 o una herramienta legacy interpreta el archivo sin BOM como si estuviera en la página de códigos ANSI
  • Solo se dañan las líneas que contienen japonés o caracteres no ASCII

Aquí suele culparse a UTF-8, pero la causa real es que hay un lector mezclado en el proceso que no logra inferir correctamente un UTF-8 sin BOM.

4.3 Windows escribe UTF-16LE y en Linux «no parece texto»

Esto también sucede con bastante frecuencia.

  • Algunas salidas de Windows PowerShell 5.1 o herramientas legacy escriben en UTF-16LE
  • Las herramientas de texto del lado de Linux esperan un flujo de 1 byte por carácter en UTF-8
  • El resultado es un «texto con aspecto binario», lleno de bytes NUL

UTF-16LE en sí mismo no es malo. Sin embargo, en muchos casos no encaja con la suposición de pasarlo directamente a una herramienta de procesamiento de texto de Linux.

4.4 La presencia o ausencia de BOM también genera fricción

El BOM no es la codificación en sí misma, pero en la práctica tiene un impacto considerable.

  • Algunas herramientas del lado de Windows se benefician de que exista un BOM
  • Algunas herramientas del lado de Linux tratan el BOM como bytes sobrantes al inicio del archivo
  • El resultado es que solo se daña el inicio de la primera columna o la primera línea, se añade basura invisible o los resultados de una comparación no coinciden

Esto es especialmente cierto en UTF-8: aunque se hable del mismo UTF-8, los bytes son distintos según haya o no BOM. Decir simplemente «lo pasamos a UTF-8» deja la regla operativa solo a medio definir.

4.5 Confiar en el aspecto de la consola lleva a confusión

Al cruzar entre Windows y Linux, otro punto peligroso es la consola.

  • La consola de Windows tiene una página de códigos de entrada/salida
  • El terminal de Linux suele funcionar bajo el supuesto de un locale UTF-8
  • Al pasar por WSL, SSH, contenedores o CI, aumentan las rutas de visualización

En esta situación, juzgar que «se leyó bien en la consola, así que el archivo también está bien» o que «se vio mal en la consola, así que el archivo está dañado» es fácil que lleve a error. Es más seguro comprobar por separado si lo que está dañado es lo que se ve o los bytes que están guardados.

4.6 Los incidentes típicos resumidos en una tabla

Situación Bytes reales Suposición del lector Síntoma típico
CSV guardado por una aplicación legacy de Windows CP932 El lado de Linux supone UTF-8 , error de decodificación, japonés sin sentido
Archivo creado en Linux / VS Code UTF-8 no BOM Windows PowerShell 5.1 lo trata como ANSI Solo se dañan las líneas en japonés
Parte de la salida de Windows PowerShell 5.1 UTF-16LE o ANSI El lado de Linux espera texto UTF-8 Bytes NUL mezclados, comportamiento de aspecto binario
Archivo UTF-8 with BOM UTF-8 + BOM Las herramientas de tipo Unix suponen UTF-8 plano Solo se daña la primera columna, se añaden caracteres sobrantes
Confiar solo en la visualización de la consola El archivo y la consola tienen suposiciones distintas Quien investiga juzga solo por lo que ve Se falla en el diagnóstico de la causa

5. El diagnóstico del mojibake se resuelve con estas cuatro preguntas

Cuando la investigación del mojibake se estanca, lo más rápido es volver a estas cuatro preguntas.

5.1 ¿Cuáles son los bytes originales?

Lo primero que hay que mirar es «qué bytes tiene ahora mismo este archivo». Hace falta el hábito de fijarse en los bytes, no en la apariencia.

  • ¿Es UTF-8?
  • ¿Es UTF-8 with BOM?
  • ¿Es CP932?
  • ¿Es UTF-16LE?
  • ¿Se ha vuelto a guardar en algún punto intermedio y ha pasado a ser otra cosa?

5.2 ¿Quién lo escribió primero y bajo qué suposición?

A continuación, hay que identificar al «primer escritor».

  • ¿Una aplicación legacy de Windows?
  • ¿PowerShell 5.1 o 7?
  • ¿Un script de Linux?
  • ¿VS Code?
  • ¿Una exportación proveniente de Excel?
  • ¿Algún middleware, proceso por lotes o CI?

Si esto queda ambiguo, deducir la codificación se convierte en cuestión de suerte.

5.3 ¿Quién lo está leyendo ahora y bajo qué suposición?

No basta con el escritor: también hace falta conocer la suposición del lector.

  • ¿El editor está usando detección automática?
  • ¿PowerShell está mirando el BOM?
  • ¿El lado de Linux lo trata como UTF-8 siguiendo el locale?
  • ¿La librería está usando su codificación predeterminada?
  • ¿Se ha especificado explícitamente Encoding.UTF8 o cp932?

El mojibake se origina casi siempre aquí.

5.4 ¿Ya se ha guardado el contenido leído erróneamente?

Por último, hay que confirmar si el daño se ha quedado solo en la visualización.

  • ¿Los bytes siguen siendo los originales?
  • ¿Alguien ha guardado el contenido que se veía dañado?
  • ¿No hay ? ni en el diff?
  • ¿No se ha reescrito todo el texto con otra codificación?

Si se responden estas cuatro preguntas, casi siempre queda claro el origen del problema.

6. Cómo reparar un archivo dañado

Una vez identificada la causa, sigue la recuperación. Lo primero que hay que determinar aquí es si los bytes originales todavía existen.

  • Los bytes originales existen: se puede recuperar leyéndolos con la codificación correcta y volviendo a escribirlos con la codificación deseada. Este capítulo describe ese procedimiento
  • Ya se guardó el resultado de una lectura errónea: solo queda recuperarlo desde una copia de seguridad o desde el historial de Git. Los caracteres que se convirtieron en o en ? no se pueden restaurar aunque después se conozca la codificación correcta

Por eso, lo primero que hay que hacer es sacar una copia del archivo con el que se va a trabajar. Realice la conversión sobre la copia y no toque el archivo original.

6.1 Si está en el lado de Linux, use iconv

Para convertir un archivo CP932 a UTF-8, lo más directo es iconv.

# CP932 -> UTF-8
iconv -f CP932 -t UTF-8 input.csv > output.csv

Si se indica mal la codificación de origen, el proceso se detiene a mitad de camino, así:

iconv: illegal input sequence at position 0

El propio hecho de que se detenga ya es información de que «no es esa codificación», así que lo más rápido es ir probando otros candidatos. A la inversa, si no se detiene sea cual sea la codificación que se pruebe, es posible que el archivo esté compuesto únicamente de ASCII.

Para convertir de UTF-16LE a UTF-8 se usa -f UTF-16LE. Sin embargo, si se convierte explícitamente con -f UTF-16LE un archivo que lleva BOM, el BOM puede quedar en la salida como el carácter U+FEFF. Si prefiere dejar que iconv se encargue del BOM, use -f UTF-16 y compruebe siempre el primer carácter después de la conversión.

6.2 Si está en el lado de Windows, use PowerShell

A partir de PowerShell 6.2, se puede pasar directamente un número de página de códigos a -Encoding. CP932 es el 932.

# PowerShell 6.2 en adelante. CP932 -> UTF-8 no BOM
Get-Content -Path .\input.csv -Encoding 932 |
    Set-Content -Path .\output.csv -Encoding utf8NoBOM

A la inversa, si se trata de pasar un archivo UTF-8 creado en el lado de Linux a un destinatario que solo puede leer CP932, se hace así:

Get-Content -Path .\input.csv -Encoding utf8 |
    Set-Content -Path .\output.csv -Encoding 932

Sin embargo, esta forma de escribirlo tiene dos efectos secundarios.

  1. Get-Content divide el contenido línea por línea y Set-Content vuelve a añadir un salto de línea después de cada una. Es decir, el carácter de salto de línea termina siendo unificado
  2. Aunque el archivo original no termine en un salto de línea, la salida sí lo llevará

Si se quiere conservar los bytes incluyendo los saltos de línea, hay que tratar el archivo completo sin dividirlo en líneas.

# CP932 -> UTF-8 no BOM conservando los saltos de línea tal cual
$text = [System.IO.File]::ReadAllText(
    "C:\work\input.csv", [System.Text.Encoding]::GetEncoding(932))
[System.IO.File]::WriteAllText(
    "C:\work\output.csv", $text,
    (New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false))

GetEncoding(932) se puede usar directamente en Windows PowerShell 5.1. En entornos de PowerShell 7 donde esto genera una excepción, ejecute primero lo siguiente una vez antes de usarlo.

[System.Text.Encoding]::RegisterProvider(
    [System.Text.CodePagesEncodingProvider]::Instance)

Si se añade o no BOM se decide según las necesidades del destinatario, como se explica en 7.1. En el ejemplo anterior, poniendo el argumento de UTF8Encoding en $true se obtiene una salida con BOM.

6.3 Qué comprobar siempre después de convertir

Una conversión no termina solo porque «no haya dado error». Como mínimo, hay que revisar lo siguiente:

  • Si 2 o 3 líneas representativas en japonés se leen correctamente
  • Si no han aumentado los ? o . Si han aumentado, ese carácter ya se ha perdido
  • Si el número de líneas no ha cambiado
  • Si el BOM y el salto de línea coinciden con lo que espera el destinatario
  • Si el tamaño del archivo no ha cambiado de forma extrema. Al pasar de CP932 a UTF-8, la parte en japonés pasa de 2 a 3 bytes, así que es normal que el tamaño aumente un poco

El segundo punto es especialmente importante. Al convertir a CP932 un texto UTF-8 que contiene caracteres que no existen en CP932, siempre se pierde información. Como se explicó en 2.2, compruebe también si, al convertir de ida y vuelta, se recupera el original.

6.4 Separe la conversión masiva como una «tarea de migración»

Por último, un comentario sobre la operación diaria. Reparar un único archivo dañado y migrar todo un repositorio de CP932 a UTF-8 son tareas distintas. Como se indica en 7.2 del siguiente capítulo, esta última no debe hacerse de paso al corregir alguna funcionalidad cotidiana: planifíquela como una tarea de migración independiente.

7. Reglas operativas para reducir incidentes

De aquí en adelante, el enfoque es más práctico. En proyectos que cruzan entre Windows y Linux, definir de antemano las siguientes reglas reduce considerablemente los incidentes.

7.1 Elija UTF-8 como primera opción para los archivos nuevos

Para los archivos de texto nuevos, lo más prudente es elegir UTF-8 como primera opción. Sin embargo, no basta con quedarse ahí: hay que decidir también qué hacer con el BOM.

Esta es la forma de decidirlo que recomendamos:

  • Textos que se leen sobre todo en el lado de Linux: usar UTF-8 no BOM como base
  • Scripts que lee una herramienta legacy de Windows o Windows PowerShell 5.1: indicar explícitamente si llevan BOM según las necesidades del destinatario
  • Si existe un destinatario concreto que requiere UTF-16LE, documentar ese requisito como especificación

Si solo se escribe «unificar en UTF-8», más adelante surgirán conflictos por el BOM.

7.2 Mantenga los archivos legacy existentes hasta que exista una tarea explícita de migración

Si un archivo existente está en CP932, lo más seguro es no convertirlo a UTF-8 por cuenta propia de paso al corregir alguna funcionalidad cotidiana.

Como práctica operativa, este es el enfoque seguro:

  • Mantener en los archivos existentes la codificación, el BOM y el salto de línea originales
  • Separar el cambio de codificación como una tarea de migración
  • Realizar la conversión masiva solo después de confirmar el alcance, el impacto y los consumidores aguas abajo

Muchos incidentes de mojibake comienzan con una «conversión a UTF-8 de paso» hecha con buena intención.

7.3 Trate la codificación como parte de la interfaz

En archivos CSV, TXT, logs, archivos de configuración y protocolos simples, no solo el contenido importa: la propia codificación es parte de la interfaz.

Por ejemplo, como mínimo conviene documentar en la especificación lo siguiente:

  • Si este archivo es UTF-8, CP932 o UTF-16LE
  • En caso de UTF-8, si lleva BOM
  • Si el salto de línea es LF o CRLF
  • Si Linux o Windows actúa como productor o como consumidor
  • Si algún proceso por lotes o ETL intermedio vuelve a guardar el archivo

Decir simplemente «se entrega como texto» no constituye una especificación.

7.4 No confíe en los valores predeterminados: especifique la codificación al escribir

Tanto en el código como en los scripts, es más seguro especificar la codificación de forma explícita.

Las siguientes formas de pensar son peligrosas:

  • Guardar con la configuración predeterminada, sin más
  • Suponer que, ajustándose al sistema operativo, probablemente saldrá bien
  • Pensar que, como se leyó bien en la consola, el archivo también estará bien
  • Confiar en que la detección automática se encargará de todo

Los valores predeterminados cambian normalmente entre Windows y Linux, entre PowerShell 5.1 y 7, y según el editor o el runtime. A menos que se especifique explícitamente, es fácil que solo esté funcionando por casualidad.

7.5 Compruebe por separado la consola y el archivo

Esta regla, aunque poco vistosa, resulta muy eficaz.

  • Verificación de la visualización en la consola
  • Verificación reabriendo el archivo

Estas dos comprobaciones deben mantenerse separadas.

Aunque chcp o la visualización del terminal parezcan correctas, no sirve de nada si el archivo guardado está en otra codificación. A la inversa, aunque el archivo esté correcto, si la página de códigos de visualización de la consola no coincide, solo se deteriorará la apariencia.

7.6 Git no corrige la codificación por usted

Es un detalle poco vistoso, pero importante.

Git, básicamente, solo hace seguimiento de bytes. Es decir, incluso los bytes dañados se incorporan al historial tal cual, con toda seriedad.

Por eso, cuando ocurra algo como lo siguiente,

  • Aparece un diff enorme sin haber cambiado nada
  • Solo las líneas en japonés muestran un diff extraño
  • Solo cambia la primera línea
  • El salto de línea y la codificación cambian a la vez

conviene sospechar antes de un incidente de recodificación que de un cambio real de contenido.

8. Lista de verificación mínima

A continuación se presenta la lista de verificación que conviene fijar desde el principio en proyectos donde se mezclan Windows y Linux.

8.1 Antes de editar

  • ¿Cuál es la codificación actual de este archivo?
  • ¿Tiene BOM?
  • ¿El salto de línea es LF o CRLF?
  • ¿Se han anotado 2 o 3 líneas representativas en japonés?
  • ¿Se sabe si el consumidor final es el lado de Linux o el de Windows?

8.2 Durante la edición

  • ¿No se está escribiendo dependiendo de la codificación predeterminada?
  • ¿No se está guardando confiando en la detección automática?
  • ¿No se están usando de forma descuidada las rutas de PowerShell o de redirección de shell?
  • ¿No se está confiando solo en que «se puede leer en pantalla»?

8.3 Después de editar

  • ¿Se ha reabierto el archivo tras guardarlo para verificarlo?
  • ¿Las líneas representativas no se ven dañadas ni en el lado de Linux ni en el de Windows?
  • ¿No han aumentado los ? o en el diff?
  • ¿No se ha dañado solo la primera línea o la primera columna?
  • ¿El diff no consiste únicamente en un gran cambio de BOM o de salto de línea?

8.4 Tareas que deben tratarse como migración

  • Conversión masiva de CP932 a UTF-8
  • Unificación de la política de BOM en UTF-8
  • Inventario de scripts que asumen PowerShell 5.1
  • Documentación explícita del paso de texto a través de CI, contenedores, WSL y SSH
  • Unificación de la configuración de guardado del editor, del formateador y de los procesos por lotes

9. Resumen

Si hay que resumir en una frase el problema de la codificación de caracteres en Windows, la esencia es que el mundo de Unicode y el mundo de las páginas de código legacy siguen coexistiendo hoy en día.

Y los incidentes aumentan al combinarlo con Linux porque el lado de Linux suele operar bajo el supuesto de UTF-8, lo que hace salir a la superficie de golpe el CP932 y el UTF-16 de Windows, la página de códigos de la consola y las diferencias entre versiones de PowerShell.

Estos son los cinco puntos que conviene recordar:

  • El mojibake es un desajuste en la interpretación de los bytes
  • El deterioro de la visualización y la corrupción de datos son cosas distintas
  • En Windows hay que pensar por separado en las capas de archivo, editor, consola y API
  • Para el texto que se intercambia con Linux, elegir UTF-8 como primera opción
  • Separar la conversión de archivos legacy existentes de las correcciones habituales

Tratar tal cual el problema de «se produjo mojibake en Windows» resulta demasiado amplio. Pero si se reduce a estas cuatro preguntas,

  • ¿Cuáles son los bytes originales?
  • ¿Quién los escribió y cómo?
  • ¿Quién los leyó y cómo?
  • ¿Ya se han guardado?

se puede ordenar bastante bien el problema.

La codificación de caracteres es un tema poco vistoso, pero entre Windows y Linux constituye el propio contrato de E/S. No dejar esto en la ambigüedad es la medida más eficaz.

10. Referencias

Windows / Microsoft

PowerShell / VS Code

GNU / Linux locale

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

Preguntas frecuentes

Preguntas habituales en las consultas sobre el tema del artículo.

¿Cuál es la causa del mojibake en Windows?
En la mayoría de los casos, la causa es haber leído la misma secuencia de bytes con un código de caracteres distinto, o haber guardado con otro código de caracteres el resultado de una lectura errónea. Por ejemplo, si se lee en el contexto de CP932 la secuencia de bytes (E3 81 82) resultante de guardar «あ» en UTF-8, aparece como una cadena distinta, algo como «縺». No ocurre porque el japonés sea difícil: la verdadera causa es que las suposiciones de encode y decode no coinciden.
¿Se puede recuperar un archivo que ha sufrido mojibake?
Si la secuencia de bytes original no ha cambiado, a veces se puede recuperar volviendo a abrirla con la codificación correcta. Lo peligroso es el flujo en el que se guarda tal cual un contenido que se ve dañado por una lectura errónea; al llegar a ese punto, ya no es un simple deterioro visual, sino corrupción de datos. Además, si una cadena Unicode se reduce a una página de códigos estrecha como CP932 y se convierte en «?» o en un carácter de sustitución, el carácter perdido no se puede restaurar aunque después se conozca la codificación correcta.
¿Si ejecuto chcp 65001, los archivos también pasarán a ser UTF-8?
No. Cambiar la página de códigos de la consola y saber qué es realmente la secuencia de bytes de un archivo existente son cuestiones distintas. En Windows, la codificación del propio archivo, la interpretación del editor, la página de códigos de entrada/salida de la consola y el formato interno de cadenas de la aplicación son cosas separadas, y hay que pensar en cada capa por separado. Juzgar que «se pudo leer en la consola, así que el archivo también estará bien» es fácil que lleve a error, por lo que conviene separar la verificación de la visualización en consola de la verificación reabriendo el archivo.
¿Cuáles son las reglas seguras para intercambiar texto entre Windows y Linux?
Elegir UTF-8 como primera opción para los archivos nuevos y decidir también qué hacer con el BOM. Para el texto que se lee sobre todo en el lado de Linux, use UTF-8 no BOM como base; cuando lo lea una herramienta legacy como Windows PowerShell 5.1, indique explícitamente si lleva BOM según las necesidades del destinatario. No convierta los archivos CP932 existentes de paso al hacer correcciones cotidianas: sepárelo como una tarea de migración explícita. Además, como en CSV y logs la propia codificación es parte de la interfaz, documentar como especificación la codificación, el BOM y el salto de línea reduce los incidentes.

Perfil del autor

Página de presentación del autor del artículo.

Go Komura

Representante de KomuraSoft LLC

Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.

Volver al blog