INI#

INI files are the longest-living config format on the operator’s hosts. Key/value pairs grouped under bracketed section headers, comments with ; or #, no formal spec and no agreement on the edge cases. Windows shipped INI in 1985; configparser made it the Python standard for config files; systemd unit files are INI in spirit; Git config, MySQL my.cnf, pip.conf, php.ini, and most .desktop entries are INI dialects.

Because there is no standard, every parser implements its own dialect. Two INI files that look identical can parse differently. Treat INI as a family of formats, not a single one.

Minimal example#

; comment (semicolon)
# comment (hash) — supported by most modern parsers

[DEFAULT]
timeout = 30
retries = 3

[server]
host = 0.0.0.0
port = 8080
tls  = true

[database]
url = postgres://localhost/scan
pool = 16

Sections#

Sections are bracketed names. Every key/value pair that follows belongs to the most recently declared section. Some dialects allow nested sections via dotted names ([server.tls]); the spec-less core does not require this.

[section_name]
key1 = value1
key2 = value2

[another_section]
key1 = different_value

A pair before any section header lives in an implicit default section (often named DEFAULT, global, or just __no_section__ depending on the parser).

Key/value pairs#

The basic key = value form. Some dialects accept : instead of =. Whitespace around the separator is usually trimmed. Values are strings in the wire form; the parser or the consumer is responsible for coercing.

host          = 0.0.0.0
port          = 8080
debug         = true
retry_delay   = 1.5
user_agent    = MyScan/1.0
description   = a longer line that the parser keeps as one string

Quotes are not standard; parsers either strip them or keep them depending on dialect. The operator avoids quoting unless the parser documentation says it strips quotes.

Comments#

Two conventions.

Marker

Notes

;

Original Windows convention. Universally accepted.

#

Unix-flavoured. Accepted by Python configparser, Git config, most modern dialects.

Inline comments (a ; after a value on the same line) are not universally supported. Python configparser accepts inline_comment_prefixes only when configured; many other parsers treat ; as part of the value.

# this is a section comment
[server]
host = 0.0.0.0      ; inline comment; risky — may be value
port = 8080         # likewise

Multi-line values#

Some dialects (Python configparser) allow continuation lines with leading whitespace. Many do not. Test with the target parser.

description = a long description
              that continues across
              multiple lines

roles = admin
        reader
        writer

In Git config, the operator uses backslash-newline. In systemd unit files, multi-line values are not allowed at all; lines join with backslash continuation in shell-style strings only.

Common dialects#

The places the operator meets INI and how each one diverges from the spec-less core.

Dialect

Notes

Windows INI

GetPrivateProfileString. ; comments. Sections in brackets. Quoted strings preserved as-is. The ancestor of every dialect below.

Python configparser

[DEFAULT] section inherited by all others. Interpolation with ${var} (RawConfigParser disables it). # and ; comments. Multi-line values via indentation.

systemd unit files

[Unit] / [Service] / [Install] / [Timer]. Repeated keys append to a list. No quotes; no interpolation. # comments only.

Git config

[section] plus [section "subsection"] (double quotes on subsection name). Boolean values (true/false/on/off/yes/no). Conditional includes ([includeIf "gitdir:~/work/**"]).

MySQL my.cnf

[mysqld], [client], [mysql]. !include / !includedir directives. Bare keys (no value) for boolean flags.

pip.conf / requirements

[global], [install], [wheel]. Same structure as Python configparser.

php.ini

[section], key = value. Quoted strings. Constants on the right side (error_reporting = E_ALL & ~E_NOTICE).

.desktop / FreeDesktop

[Desktop Entry]. Locale-specific keys with Name[de]=…. ; comments.

When the operator hits an unfamiliar .ini-flavoured file, the first question is “which parser?” — that decides the dialect.

Tooling#

Tool

Notes

configparser (Python stdlib)

The reference dialect for new INI work. Reads and writes.

crudini

CLI to read, set, delete, and merge INI keys. crudini --set my.cnf mysqld bind-address 0.0.0.0.

git config

The canonical way to read and edit Git’s INI.

systemctl edit unit.service

Drop-in override editor for systemd unit INI.

inicheck, iniparser (C)

C-side parsers when embedding INI in firmware.

yq -p ini

Convert INI to YAML / JSON for further processing.

$ crudini --set my.cnf mysqld bind-address 0.0.0.0
$ crudini --get my.cnf mysqld max_connections
$ crudini --del my.cnf mysqld old_setting

$ git config --global user.email "rk@example.com"
$ git config --get-all remote.origin.fetch

$ python -c "
import configparser
c = configparser.ConfigParser()
c.read('app.ini')
print(c.get('server', 'host'))
"

INI vs TOML vs YAML#

INI

Untyped strings, no nesting, dialect-dependent. Use when the target tool requires it (Git config, systemd, Windows registry exports).

TOML

Typed values, real nesting, a spec. Pick for new project config (pyproject.toml, Cargo.toml).

YAML

Indentation-driven, anchors/aliases, complex types. Pick for human-edited configs with reused fragments (CI, k8s).

INI is the format the operator keeps reading because legacy software writes it. It’s rarely the right choice for new configuration.

Pitfalls#

  • No standard. Two parsers can disagree on the same file. Always know which parser will read the file before writing one.

  • Type coercion. Values are strings in the wire. The reader interprets true, 1, yes differently across parsers; explicit is safer.

  • Inline comments. port = 8080 ; old port may parse the entire 8080 ; old port as the value. Put comments on their own line.

  • Repeated keys. Some parsers keep the last value, some raise an error, some build a list (systemd). Behaviour is dialect-specific.

  • Section ordering. INI files are not ordered guarantees; most parsers preserve order on read/write, but the spec does not require it.

References#