From 4e2a56a6023229956063fe0feb9d0790c2a7c9cb Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Wed, 10 Sep 2025 12:44:30 +0200
Subject: [PATCH v1 1/2] Handle --help and --version as normal commandline
 parameters
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Traditionally all postgres apps has treated --help, -?, --version and
-V as special case options which have to be the first parameter in the
commandline, else they are considered unrecognized.  This is not the
typical way to handle parameters in command line utilities, leading to
a bad user experience when a documented parameter is erroring out with
an unrecognized error.

This moves --help, -?, --version and -V into the common command line
parameter processing allowing them to be used at any argument position.
It turned out that our apps had very varied support for these in the
long_options array and getopt_long call.  Some apps had the long option
defined but not the short option, some had both and some had neither of
them.  Handling the actual parameters was also done in different ways,
some used inline code and others the handle_help_version_opts helper.
psql was its own case which had both, so removing the position one code
was all that was needed.  The getopt_long handling in psql is now the
one place which is a little different from the others as it's left the
way it was given that it already worked.

Since getopt_long will return '?' for invalid parameters as well as for
the -? parameter without any way to distinguish the case at hand, there
is a special function added which is used to separate the two.

While there, ensure that progname is used consistently when printing
the name of the app, and move progname from the global namespace to
improve consistency across the code.  TODO: this part of the patch
should probably be either ripped out or at least separated into its
own commit.

Author: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: ...
Reported-by: Matthias Hörmann <matthias.hoermann@saltation.com>
Discussion: https://postgr.es/m/19042-347869ae37c993f4@postgresql.org
---
 contrib/oid2name/oid2name.c                   | 38 ++++++++-------
 contrib/vacuumlo/vacuumlo.c                   | 33 ++++++-------
 src/bin/initdb/initdb.c                       | 33 ++++++-------
 src/bin/pg_amcheck/pg_amcheck.c               | 21 +++++++--
 src/bin/pg_archivecleanup/pg_archivecleanup.c | 43 +++++++++--------
 src/bin/pg_basebackup/pg_basebackup.c         | 39 ++++++++--------
 src/bin/pg_basebackup/pg_createsubscriber.c   | 38 +++++++--------
 src/bin/pg_basebackup/pg_receivewal.c         | 38 +++++++--------
 src/bin/pg_basebackup/pg_recvlogical.c        | 38 +++++++--------
 src/bin/pg_checksums/pg_checksums.c           | 38 +++++++--------
 src/bin/pg_combinebackup/pg_combinebackup.c   | 19 +++++++-
 src/bin/pg_controldata/pg_controldata.c       | 33 +++++++------
 src/bin/pg_ctl/pg_ctl.c                       | 38 +++++++--------
 src/bin/pg_dump/pg_dump.c                     | 36 ++++++++-------
 src/bin/pg_dump/pg_dumpall.c                  | 40 +++++++++-------
 src/bin/pg_dump/pg_restore.c                  | 36 +++++++++------
 src/bin/pg_resetwal/pg_resetwal.c             | 41 +++++++++--------
 src/bin/pg_rewind/pg_rewind.c                 | 38 ++++++++-------
 src/bin/pg_test_fsync/pg_test_fsync.c         | 42 +++++++++++------
 src/bin/pg_test_timing/pg_test_timing.c       | 43 +++++++++++------
 src/bin/pg_upgrade/option.c                   | 46 +++++++++++--------
 src/bin/pg_verifybackup/pg_verifybackup.c     | 40 ++++++++--------
 src/bin/pg_waldump/pg_waldump.c               | 38 ++++++++-------
 src/bin/pg_walsummary/pg_walsummary.c         | 21 +++++++--
 src/bin/pgbench/pgbench.c                     | 35 +++++++-------
 src/bin/psql/startup.c                        | 14 ------
 src/bin/scripts/clusterdb.c                   | 22 +++++++--
 src/bin/scripts/createdb.c                    | 22 +++++++--
 src/bin/scripts/createuser.c                  | 21 +++++++--
 src/bin/scripts/dropdb.c                      | 22 +++++++--
 src/bin/scripts/dropuser.c                    | 22 +++++++--
 src/bin/scripts/pg_isready.c                  | 21 +++++++--
 src/bin/scripts/reindexdb.c                   | 21 +++++++--
 src/bin/scripts/vacuumdb.c                    | 22 +++++++--
 src/bin/scripts/vacuuming.c                   |  1 -
 src/fe_utils/option_utils.c                   | 31 ++++++-------
 src/include/fe_utils/option_utils.h           |  5 --
 src/include/port.h                            |  3 ++
 src/interfaces/ecpg/preproc/ecpg.c            | 38 ++++++++-------
 src/port/Makefile                             |  1 +
 src/port/meson.build                          |  1 +
 src/port/pg_option_utils.c                    | 30 ++++++++++++
 src/test/regress/pg_regress.c                 |  3 +-
 43 files changed, 729 insertions(+), 476 deletions(-)
 create mode 100644 src/port/pg_option_utils.c

diff --git a/contrib/oid2name/oid2name.c b/contrib/oid2name/oid2name.c
index 51802907138..4f7a45363ac 100644
--- a/contrib/oid2name/oid2name.c
+++ b/contrib/oid2name/oid2name.c
@@ -77,7 +77,7 @@ get_opts(int argc, char **argv, struct options *my_opts)
 		{"username", required_argument, NULL, 'U'},
 		{"version", no_argument, NULL, 'V'},
 		{"extended", no_argument, NULL, 'x'},
