2

I am writing a script that will be used to merge several input files, generating a single output file, and I want to build a small command line interface using argparse. To me, the most natural-seeming way to do it would be to have a single optional input, -i, which takes one or more input file names, followed by a positional argument, representing the output file name. Here's an example of what I had in mind:

#!/usr/bin/env python3
"""
Script for merging input files
"""
import argparse

script_docstring = 'Read some input files and create a merged output.'
parser = argparse.ArgumentParser(description=script_docstring)
# Optional input file names
parser.add_argument('-i --inputs', nargs='+', type=str, required=True,
                    help='Input file names', dest='inputs')
# Positional output file name
parser.add_argument('fname_out', type=str, help='Output file name')
args = parser.parse_args()

# Display what the user chose at the command line
print(args.inputs)
print(args.fname_out)

When I print the auto-generated help message created by argparse, the call signature looks like what I had intended:

> ./mergefiles.py --help
usage: mergefiles.py [-h] -i --inputs INPUTS [INPUTS ...] fname_out

Read some input files and create a merged output.

positional arguments:
  fname_out             Output file name

optional arguments:
  -h, --help            show this help message and exit
  -i --inputs INPUTS [INPUTS ...]
                        Input file names

However, when I actually attempt to run the script, it gives an error which suggests to me that argparse is mistakenly parsing the final positional argument as though it were to be included amongst the list of optionals:

> ./mergefiles.py -i a.in b.in c.in test.out
usage: mergefiles.py [-h] -i --inputs INPUTS [INPUTS ...] fname_out
mergefiles.py: error: the following arguments are required: fname_out

My question: Is it even possible to get argparse to handle a case such as this correctly, treating the final argument as a positional? Or is my only option to accept a "workaround" solution such as turning the output file name into an optional argument as well, e.g., like -f --fname_out or similar.

If it's not possible to do what I want, then I plan to implement exactly that as my fallback solution. However, before I accept an inelegant workaround, I'm curious whether it's actually possible to get argparse to handle this the "correct" way.

stachyra
  • 4,053
  • 3
  • 18
  • 29
  • 2
    No you can't trick `argparse` into handling this. Because of the `+`, `inputs` gets all the remaining strings, leaving none for `fname_out`. Ideally it should do some sort of 'look-ahead' and reserve one for the positional, but the current code doesn't do that. Putting the positional string first should work: 'test.out -i a b c' – hpaulj Nov 18 '19 at 18:02
  • Yes, some type of lookahead capability (I suppose this would be similar to what exists in some [regular expression engines](https://www.regular-expressions.info/lookaround.html)) is sort of what I had in mind. Thanks for confirming that it's not implemented, and that there's therefore no secret technique for triggering it. – stachyra Nov 18 '19 at 18:36

1 Answers1

4

When using a positional as the last argument you must use -- to separate the arguments:

python3 args.py -i infile1 infile2 -- outfile
['infile1', 'infile2']
outfile

I hope this helps

oppressionslayer
  • 6,330
  • 2
  • 4
  • 21
  • Thanks--this does indeed work as advertised, and the double dash input technique for separating arguments ("--") is something that I wasn't aware of, so yes, it was helpful. However, I would also categorize this solution as sort of a workaround too, just one of a different type. – stachyra Nov 18 '19 at 18:33
  • @stachyra : The double dash `--` is not a workaround, it's a feature :-) `man bash` says that it signifies the end of the options, and after it only positional arguments are allowed. See e.g. https://unix.stackexchange.com/a/187548/266189 . – Laryx Decidua Jul 12 '20 at 09:39