Subject: Re: [PATCH] ip: Make 'ip stats help' exit with -1

From: Stephen Hemminger <[email-masked]>
Date: Sun, 1 Mar 2026 10:36:22 -0800

On Sun, 01 Mar 2026 16:12:03 +0200
Yedaya Katsman <[email-masked]> wrote:

> ip <subcommand> help exits with -1 for all commands except for stats, make
> it match.
> This removes a goto err, which is redundant since "enabled" gets
> allocated only if we find a level, but we enter do_help only if we didn't.
> 
> Fixes: df0b2c6d0098 ("ipstats: Add a shell of "show" command")
> Signed-off-by: Yedaya Katsman <[email-masked]>

You raise a good point, but not sure that fixing just one corner is the
right answer here. Most of iproute2 gets this wrong already.

The (poorly documented) convention for help in Unix commands:
Output destination: stdout (not stderr). 
Help text is expected, requested output — not an error.
This matters for piping, e.g. command --help | grep something.

Exit code: 0 (success).
The user asked for help and got it — that's a successful operation.

Contrast with error cases:
When a user provides an invalid flag or misuses the command, 
the usage/error message should go to stderr with a non-zero exit code
(typically 1 or 2 — 2 is common convention from many GNU tools for usage errors).

In practice, this is codified in the GNU Coding Standards, 
which explicitly state that --help should print to stdout and exit with status 0.
POSIX doesn't mandate -h specifically, but the GNU convention is widely followed and expected.
A surprising number of tools historically got this wrong (sending help to stderr or returning non-zero).

But iproute2 does not follow this convention.
Fixing it might be a good idea, the kind of thing AI refactoring might help at.

An example which should print nothing!
$ ip help >/dev/null || echo broken 
Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }
       ip [ -force ] -batch filename
where  OBJECT := { address | addrlabel | fou | help | ila | ioam | l2tp | link |
                   macsec | maddress | monitor | mptcp | mroute | mrule |
                   neighbor | neighbour | netconf | netns | nexthop | ntable |
                   ntbl | route | rule | sr | stats | tap | tcpmetrics |
                   token | tunnel | tuntap | vrf | xfrm }
       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |
                    -h[uman-readable] | -iec | -j[son] | -p[retty] |
                    -f[amily] { inet | inet6 | mpls | bridge | link } |
                    -4 | -6 | -M | -B | -0 |
                    -l[oops] { maximum-addr-flush-attempts } | -echo | -br[ief] |
                    -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |
                    -rc[vbuf] [size] | -n[etns] name | -N[umeric] | -a[ll] |
                    -c[olor]}
broken