One missing flag and xmlcatalog will happily overwrite the working catalogue you meant to only preview. You will create a catalogue, map an identifier to a URI, verify the lookup, then remove the entry without clobbering the file. The examples use xmlcatalog from libxml2-utils, version 2.9.14+dfsg-1.3ubuntu3.9.
Allow about ten minutes. You need a shell and a writable directory. Nothing here needs sudo; use elevated privileges only if the catalogue deliberately lives somewhere your account cannot write, and review the target path first.
Start in a scratch directory, not a real one. --create writes a new catalogue to standard output by default; add --noout when you actually want it saved to the filename you gave.
$ workdir=$(mktemp -d)
$ catalog="$workdir/catalog.xml"
$ xmlcatalog --create --noout "$catalog"
$ sed -n '1,8p' "$catalog"
<?xml version="1.0"?>
<!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN" "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog"/>
Checkpoint: the file should hold an empty catalogue with the XML catalogue namespace. Omit --noout and the same document just prints to standard output instead: the named file is never created or replaced.
--add takes three arguments for an XML entry: the type, the original identifier, and its replacement URI. A uri mapping earns its keep when software asks libxml2 to resolve a known identifier and you want that redirected to a local or controlled resource.
$ xmlcatalog --add uri urn:example:doc https://example.test/doc.xml --noout "$catalog"
$ sed -n '1,12p' "$catalog"
<?xml version="1.0"?>
<!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN" "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<uri name="urn:example:doc" uri="https://example.test/doc.xml"/>
</catalog>
Safety warning: never use a production catalogue as your first test target. Saving an entry changes resolver behaviour for every application that reads that file. Keep a backup before editing an existing one:
$ cp --preserve=all /path/to/catalog.xml /path/to/catalog.xml.bak
Run the command without --noout and it is a preview: the changed catalogue prints to standard output, the input file stays untouched. Worth doing before you save anything:
$ xmlcatalog --add uri urn:example:next https://example.test/next.xml "$catalog"
Add --noout only once the displayed change is the one you actually want to keep.
Give xmlcatalog the catalogue followed by the identifier and, with no --add or --del, it just performs a lookup:
$ xmlcatalog "$catalog" urn:example:doc
No entry for SYSTEM urn:example:doc
https://example.test/doc.xml
$ printf '%s\n' "$?"
0
The extra system lookup message is normal for a URI-only entry; the URI result and status 0 are what actually matter. A missing identifier produces messages like No entry for URI and returns status 4 instead.
In a script, grab the status immediately after the lookup:
if resolved=$(xmlcatalog "$catalog" urn:example:doc 2>/dev/null); then
printf '%s\n' "$resolved"
else
status=$?
printf 'catalog lookup failed with status %s\n' "$status" >&2
exit "$status"
fi
Do not assume the first output line is the replacement: a catalogue can report more than one attempted lookup. Need a quiet, machine-friendly interface instead? Use libxml2's resolver in your application and treat this command as an inspection tool, not a library.
--del takes the value to remove, and like --add it prints the resulting catalogue unless you supply --noout. Preview the deletion first:
$ xmlcatalog --del urn:example:doc "$catalog"
<?xml version="1.0"?>
...
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog"/>
$ xmlcatalog "$catalog" urn:example:doc
No entry for SYSTEM urn:example:doc
No entry for URI urn:example:doc
Destructive action: the preview above did not touch the file. This one does:
$ xmlcatalog --del urn:example:doc --noout "$catalog"
$ xmlcatalog "$catalog" urn:example:doc
No entry for SYSTEM urn:example:doc
No entry for URI urn:example:doc
Removed the wrong entry? Restore the backup you made before editing:
$ cp --preserve=all /path/to/catalog.xml.bak /path/to/catalog.xml
For a shared catalogue, coordinate the change with the applications reading it. There is no transaction or service reload built into xmlcatalog: a process may see the old or new contents purely depending on when it happened to open the file.
Reach for --shell when you want several read or edit operations in one session. Its commands include dump, public, system, resolve, add, del, debug, quiet, and exit.
$ xmlcatalog --shell "$catalog"
> dump
<?xml version="1.0"?>
...
> system "-//Example//DTD Widget 1.0//EN"
> exit
Quote identifiers that contain spaces or punctuation. This shell still operates on the named catalogue, so treat its add and del as state-changing, and keep the backup until you have verified the result.
libxml2 also consults the XML_CATALOG_FILES environment variable: a space-separated list of catalogues, with percent-encoding for spaces and other special characters. Set it to empty and you disable loading the default /etc/xml/catalog for the relevant resolver behaviour.
$ printf '%s\n' "${XML_CATALOG_FILES-}<unset>"
$ env XML_CATALOG_FILES= xmlcatalog "" urn:example:doc
No entry for SYSTEM urn:example:doc
No entry for URI urn:example:doc
That last command deliberately queries the default system catalogue shortcut with the override empty. Do not set this variable globally just to make one test pass: it changes how applications resolve external entities and schemas everywhere.
ls -l and test -w before reaching for sudo; ownership or permissions changes are a separate administrative decision.--noout, then confirmed by a lookup.XML_CATALOG_FILES was ruled out before chasing unexpected resolver behaviour.