-		{"help", no_argument, NULL, '?'},
+		{"help", no_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -101,22 +101,8 @@ get_opts(int argc, char **argv, struct options *my_opts)
 	my_opts->username = NULL;
 	my_opts->progname = progname;
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			help(progname);
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("oid2name (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
 	/* get opts */
-	while ((c = getopt_long(argc, argv, "d:f:h:H:io:p:qsSt:U:x", long_options, &optindex)) != -1)
+	while ((c = getopt_long(argc, argv, "d:f:h:H:io:p:qsSt:U:Vx?", long_options, &optindex)) != -1)
 	{
 		switch (c)
 		{
@@ -176,11 +162,31 @@ get_opts(int argc, char **argv, struct options *my_opts)
 				my_opts->username = pg_strdup(optarg);
 				break;
 
+				/* display version and exit */
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
+
 				/* display extra columns */
 			case 'x':
 				my_opts->extended = true;
 				break;
 
+				/* display help and exit */
+			case 1:
+				help(progname);
+				exit(0);
+
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					help(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/contrib/vacuumlo/vacuumlo.c b/contrib/vacuumlo/vacuumlo.c
index 16a450c4386..faf4bef7cbe 100644
--- a/contrib/vacuumlo/vacuumlo.c
+++ b/contrib/vacuumlo/vacuumlo.c
@@ -446,7 +446,7 @@ main(int argc, char **argv)
 		{"version", no_argument, NULL, 'V'},
 		{"no-password", no_argument, NULL, 'w'},
 		{"password", no_argument, NULL, 'W'},
-		{"help", no_argument, NULL, '?'},
+		{"help", no_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -471,21 +471,7 @@ main(int argc, char **argv)
 	param.transaction_limit = 1000;
 
 	/* Process command-line arguments */
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage(progname);
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("vacuumlo (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
-	while ((c = getopt_long(argc, argv, "h:l:np:U:vwW", long_options, &optindex)) != -1)
+	while ((c = getopt_long(argc, argv, "h:l:np:U:vVwW?", long_options, &optindex)) != -1)
 	{
 		switch (c)
 		{
@@ -513,12 +499,27 @@ main(int argc, char **argv)
 			case 'v':
 				param.verbose = 1;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 'w':
 				param.pg_prompt = TRI_NO;
 				break;
 			case 'W':
 				param.pg_prompt = TRI_YES;
 				break;
+			case 1:
+				usage(progname);
+				exit(0);
+				/* -? or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 92fe2f531f7..0c47922d656 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -3172,7 +3172,7 @@ main(int argc, char *argv[])
 		{"pwprompt", no_argument, NULL, 'W'},
 		{"pwfile", required_argument, NULL, 9},
 		{"username", required_argument, NULL, 'U'},
-		{"help", no_argument, NULL, '?'},
+		{"help", no_argument, NULL, 22},
 		{"version", no_argument, NULL, 'V'},
 		{"debug", no_argument, NULL, 'd'},
 		{"show", no_argument, NULL, 's'},
@@ -3220,23 +3220,9 @@ main(int argc, char *argv[])
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("initdb"));
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage(progname);
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("initdb (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
 	/* process command-line options */
 
-	while ((c = getopt_long(argc, argv, "A:c:dD:E:gkL:nNsST:U:WX:",
+	while ((c = getopt_long(argc, argv, "A:c:dD:E:gkL:nNsST:U:VWX:?",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -3392,6 +3378,21 @@ main(int argc, char *argv[])
 			case 21:
 				sync_data_files = false;
 				break;
+			case 22:
+				usage(progname);
+				exit(0);
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 2b1fd566c35..fb82bd89755 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -273,6 +273,8 @@ main(int argc, char *argv[])
 		{"install-missing", optional_argument, NULL, 13},
 		{"checkunique", no_argument, NULL, 14},
 
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 15},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -293,10 +295,8 @@ main(int argc, char *argv[])
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_amcheck"));
 
-	handle_help_version_opts(argc, argv, progname, help);
-
 	/* process command-line options */
-	while ((c = getopt_long(argc, argv, "ad:D:eh:Hi:I:j:p:Pr:R:s:S:t:T:U:vwW",
+	while ((c = getopt_long(argc, argv, "ad:D:eh:Hi:I:j:p:Pr:R:s:S:t:T:U:vVwW?",
 							long_options, &optindex)) != -1)
 	{
 		char	   *endptr;
@@ -372,6 +372,9 @@ main(int argc, char *argv[])
 				opts.verbose = true;
 				pg_logging_increase_verbosity();
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 'w':
 				prompt_password = TRI_NO;
 				break;
@@ -442,6 +445,18 @@ main(int argc, char *argv[])
 			case 14:
 				opts.checkunique = true;
 				break;
+			case 15:
+				help(progname);
+				exit(0);
+				/* -? or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					help(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_archivecleanup/pg_archivecleanup.c b/src/bin/pg_archivecleanup/pg_archivecleanup.c
index c25348bcb85..700c06a3f44 100644
--- a/src/bin/pg_archivecleanup/pg_archivecleanup.c
+++ b/src/bin/pg_archivecleanup/pg_archivecleanup.c
@@ -19,8 +19,6 @@
 #include "common/logging.h"
 #include "getopt_long.h"
 
-static const char *progname;
-
 /* Options and defaults */
 static bool dryrun = false;		/* are we performing a dry-run operation? */
 static bool cleanBackupHistory = false; /* remove files including backup
@@ -181,7 +179,7 @@ CleanupPriorWALFiles(void)
  *	  and decide whether we need cleanup
  */
 static void
-SetWALFileNameForCleanup(void)
+SetWALFileNameForCleanup(const char *progname)
 {
 	bool		fnameOK = false;
 
@@ -255,7 +253,7 @@ SetWALFileNameForCleanup(void)
  */
 
 static void
-usage(void)
+usage(const char *progname)
 {
 	printf(_("%s removes older WAL files from PostgreSQL archives.\n\n"), progname);
 	printf(_("Usage:\n"));
@@ -291,29 +289,19 @@ main(int argc, char **argv)
 		{"debug", no_argument, NULL, 'd'},
 		{"dry-run", no_argument, NULL, 'n'},
 		{"strip-extension", required_argument, NULL, 'x'},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 	int			c;
+	const char *progname;
+
 
 	pg_logging_init(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_archivecleanup"));
 	progname = get_progname(argv[0]);
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage();
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pg_archivecleanup (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
-	while ((c = getopt_long(argc, argv, "bdnx:", long_options, NULL)) != -1)
+	while ((c = getopt_long(argc, argv, "bdnx:V?", long_options, NULL)) != -1)
 	{
 		switch (c)
 		{
@@ -326,10 +314,25 @@ main(int argc, char **argv)
 			case 'n':			/* Dry-Run mode */
 				dryrun = true;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 'x':
 				additional_ext = pg_strdup(optarg); /* Extension to remove
 													 * from xlogfile names */
 				break;
+			case 1:
+				usage(progname);
+				exit(0);
+				/* -? or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -383,7 +386,7 @@ main(int argc, char **argv)
 	/*
 	 * Check filename is a valid name, then process to find cut-off
 	 */
-	SetWALFileNameForCleanup();
+	SetWALFileNameForCleanup(progname);
 
 	pg_log_debug("keeping WAL file \"%s/%s\" and later",
 				 archiveLocation, exclusiveCleanupFileName);
diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 0a3ca4315de..d34ed3751e0 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -195,7 +195,7 @@ static volatile LONG has_xlogendptr = 0;
 static PQExpBuffer recoveryconfcontents = NULL;
 
 /* Function headers */
-static void usage(void);
+static void usage(const char *progname);
 static void verify_dir_is_empty_or_create(char *dirname, bool *created, bool *found);
 static void progress_update_filename(const char *filename);
 static void progress_report(int tablespacenum, bool force, bool finished);
@@ -388,7 +388,7 @@ tablespace_list_append(const char *arg)
 
 
 static void
-usage(void)
+usage(const char *progname)
 {
 	printf(_("%s takes a base backup of a running PostgreSQL server.\n\n"),
 		   progname);
@@ -2357,7 +2357,7 @@ int
 main(int argc, char **argv)
 {
 	static struct option long_options[] = {
-		{"help", no_argument, NULL, '?'},
+		{"help", no_argument, NULL, 9},
 		{"version", no_argument, NULL, 'V'},
 		{"pgdata", required_argument, NULL, 'D'},
 		{"format", required_argument, NULL, 'F'},
@@ -2400,6 +2400,7 @@ main(int argc, char **argv)
 	char	   *compression_algorithm = "none";
 	char	   *compression_detail = NULL;
 	char	   *incremental_manifest = NULL;
+	const char *progname;
 	CompressionLocation compressloc = COMPRESS_LOCATION_UNSPECIFIED;
 	pg_compress_specification client_compress;
 
@@ -2407,24 +2408,9 @@ main(int argc, char **argv)
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_basebackup"));
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage();
-			exit(0);
-		}
-		else if (strcmp(argv[1], "-V") == 0
-				 || strcmp(argv[1], "--version") == 0)
-		{
-			puts("pg_basebackup (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
 	atexit(cleanup_directories_atexit);
 
-	while ((c = getopt_long(argc, argv, "c:Cd:D:F:h:i:l:nNp:Pr:Rs:S:t:T:U:vwWX:zZ:",
+	while ((c = getopt_long(argc, argv, "c:Cd:D:F:h:i:l:nNp:Pr:Rs:S:t:T:U:vVwWX:zZ:?",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -2511,6 +2497,9 @@ main(int argc, char **argv)
 			case 'v':
 				verbose++;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 'w':
 				dbgetpassword = -1;
 				break;
@@ -2571,6 +2560,18 @@ main(int argc, char **argv)
 				if (!parse_sync_method(optarg, &sync_method))
 					exit(1);
 				break;
+			case 9:
+				usage(progname);
+				exit(0);
+				/* -? or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index 9fa5caaf91d..442ad1445ae 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -77,7 +77,7 @@ struct LogicalRepInfos
 };
 
 static void cleanup_objects_atexit(void);
-static void usage();
+static void usage(const char *progname);
 static char *get_base_conninfo(const char *conninfo, char **dbname);
 static char *get_sub_conninfo(const struct CreateSubscriberOptions *opt);
 static char *get_exec_path(const char *argv0, const char *progname);
@@ -240,7 +240,7 @@ cleanup_objects_atexit(void)
 }
 
 static void
-usage(void)
+usage(const char *progname)
 {
 	printf(_("%s creates a new logical replica from a standby server.\n\n"),
 		   progname);
@@ -2047,7 +2047,7 @@ main(int argc, char **argv)
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
-		{"help", no_argument, NULL, '?'},
+		{"help", no_argument, NULL, 6},
 		{"config-file", required_argument, NULL, 1},
 		{"publication", required_argument, NULL, 2},
 		{"replication-slot", required_argument, NULL, 3},
@@ -2078,21 +2078,6 @@ main(int argc, char **argv)
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_basebackup"));
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage();
-			exit(0);
-		}
-		else if (strcmp(argv[1], "-V") == 0
-				 || strcmp(argv[1], "--version") == 0)
-		{
-			puts("pg_createsubscriber (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
 	/* Default settings */
 	subscriber_dir = NULL;
 	opt.config_file = NULL;
@@ -2124,7 +2109,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "ad:D:np:P:s:t:TU:v",
+	while ((c = getopt_long(argc, argv, "ad:D:np:P:s:t:TU:vV?",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -2170,6 +2155,9 @@ main(int argc, char **argv)
 			case 'v':
 				pg_logging_increase_verbosity();
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 1:
 				opt.config_file = pg_strdup(optarg);
 				break;
@@ -2206,6 +2194,18 @@ main(int argc, char **argv)
 				else
 					pg_fatal("object type \"%s\" specified more than once for --clean", optarg);
 				break;
+			case 6:
+				usage(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_basebackup/pg_receivewal.c b/src/bin/pg_basebackup/pg_receivewal.c
index 289ca14dcfe..c220dbb1ce7 100644
--- a/src/bin/pg_basebackup/pg_receivewal.c
+++ b/src/bin/pg_basebackup/pg_receivewal.c
@@ -56,7 +56,7 @@ static pg_compress_algorithm compression_algorithm = PG_COMPRESSION_NONE;
 static XLogRecPtr endpos = InvalidXLogRecPtr;
 
 
-static void usage(void);
+static void usage(const char *progname);
 static DIR *get_destination_dir(char *dest_folder);
 static void close_destination_dir(DIR *dest_dir, char *dest_folder);
 static XLogRecPtr FindStreamingStart(uint32 *tli);
@@ -72,7 +72,7 @@ disconnect_atexit(void)
 }
 
 static void
-usage(void)
+usage(const char *progname)
 {
 	printf(_("%s receives PostgreSQL streaming write-ahead logs.\n\n"),
 		   progname);
@@ -624,7 +624,7 @@ int
 main(int argc, char **argv)
 {
 	static struct option long_options[] = {
-		{"help", no_argument, NULL, '?'},
+		{"help", no_argument, NULL, 6},
 		{"version", no_argument, NULL, 'V'},
 		{"directory", required_argument, NULL, 'D'},
 		{"dbname", required_argument, NULL, 'd'},
@@ -662,22 +662,7 @@ main(int argc, char **argv)
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_basebackup"));
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage();
-			exit(0);
-		}
-		else if (strcmp(argv[1], "-V") == 0 ||
-				 strcmp(argv[1], "--version") == 0)
-		{
-			puts("pg_receivewal (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
-	while ((c = getopt_long(argc, argv, "d:D:E:h:np:s:S:U:vwWZ:",
+	while ((c = getopt_long(argc, argv, "d:D:E:h:np:s:S:U:vVwWZ:?",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -718,6 +703,9 @@ main(int argc, char **argv)
 			case 'v':
 				verbose++;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 'w':
 				dbgetpassword = -1;
 				break;
@@ -743,6 +731,18 @@ main(int argc, char **argv)
 			case 5:
 				do_sync = false;
 				break;
+			case 6:
+				usage(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_basebackup/pg_recvlogical.c b/src/bin/pg_basebackup/pg_recvlogical.c
index 7a4d1a2d2ca..b178571e62d 100644
--- a/src/bin/pg_basebackup/pg_recvlogical.c
+++ b/src/bin/pg_basebackup/pg_recvlogical.c
@@ -71,7 +71,7 @@ static bool output_needs_fsync = false;
 static XLogRecPtr output_written_lsn = InvalidXLogRecPtr;
 static XLogRecPtr output_fsync_lsn = InvalidXLogRecPtr;
 
-static void usage(void);
+static void usage(const char *progname);
 static void StreamLogicalLog(void);
 static bool flushAndSendFeedback(PGconn *conn, TimestampTz *now);
 static void prepareToTerminate(PGconn *conn, XLogRecPtr endpos,
@@ -79,7 +79,7 @@ static void prepareToTerminate(PGconn *conn, XLogRecPtr endpos,
 							   XLogRecPtr lsn);
 
 static void
-usage(void)
+usage(const char *progname)
 {
 	printf(_("%s controls PostgreSQL logical decoding streams.\n\n"),
 		   progname);
@@ -705,7 +705,7 @@ main(int argc, char **argv)
 		{"two-phase", no_argument, NULL, 't'},	/* deprecated */
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
-		{"help", no_argument, NULL, '?'},
+		{"help", no_argument, NULL, 6},
 /* connection options */
 		{"dbname", required_argument, NULL, 'd'},
 		{"host", required_argument, NULL, 'h'},
@@ -737,22 +737,7 @@ main(int argc, char **argv)
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_basebackup"));
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage();
-			exit(0);
-		}
-		else if (strcmp(argv[1], "-V") == 0 ||
-				 strcmp(argv[1], "--version") == 0)
-		{
-			puts("pg_recvlogical (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
-	while ((c = getopt_long(argc, argv, "E:f:F:ntvd:h:p:U:wWI:o:P:s:S:",
+	while ((c = getopt_long(argc, argv, "E:f:F:ntvd:h:p:U:VwWI:o:P:s:S:?",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -857,6 +842,21 @@ main(int argc, char **argv)
 				slot_exists_ok = true;
 				break;
 
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
+			case 6:
+				usage(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c
index 46cb2f36efa..1fdf8055946 100644
--- a/src/bin/pg_checksums/pg_checksums.c
+++ b/src/bin/pg_checksums/pg_checksums.c
@@ -55,8 +55,6 @@ typedef enum
 
 static PgChecksumMode mode = PG_MODE_CHECK;
 
-static const char *progname;
-
 /*
  * Progress status information.
  */
@@ -65,7 +63,7 @@ static int64 current_size = 0;
 static pg_time_t last_progress_report = 0;
 
 static void
-usage(void)
+usage(const char *progname)
 {
 	printf(_("%s enables, disables, or verifies data checksums in a PostgreSQL database cluster.\n\n"), progname);
 	printf(_("Usage:\n"));
@@ -442,6 +440,8 @@ main(int argc, char *argv[])
 		{"progress", no_argument, NULL, 'P'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"sync-method", required_argument, NULL, 1},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 2},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -451,26 +451,13 @@ main(int argc, char *argv[])
 	bool		crc_ok;
 	uint32		major_version;
 	char	   *version_str;
+	const char *progname;
 
 	pg_logging_init(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_checksums"));
 	progname = get_progname(argv[0]);
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage();
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pg_checksums (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
-	while ((c = getopt_long(argc, argv, "cdD:ef:NPv", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "cdD:ef:NPvV?", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -502,10 +489,25 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 1:
 				if (!parse_sync_method(optarg, &sync_method))
 					exit(1);
 				break;
+			case 2:
+				usage(progname);
+				exit(0);
+				/* -? or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_combinebackup/pg_combinebackup.c b/src/bin/pg_combinebackup/pg_combinebackup.c
index 3a325127209..09b561908c1 100644
--- a/src/bin/pg_combinebackup/pg_combinebackup.c
+++ b/src/bin/pg_combinebackup/pg_combinebackup.c
@@ -143,6 +143,8 @@ main(int argc, char *argv[])
 		{"clone", no_argument, NULL, 4},
 		{"copy", no_argument, NULL, 5},
 		{"copy-file-range", no_argument, NULL, 6},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 7},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -167,7 +169,6 @@ main(int argc, char *argv[])
 	pg_logging_init(argv[0]);
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_combinebackup"));
-	handle_help_version_opts(argc, argv, progname, help);
 
 	memset(&opt, 0, sizeof(opt));
 	opt.manifest_checksums = CHECKSUM_TYPE_CRC32C;
@@ -175,7 +176,7 @@ main(int argc, char *argv[])
 	opt.copy_method = COPY_METHOD_COPY;
 
 	/* process command-line options */
-	while ((c = getopt_long(argc, argv, "dknNo:T:",
+	while ((c = getopt_long(argc, argv, "dknNo:T:V?",
 							long_options, &optindex)) != -1)
 	{
 		switch (c)
@@ -221,6 +222,20 @@ main(int argc, char *argv[])
 			case 6:
 				opt.copy_method = COPY_METHOD_COPY_FILE_RANGE;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
+			case 7:
+				help(progname);
+				exit(0);
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					help(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index 10de058ce91..c4b050b9b96 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -90,6 +90,8 @@ main(int argc, char *argv[])
 {
 	static struct option long_options[] = {
 		{"pgdata", required_argument, NULL, 'D'},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -112,28 +114,29 @@ main(int argc, char *argv[])
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_controldata"));
 	progname = get_progname(argv[0]);
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage(progname);
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pg_controldata (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
-	while ((c = getopt_long(argc, argv, "D:", long_options, NULL)) != -1)
+	while ((c = getopt_long(argc, argv, "D:V?", long_options, NULL)) != -1)
 	{
 		switch (c)
 		{
 			case 'D':
 				DataDir = optarg;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
+			case 1:
+				usage(progname);
+				exit(0);
 
+				/* -? or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 8a405ff122c..8995855537f 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -115,7 +115,7 @@ static HANDLE shutdownHandles[2];
 
 static void write_stderr(const char *fmt,...) pg_attribute_printf(1, 2);
 static void do_advice(void);
-static void do_help(void);
+static void do_help(const char *progname);
 static void set_mode(char *modeopt);
 static void set_sig(char *signame);
 static void do_init(void);
@@ -1970,7 +1970,7 @@ do_advice(void)
 
 
 static void
-do_help(void)
+do_help(const char *progname)
 {
 	printf(_("%s is a utility to initialize, start, stop, or control a PostgreSQL server.\n\n"), progname);
 	printf(_("Usage:\n"));
@@ -2202,7 +2202,7 @@ int
 main(int argc, char **argv)
 {
 	static struct option long_options[] = {
-		{"help", no_argument, NULL, '?'},
+		{"help", no_argument, NULL, 1},
 		{"version", no_argument, NULL, 'V'},
 		{"log", required_argument, NULL, 'l'},
 		{"mode", required_argument, NULL, 'm'},
@@ -2235,21 +2235,6 @@ main(int argc, char **argv)
 	/* Set restrictive mode mask until PGDATA permissions are checked */
 	umask(PG_MODE_MASK_OWNER);
 
-	/* support --help and --version even if invoked as root */
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			do_help();
-			exit(0);
-		}
-		else if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pg_ctl (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
 	/*
 	 * Disallow running as root, to forestall any possible security holes.
 	 */
@@ -2270,7 +2255,7 @@ main(int argc, char **argv)
 		wait_seconds = atoi(env_wait);
 
 	/* process command-line options */
-	while ((c = getopt_long(argc, argv, "cD:e:l:m:N:o:p:P:sS:t:U:wW",
+	while ((c = getopt_long(argc, argv, "cD:e:l:m:N:o:p:P:sS:t:U:VwW?",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -2353,6 +2338,21 @@ main(int argc, char **argv)
 			case 'c':
 				allow_core_files = true;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
+			case 1:
+				do_help(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					do_help(progname);
+					exit(0);
+				}
+				do_advice();
+				exit(1);
 			default:
 				/* getopt_long already issued a suitable error message */
 				do_advice();
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 47913178a93..b452a0b95a4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -487,7 +487,7 @@ main(int argc, char **argv)
 		{"no-acl", no_argument, NULL, 'x'},
 		{"compress", required_argument, NULL, 'Z'},
 		{"encoding", required_argument, NULL, 'E'},
-		{"help", no_argument, NULL, '?'},
+		{"help", no_argument, NULL, 26},
 		{"version", no_argument, NULL, 'V'},
 
 		/*
@@ -554,23 +554,9 @@ main(int argc, char **argv)
 
 	progname = get_progname(argv[0]);
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			help(progname);
-			exit_nicely(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pg_dump (PostgreSQL) " PG_VERSION);
-			exit_nicely(0);
-		}
-	}
-
 	InitDumpOptions(&dopt);
 
-	while ((c = getopt_long(argc, argv, "abBcCd:e:E:f:F:h:j:n:N:Op:RsS:t:T:U:vwWxXZ:",
+	while ((c = getopt_long(argc, argv, "abBcCd:e:E:f:F:h:j:n:N:Op:RsS:t:T:U:vVwWxXZ:?",
 							long_options, &optindex)) != -1)
 	{
 		switch (c)
@@ -674,6 +660,10 @@ main(int argc, char **argv)
 				pg_logging_increase_verbosity();
 				break;
 
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit_nicely(0);
+
 			case 'w':
 				dopt.cparams.promptPassword = TRI_NO;
 				break;
@@ -802,6 +792,20 @@ main(int argc, char **argv)
 				dopt.restrict_key = pg_strdup(optarg);
 				break;
 
+			case 26:
+				help(progname);
+				exit_nicely(0);
+
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					help(progname);
+					exit_nicely(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit_nicely(1);
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index bb451c1bae1..7952af6a5f4 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -55,7 +55,7 @@ typedef struct
 #define SH_DEFINE
 #include "lib/simplehash.h"
 
-static void help(void);
+static void help(const char *progname);
 
 static void dropRoles(PGconn *conn);
 static void dumpRoles(PGconn *conn);
@@ -187,6 +187,8 @@ main(int argc, char *argv[])
 		{"filter", required_argument, NULL, 8},
 		{"sequence-data", no_argument, &sequence_data, 1},
 		{"restrict-key", required_argument, NULL, 9},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 10},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -214,20 +216,6 @@ main(int argc, char *argv[])
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_dump"));
 	progname = get_progname(argv[0]);
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			help();
-			exit_nicely(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pg_dumpall (PostgreSQL) " PG_VERSION);
-			exit_nicely(0);
-		}
-	}
-
 	if ((ret = find_other_exec(argv[0], "pg_dump", PGDUMP_VERSIONSTR,
 							   pg_dump_bin)) < 0)
 	{
@@ -246,7 +234,7 @@ main(int argc, char *argv[])
 
 	pgdumpopts = createPQExpBuffer();
 
-	while ((c = getopt_long(argc, argv, "acd:E:f:gh:l:Op:rsS:tU:vwWx", long_options, &optindex)) != -1)
+	while ((c = getopt_long(argc, argv, "acd:E:f:gh:l:Op:rsS:tU:vVwWx?", long_options, &optindex)) != -1)
 	{
 		switch (c)
 		{
@@ -322,6 +310,10 @@ main(int argc, char *argv[])
 				appendPQExpBufferStr(pgdumpopts, " -v");
 				break;
 
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit_nicely(0);
+
 			case 'w':
 				prompt_password = TRI_NO;
 				appendPQExpBufferStr(pgdumpopts, " -w");
@@ -380,6 +372,20 @@ main(int argc, char *argv[])
 				appendShellString(pgdumpopts, optarg);
 				break;
 
+			case 10:
+				help(progname);
+				exit_nicely(0);
+
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					help(progname);
+					exit_nicely(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit_nicely(1);
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -688,7 +694,7 @@ main(int argc, char *argv[])
 
 
 static void
-help(void)
+help(const char *progname)
 {
 	printf(_("%s exports a PostgreSQL database cluster as an SQL script.\n\n"), progname);
 	printf(_("Usage:\n"));
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index c9776306c5c..dbef274d65c 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -142,6 +142,8 @@ main(int argc, char **argv)
 		{"statistics-only", no_argument, &statistics_only, 1},
 		{"filter", required_argument, NULL, 4},
 		{"restrict-key", required_argument, NULL, 6},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 7},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -156,21 +158,7 @@ main(int argc, char **argv)
 
 	progname = get_progname(argv[0]);
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage(progname);
-			exit_nicely(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pg_restore (PostgreSQL) " PG_VERSION);
-			exit_nicely(0);
-		}
-	}
-
-	while ((c = getopt_long(argc, argv, "acCd:ef:F:h:I:j:lL:n:N:Op:P:RsS:t:T:U:vwWx1",
+	while ((c = getopt_long(argc, argv, "acCd:ef:F:h:I:j:lL:n:N:Op:P:RsS:t:T:U:vVwWx1?",
 							cmdopts, NULL)) != -1)
 	{
 		switch (c)
@@ -273,6 +261,10 @@ main(int argc, char **argv)
 				pg_logging_increase_verbosity();
 				break;
 
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit_nicely(0);
+
 			case 'w':
 				opts->cparams.promptPassword = TRI_NO;
 				break;
@@ -321,6 +313,20 @@ main(int argc, char **argv)
 				opts->restrict_key = pg_strdup(optarg);
 				break;
 
+			case 7:
+				usage(progname);
+				exit_nicely(0);
+
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit_nicely(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit_nicely(1);
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index a89d72fc5cf..82ac2addf03 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -89,7 +89,7 @@ static void KillExistingXLOG(void);
 static void KillExistingArchiveStatus(void);
 static void KillExistingWALSummaries(void);
 static void WriteEmptyXLOG(void);
-static void usage(void);
+static void usage(const char *progname);
 
 
 int
@@ -109,6 +109,8 @@ main(int argc, char *argv[])
 		{"next-transaction-id", required_argument, NULL, 'x'},
 		{"wal-segsize", required_argument, NULL, 1},
 		{"char-signedness", required_argument, NULL, 2},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -126,22 +128,7 @@ main(int argc, char *argv[])
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_resetwal"));
 	progname = get_progname(argv[0]);
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage();
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pg_resetwal (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
-
-	while ((c = getopt_long(argc, argv, "c:D:e:fl:m:no:O:u:x:", long_options, NULL)) != -1)
+	while ((c = getopt_long(argc, argv, "c:D:e:fl:m:no:O:u:Vx:?", long_options, NULL)) != -1)
 	{
 		switch (c)
 		{
@@ -293,6 +280,10 @@ main(int argc, char *argv[])
 				log_fname = pg_strdup(optarg);
 				break;
 
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
+
 			case 1:
 				{
 					int			wal_segsize_mb;
@@ -322,6 +313,20 @@ main(int argc, char *argv[])
 					break;
 				}
 
+			case 3:
+				usage(progname);
+				exit(0);
+
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -1171,7 +1176,7 @@ WriteEmptyXLOG(void)
 
 
 static void
-usage(void)
+usage(const char *progname)
 {
 	printf(_("%s resets the PostgreSQL write-ahead log.\n\n"), progname);
 	printf(_("Usage:\n"));
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 0c68dd4235e..cc2fd37bf60 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -121,7 +121,8 @@ int
 main(int argc, char **argv)
 {
 	static struct option long_options[] = {
-		{"help", no_argument, NULL, '?'},
+		{"help", no_argument, NULL, 7},
+		{"version", no_argument, NULL, 'V'},
 		{"target-pgdata", required_argument, NULL, 'D'},
 		{"write-recovery-conf", no_argument, NULL, 'R'},
 		{"source-pgdata", required_argument, NULL, 1},
@@ -158,22 +159,7 @@ main(int argc, char **argv)
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_rewind"));
 	progname = get_progname(argv[0]);
 
-	/* Process command-line arguments */
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage(progname);
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pg_rewind (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
-	while ((c = getopt_long(argc, argv, "cD:nNPR", long_options, &option_index)) != -1)
+	while ((c = getopt_long(argc, argv, "cD:nNPRV?", long_options, &option_index)) != -1)
 	{
 		switch (c)
 		{
@@ -227,6 +213,24 @@ main(int argc, char **argv)
 					exit(1);
 				break;
 
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
+
+			case 7:
+				usage(progname);
+				exit(0);
+
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_test_fsync/pg_test_fsync.c b/src/bin/pg_test_fsync/pg_test_fsync.c
index 0060ea15902..85739bd88c4 100644
--- a/src/bin/pg_test_fsync/pg_test_fsync.c
+++ b/src/bin/pg_test_fsync/pg_test_fsync.c
@@ -142,12 +142,20 @@ main(int argc, char *argv[])
 	return 0;
 }
 
+static void
+usage(const char *progname)
+{
+	printf(_("Usage: %s [-f FILENAME] [-s SECS-PER-TEST]\n"), progname);
+}
+
 static void
 handle_args(int argc, char *argv[])
 {
 	static struct option long_options[] = {
 		{"filename", required_argument, NULL, 'f'},
 		{"secs-per-test", required_argument, NULL, 's'},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -156,21 +164,7 @@ handle_args(int argc, char *argv[])
 	unsigned long optval;		/* used for option parsing */
 	char	   *endptr;
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			printf(_("Usage: %s [-f FILENAME] [-s SECS-PER-TEST]\n"), progname);
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pg_test_fsync (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
-	while ((option = getopt_long(argc, argv, "f:s:",
+	while ((option = getopt_long(argc, argv, "f:s:V?",
 								 long_options, &optindex)) != -1)
 	{
 		switch (option)
@@ -197,6 +191,24 @@ handle_args(int argc, char *argv[])
 							 "--secs-per-test", 1, UINT_MAX);
 				break;
 
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
+
+			case 1:
+				usage(progname);
+				exit(0);
+
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
+
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pg_test_timing/pg_test_timing.c b/src/bin/pg_test_timing/pg_test_timing.c
index a5621251afc..25e44aa79f7 100644
--- a/src/bin/pg_test_timing/pg_test_timing.c
+++ b/src/bin/pg_test_timing/pg_test_timing.c
@@ -50,12 +50,20 @@ main(int argc, char *argv[])
 	return 0;
 }
 
+static void
+usage(const char *progname)
+{
+	printf(_("Usage: %s [-d DURATION] [-c CUTOFF]\n"), progname);
+}
+
 static void
 handle_args(int argc, char *argv[])
 {
 	static struct option long_options[] = {
 		{"duration", required_argument, NULL, 'd'},
 		{"cutoff", required_argument, NULL, 'c'},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -64,21 +72,7 @@ handle_args(int argc, char *argv[])
 	unsigned long optval;		/* used for option parsing */
 	char	   *endptr;
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			printf(_("Usage: %s [-d DURATION] [-c CUTOFF]\n"), progname);
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pg_test_timing (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
-	while ((option = getopt_long(argc, argv, "d:c:",
+	while ((option = getopt_long(argc, argv, "d:c:V?",
 								 long_options, &optindex)) != -1)
 	{
 		switch (option)
@@ -125,6 +119,25 @@ handle_args(int argc, char *argv[])
 				}
 				break;
 
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
+
+			case 1:
+				usage(progname);
+				exit(0);
+
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+						progname);
+				exit(1);
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						progname);
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 7fd7f1d33fc..557919e9d90 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -19,7 +19,7 @@
 #include "pg_upgrade.h"
 #include "utils/pidfile.h"
 
-static void usage(void);
+static void usage(const char *progname);
 static void check_required_directory(char **dirpath,
 									 const char *envVarName, bool useCwd,
 									 const char *cmdLineOption, const char *description,
@@ -64,6 +64,9 @@ parseCommandLine(int argc, char *argv[])
 		{"set-char-signedness", required_argument, NULL, 6},
 		{"swap", no_argument, NULL, 7},
 
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 8},
+
 		{NULL, 0, NULL, 0}
 	};
 	int			option;			/* Command line option */
@@ -91,25 +94,11 @@ parseCommandLine(int argc, char *argv[])
 		os_info.user = pg_strdup(getenv("PGUSER"));
 	}
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage();
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pg_upgrade (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
 	/* Allow help and version to be run as root, so do the test here. */
 	if (os_user_effective_id == 0)
 		pg_fatal("%s: cannot be run as root", os_info.progname);
 
-	while ((option = getopt_long(argc, argv, "b:B:cd:D:j:kNo:O:p:P:rs:U:v",
+	while ((option = getopt_long(argc, argv, "b:B:cd:D:j:kNo:O:p:P:rs:U:vV?",
 								 long_options, &optindex)) != -1)
 	{
 		switch (option)
@@ -200,6 +189,10 @@ parseCommandLine(int argc, char *argv[])
 				log_opts.verbose = true;
 				break;
 
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", os_info.progname);
+				exit(0);
+
 			case 1:
 				user_opts.transfer_mode = TRANSFER_MODE_CLONE;
 				break;
@@ -234,6 +227,21 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_SWAP;
 				break;
 
+			case 8:
+				usage(os_info.progname);
+				exit(0);
+
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(os_info.progname);
+					exit(0);
+				}
+				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+						os_info.progname);
+				exit(1);
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -301,11 +309,11 @@ parseCommandLine(int argc, char *argv[])
 
 
 static void
-usage(void)
+usage(const char *progname)
 {
-	printf(_("pg_upgrade upgrades a PostgreSQL cluster to a different major version.\n\n"));
+	printf(_("%s upgrades a PostgreSQL cluster to a different major version.\n\n"), progname);
 	printf(_("Usage:\n"));
-	printf(_("  pg_upgrade [OPTION]...\n\n"));
+	printf(_("  %s [OPTION]...\n\n"), progname);
 	printf(_("Options:\n"));
 	printf(_("  -b, --old-bindir=BINDIR       old cluster executable directory\n"));
 	printf(_("  -B, --new-bindir=BINDIR       new cluster executable directory (default\n"
diff --git a/src/bin/pg_verifybackup/pg_verifybackup.c b/src/bin/pg_verifybackup/pg_verifybackup.c
index 5e6c13bb921..0b68167ca82 100644
--- a/src/bin/pg_verifybackup/pg_verifybackup.c
+++ b/src/bin/pg_verifybackup/pg_verifybackup.c
@@ -100,9 +100,7 @@ static astreamer *create_archive_verifier(verifier_context *context,
 										  pg_compress_algorithm compress_algo);
 
 static void progress_report(bool finished);
-static void usage(void);
-
-static const char *progname;
+static void usage(const char *progname);
 
 /* is progress reporting enabled? */
 static bool show_progress = false;
@@ -127,6 +125,8 @@ main(int argc, char **argv)
 		{"quiet", no_argument, NULL, 'q'},
 		{"skip-checksums", no_argument, NULL, 's'},
 		{"wal-directory", required_argument, NULL, 'w'},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -138,6 +138,7 @@ main(int argc, char **argv)
 	char	   *wal_directory = NULL;
 	char	   *pg_waldump_path = NULL;
 	DIR		   *dir;
+	const char *progname;
 
 	pg_logging_init(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_verifybackup"));
@@ -145,20 +146,6 @@ main(int argc, char **argv)
 
 	memset(&context, 0, sizeof(context));
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage();
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pg_verifybackup (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
 	/*
 	 * Skip certain files in the toplevel directory.
 	 *
@@ -180,7 +167,7 @@ main(int argc, char **argv)
 	simple_string_list_append(&context.ignore_list, "recovery.signal");
 	simple_string_list_append(&context.ignore_list, "standby.signal");
 
-	while ((c = getopt_long(argc, argv, "eF:i:m:nPqsw:", long_options, NULL)) != -1)
+	while ((c = getopt_long(argc, argv, "eF:i:m:nPqsVw:?", long_options, NULL)) != -1)
 	{
 		switch (c)
 		{
@@ -220,10 +207,25 @@ main(int argc, char **argv)
 			case 's':
 				context.skip_checksums = true;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 'w':
 				wal_directory = pstrdup(optarg);
 				canonicalize_path(wal_directory);
 				break;
+			case 1:
+				usage(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -1363,7 +1365,7 @@ progress_report(bool finished)
  * Print out usage information and exit.
  */
 static void
-usage(void)
+usage(const char *progname)
 {
 	printf(_("%s verifies a backup against the backup manifest.\n\n"), progname);
 	printf(_("Usage:\n  %s [OPTION]... BACKUPDIR\n\n"), progname);
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index 13d3ec2f5be..5159893b619 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -37,8 +37,6 @@
  * give a thought about doing the same in pg_walinspect contrib module as well.
  */
 
-static const char *progname;
-
 static int	WalSegSz;
 static volatile sig_atomic_t time_to_stop = false;
 
@@ -753,7 +751,7 @@ XLogDumpDisplayStats(XLogDumpConfig *config, XLogStats *stats)
 }
 
 static void
-usage(void)
+usage(const char *progname)
 {
 	printf(_("%s decodes and displays PostgreSQL write-ahead logs for debugging.\n\n"),
 		   progname);
@@ -809,7 +807,7 @@ main(int argc, char **argv)
 		{"follow", no_argument, NULL, 'f'},
 		{"fork", required_argument, NULL, 'F'},
 		{"fullpage", no_argument, NULL, 'w'},
-		{"help", no_argument, NULL, '?'},
+		{"help", no_argument, NULL, 2},
 		{"limit", required_argument, NULL, 'n'},
 		{"path", required_argument, NULL, 'p'},
 		{"quiet", no_argument, NULL, 'q'},
@@ -826,7 +824,7 @@ main(int argc, char **argv)
 
 	int			option;
 	int			optindex = 0;
-
+	const char *progname;
 #ifndef WIN32
 	pqsignal(SIGINT, sigint_handler);
 #endif
@@ -835,20 +833,6 @@ main(int argc, char **argv)
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_waldump"));
 	progname = get_progname(argv[0]);
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage();
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pg_waldump (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
 	memset(&private, 0, sizeof(XLogDumpPrivate));
 	memset(&config, 0, sizeof(XLogDumpConfig));
 	memset(&stats, 0, sizeof(XLogStats));
@@ -885,7 +869,7 @@ main(int argc, char **argv)
 		goto bad_argument;
 	}
 
-	while ((option = getopt_long(argc, argv, "bB:e:fF:n:p:qr:R:s:t:wx:z",
+	while ((option = getopt_long(argc, argv, "bB:e:fF:n:p:qr:R:s:t:Vwx:z?",
 								 long_options, &optindex)) != -1)
 	{
 		switch (option)
@@ -1046,6 +1030,9 @@ main(int argc, char **argv)
 
 					break;
 				}
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 'w':
 				config.filter_by_fpw = true;
 				break;
@@ -1076,6 +1063,17 @@ main(int argc, char **argv)
 			case 1:
 				config.save_fullpage_path = pg_strdup(optarg);
 				break;
+			case 2:
+				usage(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				goto bad_argument;
 			default:
 				goto bad_argument;
 		}
diff --git a/src/bin/pg_walsummary/pg_walsummary.c b/src/bin/pg_walsummary/pg_walsummary.c
index cd7a6b147b2..ac39dddd724 100644
--- a/src/bin/pg_walsummary/pg_walsummary.c
+++ b/src/bin/pg_walsummary/pg_walsummary.c
@@ -18,7 +18,6 @@
 #include "common/blkreftable.h"
 #include "common/int.h"
 #include "common/logging.h"
-#include "fe_utils/option_utils.h"
 #include "getopt_long.h"
 
 typedef struct ws_options
@@ -54,6 +53,8 @@ main(int argc, char *argv[])
 	static struct option long_options[] = {
 		{"individual", no_argument, NULL, 'i'},
 		{"quiet", no_argument, NULL, 'q'},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -67,10 +68,9 @@ main(int argc, char *argv[])
 	pg_logging_init(argv[0]);
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_walsummary"));
-	handle_help_version_opts(argc, argv, progname, help);
 
 	/* process command-line options */
-	while ((c = getopt_long(argc, argv, "iq",
+	while ((c = getopt_long(argc, argv, "iqV?",
 							long_options, &optindex)) != -1)
 	{
 		switch (c)
@@ -81,6 +81,21 @@ main(int argc, char *argv[])
 			case 'q':
 				opt.quiet = true;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
+			case 1:
+				help(progname);
+				exit(0);
+				/* -? or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					help(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 1515ed405ba..0c34a533499 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -894,7 +894,7 @@ pg_time_now_lazy(pg_time_usec_t *now)
 #define PG_TIME_GET_DOUBLE(t) (0.000001 * (t))
 
 static void
-usage(void)
+usage(const char *progname)
 {
 	printf("%s is a benchmarking tool for PostgreSQL.\n\n"
 		   "Usage:\n"
@@ -6739,6 +6739,8 @@ main(int argc, char **argv)
 		{"verbose-errors", no_argument, NULL, 15},
 		{"exit-on-abort", no_argument, NULL, 16},
 		{"debug", no_argument, NULL, 17},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 18},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -6790,27 +6792,13 @@ main(int argc, char **argv)
 	pg_logging_init(argv[0]);
 	progname = get_progname(argv[0]);
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			usage();
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			puts("pgbench (PostgreSQL) " PG_VERSION);
-			exit(0);
-		}
-	}
-
 	state = (CState *) pg_malloc0(sizeof(CState));
 
 	/* set random seed early, because it may be used while parsing scripts. */
 	if (!set_random_seed(getenv("PGBENCH_RANDOM_SEED")))
 		pg_fatal("error while setting random seed from PGBENCH_RANDOM_SEED environment variable");
 
-	while ((c = getopt_long(argc, argv, "b:c:Cd:D:f:F:h:iI:j:lL:M:nNp:P:qrR:s:St:T:U:v", long_options, &optindex)) != -1)
+	while ((c = getopt_long(argc, argv, "b:c:Cd:D:f:F:h:iI:j:lL:M:nNp:P:qrR:s:St:T:U:vV?", long_options, &optindex)) != -1)
 	{
 		char	   *script;
 
@@ -7001,6 +6989,9 @@ main(int argc, char **argv)
 				benchmarking_option_set = true;
 				do_vacuum_accounts = true;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 1:				/* unlogged-tables */
 				initialization_option_set = true;
 				unlogged_tables = true;
@@ -7092,6 +7083,18 @@ main(int argc, char **argv)
 			case 17:			/* debug */
 				pg_logging_increase_verbosity();
 				break;
+			case 18:
+				usage(progname);
+				exit(0);
+				/* -? or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					usage(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 249b6aa5169..73046382c9b 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -135,20 +135,6 @@ main(int argc, char *argv[])
 	pg_logging_set_locus_callback(log_locus_callback);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("psql"));
 
-	if (argc > 1)
-	{
-		if ((strcmp(argv[1], "-?") == 0) || (argc == 2 && (strcmp(argv[1], "--help") == 0)))
-		{
-			usage(NOPAGER);
-			exit(EXIT_SUCCESS);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			showVersion();
-			exit(EXIT_SUCCESS);
-		}
-	}
-
 	pset.progname = get_progname(argv[0]);
 
 	pset.db = NULL;
diff --git a/src/bin/scripts/clusterdb.c b/src/bin/scripts/clusterdb.c
index a0aa950b784..4107e6ff98c 100644
--- a/src/bin/scripts/clusterdb.c
+++ b/src/bin/scripts/clusterdb.c
@@ -13,7 +13,6 @@
 #include "common.h"
 #include "common/logging.h"
 #include "fe_utils/cancel.h"
-#include "fe_utils/option_utils.h"
 #include "fe_utils/query_utils.h"
 #include "fe_utils/simple_list.h"
 
@@ -42,6 +41,8 @@ main(int argc, char *argv[])
 		{"table", required_argument, NULL, 't'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"maintenance-db", required_argument, NULL, 2},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -66,9 +67,7 @@ main(int argc, char *argv[])
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
 
-	handle_help_version_opts(argc, argv, "clusterdb", help);
-
-	while ((c = getopt_long(argc, argv, "ad:eh:p:qt:U:vwW", long_options, &optindex)) != -1)
+	while ((c = getopt_long(argc, argv, "ad:eh:p:qt:U:vVwW?", long_options, &optindex)) != -1)
 	{
 		switch (c)
 		{
@@ -99,6 +98,9 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 'w':
 				prompt_password = TRI_NO;
 				break;
@@ -108,6 +110,18 @@ main(int argc, char *argv[])
 			case 2:
 				maintenance_db = pg_strdup(optarg);
 				break;
+			case 3:
+				help(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					help(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/scripts/createdb.c b/src/bin/scripts/createdb.c
index 7c0cf32d6a1..6fbbf491dfd 100644
--- a/src/bin/scripts/createdb.c
+++ b/src/bin/scripts/createdb.c
@@ -13,7 +13,6 @@
 
 #include "common.h"
 #include "common/logging.h"
-#include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 
 
@@ -43,6 +42,8 @@ main(int argc, char *argv[])
 		{"builtin-locale", required_argument, NULL, 5},
 		{"icu-locale", required_argument, NULL, 6},
 		{"icu-rules", required_argument, NULL, 7},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 8},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -81,9 +82,7 @@ main(int argc, char *argv[])
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
 
-	handle_help_version_opts(argc, argv, "createdb", help);
-
-	while ((c = getopt_long(argc, argv, "D:eE:h:l:O:p:S:T:U:wW", long_options, &optindex)) != -1)
+	while ((c = getopt_long(argc, argv, "D:eE:h:l:O:p:S:T:U:VwW?", long_options, &optindex)) != -1)
 	{
 		switch (c)
 		{
@@ -117,6 +116,9 @@ main(int argc, char *argv[])
 			case 'U':
 				username = pg_strdup(optarg);
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 'w':
 				prompt_password = TRI_NO;
 				break;
@@ -144,6 +146,18 @@ main(int argc, char *argv[])
 			case 7:
 				icu_rules = pg_strdup(optarg);
 				break;
+			case 8:
+				help(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					help(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/scripts/createuser.c b/src/bin/scripts/createuser.c
index 81e6abfc46e..44fa1785be5 100644
--- a/src/bin/scripts/createuser.c
+++ b/src/bin/scripts/createuser.c
@@ -57,6 +57,8 @@ main(int argc, char *argv[])
 		{"interactive", no_argument, NULL, 3},
 		{"bypassrls", no_argument, NULL, 4},
 		{"no-bypassrls", no_argument, NULL, 5},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 6},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -97,9 +99,7 @@ main(int argc, char *argv[])
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
 
-	handle_help_version_opts(argc, argv, "createuser", help);
-
-	while ((c = getopt_long(argc, argv, "a:c:dDeEg:h:iIlLm:p:PrRsSU:v:wW",
+	while ((c = getopt_long(argc, argv, "a:c:dDeEg:h:iIlLm:p:PrRsSU:v:VwW?",
 							long_options, &optindex)) != -1)
 	{
 		switch (c)
@@ -169,6 +169,9 @@ main(int argc, char *argv[])
 			case 'v':
 				pwexpiry = pg_strdup(optarg);
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 'w':
 				prompt_password = TRI_NO;
 				break;
@@ -190,6 +193,18 @@ main(int argc, char *argv[])
 			case 5:
 				bypassrls = TRI_NO;
 				break;
+			case 6:
+				help(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					help(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index 0b630818277..9ab2d170ff9 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -13,7 +13,6 @@
 #include "postgres_fe.h"
 #include "common.h"
 #include "common/logging.h"
-#include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 
 
@@ -36,6 +35,8 @@ main(int argc, char *argv[])
 		{"if-exists", no_argument, &if_exists, 1},
 		{"maintenance-db", required_argument, NULL, 2},
 		{"force", no_argument, NULL, 'f'},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 3},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -63,9 +64,7 @@ main(int argc, char *argv[])
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
 
-	handle_help_version_opts(argc, argv, "dropdb", help);
-
-	while ((c = getopt_long(argc, argv, "efh:ip:U:wW", long_options, &optindex)) != -1)
+	while ((c = getopt_long(argc, argv, "efh:ip:U:wWV?", long_options, &optindex)) != -1)
 	{
 		switch (c)
 		{
@@ -87,6 +86,9 @@ main(int argc, char *argv[])
 			case 'U':
 				username = pg_strdup(optarg);
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 'w':
 				prompt_password = TRI_NO;
 				break;
@@ -99,6 +101,18 @@ main(int argc, char *argv[])
 			case 2:
 				maintenance_db = pg_strdup(optarg);
 				break;
+			case 3:
+				help(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					help(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/scripts/dropuser.c b/src/bin/scripts/dropuser.c
index 39bb1686173..d55d5a34aa3 100644
--- a/src/bin/scripts/dropuser.c
+++ b/src/bin/scripts/dropuser.c
@@ -14,7 +14,6 @@
 #include "common.h"
 #include "common/logging.h"
 #include "common/string.h"
-#include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
 
 
@@ -35,6 +34,8 @@ main(int argc, char *argv[])
 		{"echo", no_argument, NULL, 'e'},
 		{"interactive", no_argument, NULL, 'i'},
 		{"if-exists", no_argument, &if_exists, 1},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 2},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -60,9 +61,7 @@ main(int argc, char *argv[])
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
 
-	handle_help_version_opts(argc, argv, "dropuser", help);
-
-	while ((c = getopt_long(argc, argv, "eh:ip:U:wW", long_options, &optindex)) != -1)
+	while ((c = getopt_long(argc, argv, "eh:ip:U:VwW?", long_options, &optindex)) != -1)
 	{
 		switch (c)
 		{
@@ -81,6 +80,9 @@ main(int argc, char *argv[])
 			case 'U':
 				username = pg_strdup(optarg);
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 'w':
 				prompt_password = TRI_NO;
 				break;
@@ -90,6 +92,18 @@ main(int argc, char *argv[])
 			case 0:
 				/* this covers the long options */
 				break;
+			case 2:
+				help(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					help(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/scripts/pg_isready.c b/src/bin/scripts/pg_isready.c
index 7b4ea3f39a9..031c84a05c9 100644
--- a/src/bin/scripts/pg_isready.c
+++ b/src/bin/scripts/pg_isready.c
@@ -12,7 +12,6 @@
 #include "postgres_fe.h"
 #include "common.h"
 #include "common/logging.h"
-#include "fe_utils/option_utils.h"
 
 #define DEFAULT_CONNECT_TIMEOUT "3"
 
@@ -62,15 +61,16 @@ main(int argc, char **argv)
 		{"quiet", no_argument, NULL, 'q'},
 		{"timeout", required_argument, NULL, 't'},
 		{"username", required_argument, NULL, 'U'},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 1},
 		{NULL, 0, NULL, 0}
 	};
 
 	pg_logging_init(argv[0]);
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
-	handle_help_version_opts(argc, argv, progname, help);
 
-	while ((c = getopt_long(argc, argv, "d:h:p:qt:U:", long_options, NULL)) != -1)
+	while ((c = getopt_long(argc, argv, "d:h:p:qt:U:V?", long_options, NULL)) != -1)
 	{
 		switch (c)
 		{
@@ -92,6 +92,21 @@ main(int argc, char **argv)
 			case 'U':
 				pguser = pg_strdup(optarg);
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
+			case 1:
+				help(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					help(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(PQPING_NO_ATTEMPT);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/scripts/reindexdb.c b/src/bin/scripts/reindexdb.c
index 8ddcc5312f7..a2e28165def 100644
--- a/src/bin/scripts/reindexdb.c
+++ b/src/bin/scripts/reindexdb.c
@@ -86,6 +86,8 @@ main(int argc, char *argv[])
 		{"concurrently", no_argument, NULL, 1},
 		{"maintenance-db", required_argument, NULL, 2},
 		{"tablespace", required_argument, NULL, 3},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 4},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -116,10 +118,8 @@ main(int argc, char *argv[])
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
 
-	handle_help_version_opts(argc, argv, "reindexdb", help);
-
 	/* process command-line options */
-	while ((c = getopt_long(argc, argv, "ad:eh:i:j:qp:sS:t:U:vwW", long_options, &optindex)) != -1)
+	while ((c = getopt_long(argc, argv, "ad:eh:i:j:qp:sS:t:U:vVwW?", long_options, &optindex)) != -1)
 	{
 		switch (c)
 		{
@@ -164,6 +164,9 @@ main(int argc, char *argv[])
 			case 'v':
 				verbose = true;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 'w':
 				prompt_password = TRI_NO;
 				break;
@@ -179,6 +182,18 @@ main(int argc, char *argv[])
 			case 3:
 				tablespace = pg_strdup(optarg);
 				break;
+			case 4:
+				help(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					help(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index e117dac2242..ccc896e7046 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -59,6 +59,8 @@ main(int argc, char *argv[])
 		{"no-process-main", no_argument, NULL, 12},
 		{"buffer-usage-limit", required_argument, NULL, 13},
 		{"missing-stats-only", no_argument, NULL, 14},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 15},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -95,10 +97,7 @@ main(int argc, char *argv[])
 	progname = get_progname(argv[0]);
 	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
 
-	handle_help_version_opts(argc, argv, "vacuumdb", help);
-
-	while ((c = getopt_long(argc, argv, "ad:efFh:j:n:N:p:P:qt:U:vwWzZ",
-							long_options, &optindex)) != -1)
+	while ((c = getopt_long(argc, argv, "ad:efFh:j:n:N:p:P:qt:U:vVwWzZ?", long_options, &optindex)) != -1)
 	{
 		switch (c)
 		{
@@ -156,6 +155,9 @@ main(int argc, char *argv[])
 			case 'v':
 				vacopts.verbose = true;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
 			case 'w':
 				cparams.prompt_password = TRI_NO;
 				break;
@@ -213,6 +215,18 @@ main(int argc, char *argv[])
 			case 14:
 				vacopts.missing_stats_only = true;
 				break;
+			case 15:
+				help(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, argv, optind))
+				{
+					help(progname);
+					exit(0);
+				}
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
 			default:
 				/* getopt_long already emitted a complaint */
 				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
diff --git a/src/bin/scripts/vacuuming.c b/src/bin/scripts/vacuuming.c
index f836f21fb03..947fca5bfe4 100644
--- a/src/bin/scripts/vacuuming.c
+++ b/src/bin/scripts/vacuuming.c
@@ -17,7 +17,6 @@
 #include "common/connect.h"
 #include "common/logging.h"
 #include "fe_utils/cancel.h"
-#include "fe_utils/option_utils.h"
 #include "fe_utils/parallel_slot.h"
 #include "fe_utils/query_utils.h"
 #include "fe_utils/string_utils.h"
diff --git a/src/fe_utils/option_utils.c b/src/fe_utils/option_utils.c
index 6cb4ac7ffeb..12cbe1ef830 100644
--- a/src/fe_utils/option_utils.c
+++ b/src/fe_utils/option_utils.c
@@ -16,27 +16,22 @@
 #include "common/string.h"
 #include "fe_utils/option_utils.h"
 
+
 /*
- * Provide strictly harmonized handling of --help and --version
- * options.
+ * is_help_param
+ *
+ * getopt_long returns '?' for any invalid parameters as well as for the help
+ * parameter '-?' without any way to distinguish the two cases.  This helper
+ * function can be used to inspect the current parameter in argv in order to
+ * determine which case it was.
  */
-void
-handle_help_version_opts(int argc, char *argv[],
-						 const char *fixed_progname, help_handler hlp)
+bool
+is_help_param(int argc, char *argv[], int optind)
 {
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			hlp(get_progname(argv[0]));
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			printf("%s (PostgreSQL) " PG_VERSION "\n", fixed_progname);
-			exit(0);
-		}
-	}
+	if (argc > 1 && optind <= argc && strcmp(argv[optind - 1], "-?") == 0)
+		return true;
+
+	return false;
 }
 
 /*
diff --git a/src/include/fe_utils/option_utils.h b/src/include/fe_utils/option_utils.h
index 4504bbb36a8..d8fd0ba8c0d 100644
--- a/src/include/fe_utils/option_utils.h
+++ b/src/include/fe_utils/option_utils.h
@@ -14,11 +14,6 @@
 
 #include "common/file_utils.h"
 
-typedef void (*help_handler) (const char *progname);
-
-extern void handle_help_version_opts(int argc, char *argv[],
-									 const char *fixed_progname,
-									 help_handler hlp);
 extern bool option_parse_int(const char *optarg, const char *optname,
 							 int min_range, int max_range,
 							 int *result);
diff --git a/src/include/port.h b/src/include/port.h
index 3964d3b1293..152eb4991ad 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -562,4 +562,7 @@ extern int	wait_result_to_exit_code(int exit_status);
 #define HAVE_SYMLINK 1
 #endif
 
+/* port/pg_option_utils.c */
+extern bool is_help_param(int argc, char *argv[], int optind);
+
 #endif							/* PG_PORT_H */
diff --git a/src/interfaces/ecpg/preproc/ecpg.c b/src/interfaces/ecpg/preproc/ecpg.c
index 3f0f10e654a..5e0143fedb5 100644
--- a/src/interfaces/ecpg/preproc/ecpg.c
+++ b/src/interfaces/ecpg/preproc/ecpg.c
@@ -126,11 +126,14 @@ add_preprocessor_define(char *define)
 }
 
 #define ECPG_GETOPT_LONG_REGRESSION		1
+#define ECPG_GETOPT_LONG_HELP			2
 int
 main(int argc, char *const argv[])
 {
 	static struct option ecpg_options[] = {
 		{"regression", no_argument, NULL, ECPG_GETOPT_LONG_REGRESSION},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, ECPG_GETOPT_LONG_HELP},
 		{NULL, 0, NULL, 0}
 	};
 
@@ -153,22 +156,8 @@ main(int argc, char *const argv[])
 		return ILLEGAL_OPTION;
 	}
 
-	if (argc > 1)
-	{
-		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
-		{
-			help(progname);
-			exit(0);
-		}
-		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
-		{
-			printf("ecpg (PostgreSQL) %s\n", PG_VERSION);
-			exit(0);
-		}
-	}
-
 	output_filename = NULL;
-	while ((c = getopt_long(argc, argv, "cC:dD:hiI:o:r:tv", ecpg_options, NULL)) != -1)
+	while ((c = getopt_long(argc, argv, "cC:dD:hiI:o:r:tvV?", ecpg_options, NULL)) != -1)
 	{
 		switch (c)
 		{
@@ -256,8 +245,23 @@ main(int argc, char *const argv[])
 			case ECPG_GETOPT_LONG_REGRESSION:
 				regression_mode = true;
 				break;
+			case 'V':
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
+				exit(0);
+			case ECPG_GETOPT_LONG_HELP:
+				help(progname);
+				exit(0);
+				/* -? help or invalid option */
+			case '?':
+				if (is_help_param(argc, (char **) argv, optind))
+				{
+					help(progname);
+					exit(0);
+				}
+				fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
+				exit(1);
 			default:
-				fprintf(stderr, _("Try \"%s --help\" for more information.\n"), argv[0]);
+				fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
 				return ILLEGAL_OPTION;
 		}
 	}
@@ -283,7 +287,7 @@ main(int argc, char *const argv[])
 	if (optind >= argc)			/* no files specified */
 	{
 		fprintf(stderr, _("%s: no input files specified\n"), progname);
-		fprintf(stderr, _("Try \"%s --help\" for more information.\n"), argv[0]);
+		fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
 		return ILLEGAL_OPTION;
 	}
 	else
diff --git a/src/port/Makefile b/src/port/Makefile
index 4274949dfa4..a4376b954b7 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -46,6 +46,7 @@ OBJS = \
 	pg_bitutils.o \
 	pg_localeconv_r.o \
 	pg_numa.o \
+	pg_option_utils.o \
 	pg_popcount_aarch64.o \
 	pg_popcount_avx512.o \
 	pg_strong_random.o \
diff --git a/src/port/meson.build b/src/port/meson.build
index fc7b059fee5..0a81144f713 100644
--- a/src/port/meson.build
+++ b/src/port/meson.build
@@ -9,6 +9,7 @@ pgport_sources = [
   'pg_bitutils.c',
   'pg_localeconv_r.c',
   'pg_numa.c',
+  'pg_option_utils.c',
   'pg_popcount_aarch64.c',
   'pg_popcount_avx512.c',
   'pg_strong_random.c',
diff --git a/src/port/pg_option_utils.c b/src/port/pg_option_utils.c
new file mode 100644
index 00000000000..b24f3bb28e9
--- /dev/null
+++ b/src/port/pg_option_utils.c
@@ -0,0 +1,30 @@
+/*-------------------------------------------------------------------------
+ *
+ * Command line option processing facilities
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/port/pg_option_utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "c.h"
+
+/*
+ * is_help_param
+ *
+ * getopt_long returns '?' for any invalid parameters as well as for the help
+ * parameter '-?' without any way to distinguish the two cases.  This helper
+ * function can be used to inspect the current parameter in argv in order to
+ * determine which case it was.
+ */
+bool
+is_help_param(int argc, char *argv[], int optind)
+{
+	if (argc > 1 && optind <= argc && strcmp(argv[optind - 1], "-?") == 0)
+		return true;
+
+	return false;
+}
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index efc41fca2ba..fc32075bcad 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -2105,6 +2105,7 @@ regression_main(int argc, char *argv[],
 	int			i;
 	int			option_index;
 	char		buf[MAXPGPATH * 4];
+	const char *progname;
 
 	pg_logging_init(argv[0]);
 	progname = get_progname(argv[0]);
@@ -2145,7 +2146,7 @@ regression_main(int argc, char *argv[],
 				help();
 				exit(0);
 			case 'V':
-				puts("pg_regress (PostgreSQL) " PG_VERSION);
+				printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
 				exit(0);
 			case 1:
 
-- 
2.39.3 (Apple Git-146)

