Home / Alt manpages / charmap(5)

  • charmap(5)
  • File format
  • linux

Build and Test a Private Locale with a charmap File

You will turn a character set description into a private locale directory, then verify the result without changing the system locale database. This is useful when a program needs a controlled character encoding or character-width table for a test. The examples use charmap(5) from Linux man-pages 6.7 and localedef from glibc 2.39 on this machine. Allow about 15 minutes and keep the work under a temporary directory.

No command below needs sudo. Installing a locale system-wide would require a separate, privileged operation and is not needed for this workflow.

1. Check the tools and source map

A charmap describes the characters available in a character set and the byte sequence for each one. localedef consumes it while compiling a locale. Check that both commands exist and see where the distribution keeps its compressed maps:

$ command -v localedef locale
/usr/bin/localedef
/usr/bin/locale
$ ls /usr/share/i18n/charmaps/UTF-8.gz
/usr/share/i18n/charmaps/UTF-8.gz
$ localedef --version | head -1
localedef (Ubuntu GLIBC 2.39-0ubuntu8.9) 2.39

The installed map is compressed. localedef needs a readable charmap file, so unpack a working copy. Do not edit the file under /usr/share:

$ workdir=$(mktemp -d /tmp/charmap-work.XXXXXX)
$ mkdir -p "$workdir/locale"
$ zcat /usr/share/i18n/charmaps/UTF-8.gz > "$workdir/UTF-8-demo"
$ head -6 "$workdir/UTF-8-demo"
<code_set_name> UTF-8
<comment_char> %
<escape_char> /
<mb_cur_min> 1
<mb_cur_max> 6

$ grep -nE '^(CHARMAP|END CHARMAP|WIDTH|END WIDTH)' "$workdir/UTF-8-demo"
9:CHARMAP
49856:END CHARMAP
49866:WIDTH
50349:END WIDTH

Checkpoint: you should have a regular, uncompressed file and a separate output directory. The large size is expected. A production charmap must cover the character set it claims to describe; a seven-line demonstration map is not a safe substitute.

2. Read the header before changing anything

The header controls how the rest of the file is read. <code_set_name> names the map. <comment_char> and <escape_char> set the comment and special-character markers, defaulting to # and backslash when omitted. <mb_cur_min> and <mb_cur_max> describe the minimum and maximum bytes per character; the minimum cannot exceed the maximum. In this UTF-8 map, the values are 1 and 6.

From CHARMAP to END CHARMAP, each entry maps a symbolic character such as <U20AC> to bytes such as /xe2/x82/xac. A range can map a sequence of characters with one rule. The byte sequence is the encoding, not a display width and not a shell escape.

Do not change the byte mappings casually. A wrong mapping can make text unreadable or cause different characters to collide. If your goal is display width, edit the later width section instead.

3. Add a width rule in the WIDTH section

The width section is optional. Characters not listed there have width 1 unless WIDTH_DEFAULT supplies another default. Individual entries use a symbolic character followed by a width; ranges use two symbolic characters separated by three dots. This example records the Euro sign explicitly as one column while leaving the existing UTF-8 byte mapping untouched:

$ sed -i '/^WIDTH$/a <U20AC> 1' "$workdir/UTF-8-demo"
$ sed -n '/^WIDTH$/,/^END WIDTH$/p' "$workdir/UTF-8-demo" | head -3
WIDTH
<U20AC> 1
<U000300>...<U00036F> 0

Use an editor instead if you need several deliberate rules. Keep every width non-negative and check whether the original map already covers the character through a range. Do not add a duplicate or overlapping rule merely because the character is hard to find.

Checkpoint: confirm both section terminators still exist:

$ tail -1 "$workdir/UTF-8-demo"
END WIDTH
$ grep -n '^END CHARMAP$' "$workdir/UTF-8-demo"
49856:END CHARMAP

4. Compile the map into a private locale

A charmap is not itself a locale. Pair it with a locale source. The installed C source is a compact, predictable choice for a test locale. The final argument is an output directory name, not a request to install the locale globally:

$ localedef --no-archive     -f "$workdir/UTF-8-demo"     -i /usr/share/i18n/locales/C     "$workdir/locale/demo"
$ printf '%s
' "$?"
0
$ find "$workdir/locale/demo" -maxdepth 1 -type f -printf '%f
' | sort
LC_ADDRESS
LC_COLLATE
LC_CTYPE
LC_IDENTIFICATION
LC_MEASUREMENT
LC_MONETARY
LC_NAME
LC_NUMERIC
LC_PAPER
LC_TELEPHONE
LC_TIME

--no-archive keeps the result as files under your chosen directory. Without it, a normal system installation can try to update the locale archive, which is unnecessary for a private test and may need elevated access. The source and charmap must describe compatible character data. An incomplete custom map can make localedef reject the input; on this glibc build, a very small malformed map even triggered a segmentation fault, so start from a complete installed map and validate changes incrementally.

5. Load the result without changing the host

Set LOCPATH only for the command or shell that needs the test locale. Use a simple locale name such as demo so the directory created by localedef is unambiguous:

$ LOCPATH="$workdir/locale" LC_ALL=demo locale charmap
UTF-8
$ LOCPATH="$workdir/locale" LC_ALL=demo locale | grep -E '^(LANG|LC_CTYPE)='
LANG=C.UTF-8
LC_CTYPE="demo"

The first command verifies the compiled character encoding. The second shows that the locale is selected for that process. It does not alter /etc/locale.conf, your login environment or the system archive. To undo the test, let the temporary directory go away at the end of your shell session, or remove the exact directory identified by printf '%s ' "$workdir" after you have finished checking it.

6. Diagnose failures safely

If localedef reports a syntax error, inspect the line number in the uncompressed copy. Check that the header precedes CHARMAP, the character section ends with END CHARMAP, and any width section ends with END WIDTH. Remember that the active escape character is / in this map, so byte sequences use forms such as /x20.

If the locale compiles but locale cannot load it, check the output directory name and export LOCPATH for the same command. A successful compilation into a directory that is not on the lookup path does not make the locale visible. If a character renders with an unexpected width, inspect the width rule and its range before changing the byte mapping.

Keep the original compressed map and the generated locale until the checks pass. The only state changed here is under $workdir. Do not copy files into /usr/lib/locale or modify global locale settings just to solve a private test.

Done means

  • A complete charmap was copied to a temporary working directory.
  • Header values, byte mappings and width rules were checked separately.
  • localedef --no-archive compiled the locale with exit status 0.
  • LOCPATH=... and LC_ALL=demo locale charmap reported UTF-8.
  • No system locale archive, login setting or file under /usr/share was changed.