Disassemble ICU Resource Bundles Safely with derb

You have a compiled .res file and no idea what is inside it. derb turns an ICU binary resource bundle back into readable resource text. This guide shows where it looks for input, how to send output to a file or standard output, and how to avoid truncating useful data. Allow about fifteen minutes. The examples use ICU 74.2 from the Ubuntu icu-devtools package.

This is a read-and-export task. It does not need elevated privileges when the bundle and destination are readable and writable by your user. Keep the original .res file: the generated text is an inspection or editing aid, not a replacement binary bundle.

1. Check the installed tool

Confirm which executable will run and record its version before comparing output with another machine:

$ command -v derb
/usr/bin/derb
$ derb --version
derb version 1.1 (ICU version 74.2).

The manpage installed with this package identifies itself as the ICU 74.2 manual. Command-line details can vary between ICU releases, so keep this version with any bug report or reproducibility note.

Checkpoint: if command -v derb prints nothing, install the ICU development tools through your normal package-management process. Do not use sudo merely to inspect a bundle that you can already read.

2. Separate the bundle and destination directories

derb takes one or more bundle names. Two options control where files come from and go to:

Without these options, the source directory is the current directory and the destination follows ICU's data-location rules, which is easy to miss when working in a project tree.

For a bundle at /srv/app/locale/en_GB.res, make a private output directory and disassemble into it:

$ mkdir -p /tmp/derb-output
$ derb --sourcedir /srv/app/locale \
    --destdir /tmp/derb-output \
    en_GB.res
processing bundle "en_GB.res"

The result is normally /tmp/derb-output/en_GB.txt. Check both the file name and its first lines:

$ file /tmp/derb-output/en_GB.txt
/tmp/derb-output/en_GB.txt: Unicode text, UTF-8 (with BOM) text
$ sed -n '1,18p' /tmp/derb-output/en_GB.txt
// -*- Coding: UTF-8; -*-
//
// This file was dumped by derb(8) from /srv/app/locale
// derb(8) by Vladimir Weinstein and Yves Arrouye

en_GB{
    ...

Checkpoint: your file wording may differ. The useful checks are that the expected .txt file exists, the top-level bundle name is present, and nested keys have become readable tables or values.

3. Send one bundle to standard output

Use -c or --to-stdout when you want to inspect a bundle without creating a destination file. It suits a quick review, or piping the text into another read-only command:

$ derb --sourcedir /srv/app/locale --to-stdout en_GB.res | sed -n '1,30p'
// -*- Coding: UTF-8; -*-
//
// This file was dumped by derb(8) from /srv/app/locale
// derb(8) by Vladimir Weinstein and Yves Arrouye

en_GB{
    ...

Warning: on the installed ICU 74.2 binary, do not combine --to-stdout with --encoding. It rejects that combination. If you need a particular encoding, write a file with -d instead:

$ derb --sourcedir /srv/app/locale \
    --destdir /tmp/derb-output \
    --encoding UTF-8 \
    en_GB.res

--bom adds a byte-order mark. The manpage says it should be used only with a Unicode transformation format such as UTF-8. It changes the written representation, not the resource data.

4. Build a small test bundle

If you do not have a bundle to inspect, create one with ICU's companion genrb tool. This keeps the test local and makes the expected keys obvious:

$ mkdir -p /tmp/derb-demo/res /tmp/derb-demo/out
$ genrb --destdir /tmp/derb-demo/res /path/to/demo.txt
$ derb --sourcedir /tmp/derb-demo/res \
    --destdir /tmp/derb-demo/out \
    demo.res
$ sed -n '1,40p' /tmp/derb-demo/out/demo.txt

The source file must actually describe a bundle named demo, so a source file with a different top-level name will not reliably produce the file used in this example. If you are starting from an existing .res, skip genrb entirely. It compiles text to binary; derb performs the reverse inspection step.

5. Limit large resources deliberately

Use -t or --truncate when a bundle contains very long strings or binary data and you only need a sample. With no size argument, the documented default is 80 bytes. Pass an explicit size when the limit matters:

$ derb --sourcedir /srv/app/locale \
    --destdir /tmp/derb-output \
    --truncate 256 \
    en_GB.res

The output marks truncated resources with a warning. In the installed tool, a 10-byte limit caused a 14-byte string to be shown as five characters, because the limit is applied to the encoded resource data and the result still has to be valid text. Treat this output as a diagnostic view, not as a faithful copy to compile again.

Warning: writing into an existing destination can replace its same-named text output. Use a new temporary directory while investigating, or copy an output you need to preserve before rerunning with a different encoding or truncation limit. The input .res is not changed by derb.

6. Diagnose paths and ICU data

If you see U_MISSING_RESOURCE_ERROR, first check the exact source path and file name:

$ ls -l /srv/app/locale/en_GB.res
$ test -r /srv/app/locale/en_GB.res && echo readable

When -s - is used, derb searches the default ICU data location named by ICU_DATA, or the location selected when ICU was built. If you set ICU_DATA yourself, the manpage warns that some ICU tools require its trailing slash:

$ ICU_DATA=/srv/icu-data/ derb --sourcedir - --to-stdout en_GB.res | sed -n '1,20p'

Tip: the installed binary printed an error for a missing bundle but returned status 0 in a direct test. For automation, capture standard error and verify that the expected output was produced instead of trusting the exit status alone:

derb --sourcedir /srv/app/locale --destdir /tmp/derb-output en_GB.res \
    >/tmp/derb.stdout 2>/tmp/derb.stderr
status=$?
test "$status" -eq 0 && test -s /tmp/derb-output/en_GB.txt \
    && printf '%s\n' 'bundle text written' \
    || { printf '%s\n' 'derb did not produce the expected file' >&2; exit 1; }

Keep the stderr file when diagnosing a failed build. Do not "fix" a path error by running the command as root; that can hide an ownership or deployment mistake and may leave root-owned output behind.

Done means