Skip to content

Commit bce4dda

Browse files
bpo-40527: Fix command line argument parsing (GH-19955)
(cherry picked from commit 2668a9a) Co-authored-by: Victor Stinner <[email protected]>
1 parent a32587a commit bce4dda

File tree

3 files changed

+29
-7
lines changed

3 files changed

+29
-7
lines changed

Lib/test/test_cmd_line.py

+11
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,17 @@ def test_argv0_normalization(self):
740740
self.assertEqual(proc.returncode, 0, proc)
741741
self.assertEqual(proc.stdout.strip(), b'0')
742742

743+
def test_parsing_error(self):
744+
args = [sys.executable, '-I', '--unknown-option']
745+
proc = subprocess.run(args,
746+
stdout=subprocess.PIPE,
747+
stderr=subprocess.PIPE,
748+
text=True)
749+
err_msg = "unknown option --unknown-option\nusage: "
750+
self.assertTrue(proc.stderr.startswith(err_msg), proc.stderr)
751+
self.assertNotEqual(proc.returncode, 0)
752+
753+
743754
@unittest.skipIf(interpreter_requires_environment(),
744755
'Cannot run -I tests when PYTHON env vars are required.')
745756
class IgnoreEnvironmentTest(unittest.TestCase):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix command line argument parsing: no longer write errors multiple times
2+
into stderr.

Python/getopt.c

+16-7
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,9 @@ int _PyOS_GetOpt(Py_ssize_t argc, wchar_t * const *argv, int *longindex)
105105
if (option == L'-') {
106106
// Parse long option.
107107
if (*opt_ptr == L'\0') {
108-
fprintf(stderr, "expected long option\n");
108+
if (_PyOS_opterr) {
109+
fprintf(stderr, "expected long option\n");
110+
}
109111
return -1;
110112
}
111113
*longindex = 0;
@@ -115,31 +117,37 @@ int _PyOS_GetOpt(Py_ssize_t argc, wchar_t * const *argv, int *longindex)
115117
break;
116118
}
117119
if (!opt->name) {
118-
fprintf(stderr, "unknown option %ls\n", argv[_PyOS_optind - 1]);
120+
if (_PyOS_opterr) {
121+
fprintf(stderr, "unknown option %ls\n", argv[_PyOS_optind - 1]);
122+
}
119123
return '_';
120124
}
121125
opt_ptr = L"";
122126
if (!opt->has_arg) {
123127
return opt->val;
124128
}
125129
if (_PyOS_optind >= argc) {
126-
fprintf(stderr, "Argument expected for the %ls options\n",
127-
argv[_PyOS_optind - 1]);
130+
if (_PyOS_opterr) {
131+
fprintf(stderr, "Argument expected for the %ls options\n",
132+
argv[_PyOS_optind - 1]);
133+
}
128134
return '_';
129135
}
130136
_PyOS_optarg = argv[_PyOS_optind++];
131137
return opt->val;
132138
}
133139

134140
if (option == 'J') {
135-
if (_PyOS_opterr)
141+
if (_PyOS_opterr) {
136142
fprintf(stderr, "-J is reserved for Jython\n");
143+
}
137144
return '_';
138145
}
139146

140147
if ((ptr = wcschr(SHORT_OPTS, option)) == NULL) {
141-
if (_PyOS_opterr)
148+
if (_PyOS_opterr) {
142149
fprintf(stderr, "Unknown option: -%c\n", (char)option);
150+
}
143151
return '_';
144152
}
145153

@@ -151,9 +159,10 @@ int _PyOS_GetOpt(Py_ssize_t argc, wchar_t * const *argv, int *longindex)
151159

152160
else {
153161
if (_PyOS_optind >= argc) {
154-
if (_PyOS_opterr)
162+
if (_PyOS_opterr) {
155163
fprintf(stderr,
156164
"Argument expected for the -%c option\n", (char)option);
165+
}
157166
return '_';
158167
}
159168

0 commit comments

Comments
 (0)