The real question here wasn't "how can I do this"...
It's, I would think that wanting to know if a value came from a default or the user would be common, but doesn't seem to be in the normal command line parsers...
Is it bad form for some reason?
or just not needed often enough to be worth putting in?
I would think that wanting to know if a value came from a default or the user would be common, but doesn't seem to be in the normal command line parsers...
Because it's actually not common. You're talking about "accountability" (tracking the provenance) for the options used in a given run of a tool, but in the general case, when it comes to actually executing the operations that are selected by a given set of parameters, the origins of the parameters make no difference -- it only matters what their effective values are.
No matter whether "-f foobar" was set explicitly by the user on the command line, or came from an environment variable, or was a default value, it's going to do the same thing. The behavior invoked by '-f foobar' might depend on other params, but again, it's a matter of what those other param values are, rather than how they got to be what they are.
Obviously, precedence is important, and the normal approach is "env overrides default, argv overrides both"; the only other consideration is that the default case should be the most typical.
If you have a pressing need for "accountability" -- that is, tracking and documenting the causality of param settings for each run of the program -- that's a separate matter, going beyond the scope of typical command-option parsing. Set up a HoH whose primary keys are the option flags or parameter names; for each of these, you'll want a sub-hash whose keys are, say, "value" and "source".
You can still use one of the standard modules (Getopt::Long or Getopt::Std), and use one or the other sequences proposed in the other replies. As you copy the parameter values into the HoH, you also say where that value is coming from (argv, env or default).