Highlight Source Files Reliably with pygmentize
Paste code into a report and plain text never reads as well as highlighted output. pygmentize turns a source file into coloured terminal output or HTML on demand. The examples use Debian's python3-pygments package, version 2.17.2+dfsg-1, and the installed command reports version 2.17.2.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need a shell, a readable input file and the pygmentize command. These examples only read input and write to standard output or a named file. No elevated privileges are needed unless you deliberately choose a directory your account cannot write to.
1. Check the installed command
Confirm the executable and version before you rely on a formatter or lexer in a script:
$ command -v pygmentize
/usr/bin/pygmentize
$ pygmentize -V
Pygments version 2.17.2, (c) 2006-2023 by Georg Brandl, Matthaus Chajdas and contributors.
$ dpkg-query -W -f='${Package} ${Version}\n' python3-pygments
python3-pygments 2.17.2+dfsg-1
The copyright line can vary slightly between package builds. What matters is that the command is the one you expect and the package version is on record.
2. Highlight a named source file
Create or choose a small file whose extension identifies its language; pygmentize guesses the lexer from that extension when you leave out -l:
$ printf '%s\n' 'def greet(name):' ' return f"Hello, {name}"' > /tmp/example.py
$ pygmentize /tmp/example.py
The default output carries ANSI colour escape sequences meant for a terminal. Redirect it to a file and those control sequences travel with it, which is usually the wrong format for a web page or a plain text attachment.
Checkpoint
Ask the command which lexer it would use, without highlighting anything:
$ pygmentize -N /tmp/example.py
python
-N bases its answer purely on the filename. An unknown extension produces text, so a plausible-looking result is not proof the source was actually parsed as the language you intended.
3. Select the lexer and formatter explicitly
Use -l for the lexer and -f for the formatter whenever the filename is ambiguous or the output feeds a repeatable process:
$ pygmentize -l python -f terminal /tmp/example.py
For an HTML fragment, pick the HTML formatter:
$ pygmentize -l python -f html /tmp/example.py
<div class="highlight"><pre><span></span><span class="k">def</span> ...
</pre></div>
Exact spans depend on the source and the Pygments version. The HTML formatter emits markup and CSS class names but not a stylesheet, so generate or supply matching CSS separately if a browser needs to show the colours.
Use -L to check what is actually installed instead of guessing a name:
$ pygmentize -L lexers
$ pygmentize -L formatters
$ pygmentize -H formatter html
All three are read-only queries, and a useful first move when a copied command fails on an unknown lexer or formatter.
4. Write HTML to a new file safely
Pass -o when another program needs a file on disk. The output filename can also hint at a formatter, but specifying -f html makes your intention explicit:
$ pygmentize -l python -f html -o /tmp/example.html /tmp/example.py
$ file /tmp/example.html
/tmp/example.html: HTML document, ASCII text
$ sed -n '1,4p' /tmp/example.html
<div class="highlight"><pre><span></span>...
Warning
Do not point -o at the input file, and remember that both shell redirection and -o happily overwrite an existing destination. Choose a new path, or back up first:
$ cp --preserve=all /path/to/result.html /path/to/result.html.bak
$ pygmentize -l python -f html -o /path/to/result.html.new /path/to/example.py
$ mv /path/to/result.html.new /path/to/result.html
If the conversion fails, the old result stays put and the .new file is there to inspect or discard. Keep the backup until you have checked the replacement; rm is irreversible.
5. Handle standard input deliberately
With no filename, pygmentize reads standard input, which suits a pipeline but removes the extension-based guess. Pick the lexer yourself:
$ printf '%s\n' 'SELECT id FROM users;' | pygmentize -l sql -f terminal
Do not combine -g with -l; they are alternative ways to pick a lexer. Use -g when you want Pygments to guess from content, including a shebang or modeline where one exists:
$ printf '%s\n' '#!/usr/bin/env python3' 'print("ok")' | pygmentize -g -f terminal
Content guessing is a fallback, not a correctness check, and it can misfire on short or ambiguous input. Use -C to print the guessed lexer name for standard input without producing any highlighted output:
$ printf '%s\n' 'SELECT id FROM users;' | pygmentize -C
text
That result is your cue to use -l sql when the language actually matters. Following an unbounded stream such as tail -f? The installed command also supports -s for line-at-a-time processing, but only with standard input and lexers that do not span lines.
6. Set options without losing them to the shell
Use -O for comma-separated lexer or formatter options, quoting the whole value so the shell cannot split or expand it:
$ pygmentize -l python -f html -O 'linenos=1,style=emacs' /tmp/example.py
Use -P instead when a single option value contains commas or equals signs, since it takes one option per occurrence:
$ pygmentize -l python -f html -P 'cssclass=source-code' /tmp/example.py
Option names belong to the selected lexer or formatter. Check documented help with -H lexer NAME or -H formatter NAME before putting one into automation. An option that gets rejected means: drop it and confirm the exact object name with -L.
7. Diagnose the common mistakes
An empty or missing output file usually comes down to input path, output path or permissions. Test read and write access without changing anything:
$ test -r /path/to/example.py && echo 'input is readable'
$ test -w /path/to/output-directory && echo 'directory is writable'
$ pygmentize -l python -f html -o /tmp/example.html /path/to/example.py
$ test -s /tmp/example.html && echo 'output is non-empty'
A non-zero exit status means it failed, but a successful one does not prove the lexer was correct. Check the selected lexer with -N or -C, and inspect the output format before handing it to a browser or another tool. Avoid sudo as a first response: it hides a path or ownership problem and is unnecessary for files in your own working directory.
Done means
- Recorded the version. Installed Pygments version and package are on record.
- Chose lexer and formatter on purpose. You can pick both with
-land-frather than guessing. - Know the default. An omitted formatter defaults to terminal output on standard output.
- HTML written safely. Output went to a new or deliberately replaced destination, with a backup where it mattered.
- Standard input got an explicit lexer. No relying on a guess when it would be ambiguous.
- Options quoted and checked. Values are verified against the installed formatter or lexer.