883

I have a script which is meant to be used like this: usage: installer.py dir [-h] [-v]

dir is a positional argument which is defined like this:

parser.add_argument('dir', default=os.getcwd())

I want the dir to be optional: when it's not specified it should just be cwd.

Unfortunately when I don't specify the dir argument, I get Error: Too few arguments.

wOxxOm
  • 65,848
  • 11
  • 132
  • 136
Waldo Bronchart
  • 10,152
  • 4
  • 23
  • 29

5 Answers5

1136

Use nargs='?' (or nargs='*' if you need more than one dir)

parser.add_argument('dir', nargs='?', default=os.getcwd())

extended example:

>>> import os, argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-v', action='store_true')
_StoreTrueAction(option_strings=['-v'], dest='v', nargs=0, const=True, default=False, type=None, choices=None, help=None, metavar=None)
>>> parser.add_argument('dir', nargs='?', default=os.getcwd())
_StoreAction(option_strings=[], dest='dir', nargs='?', const=None, default='/home/vinay', type=None, choices=None, help=None, metavar=None)
>>> parser.parse_args('somedir -v'.split())
Namespace(dir='somedir', v=True)
>>> parser.parse_args('-v'.split())
Namespace(dir='/home/vinay', v=True)
>>> parser.parse_args(''.split())
Namespace(dir='/home/vinay', v=False)
>>> parser.parse_args(['somedir'])
Namespace(dir='somedir', v=False)
>>> parser.parse_args('somedir -h -v'.split())
usage: [-h] [-v] [dir]

positional arguments:
  dir

optional arguments:
  -h, --help  show this help message and exit
  -v
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
Vinay Sajip
  • 95,872
  • 14
  • 179
  • 191
  • 33
    Do the `?` and `*` mean the same thing they mean in regular expressions (i.e. `?` requires 0 or 1, and `*` requiring 0 or more)? If so, does `+` work as well? – Dolan Antenucci Jan 08 '13 at 16:11
  • 54
    @dolan: Yes, `+` works, too. See http://docs.python.org/2/library/argparse.html#nargs for the details. – Vinay Sajip Jan 08 '13 at 23:53
  • 3
    is there some way to get dir to show up in optional arguments? or it seems that positional arguments should have a preceeding 'optional' qualifier. is it possible to register (as far as help is concerned) it as such? – scagnetti Sep 15 '14 at 21:54
  • 6
    @ant From the above, you can see that dir is optional (that it appears in square brackets in argparse output indicates this). – Vinay Sajip Sep 15 '14 at 22:00
  • 1
    Tx! Access dir from `options.dir`, not `args.dir`, as I was trying! – ptim Feb 02 '18 at 03:31
  • 5
    Here's the updated (Python 3) documentation--a careful reading of it explains it all: https://docs.python.org/3/library/argparse.html#nargs. For anyone new to the `argparse` module, start with the tutorial: https://docs.python.org/3/howto/argparse.html – Gabriel Staples May 16 '19 at 01:55
  • 1
    @dolan and Vinay, '+' does not work because it requires at least one argument at the command line. – Joonho Park Feb 04 '20 at 06:23
98

As an extension to @VinaySajip answer. There are additional nargs worth mentioning.

  1. parser.add_argument('dir', nargs=1, default=os.getcwd())

N (an integer). N arguments from the command line will be gathered together into a list

  1. parser.add_argument('dir', nargs='*', default=os.getcwd())

'*'. All command-line arguments present are gathered into a list. Note that it generally doesn't make much sense to have more than one positional argument with nargs='*', but multiple optional arguments with nargs='*' is possible.

  1. parser.add_argument('dir', nargs='+', default=os.getcwd())

'+'. Just like '*', all command-line args present are gathered into a list. Additionally, an error message will be generated if there wasn’t at least one command-line argument present.

  1. parser.add_argument('dir', nargs=argparse.REMAINDER, default=os.getcwd())

argparse.REMAINDER. All the remaining command-line arguments are gathered into a list. This is commonly useful for command line utilities that dispatch to other command line utilities

If the nargs keyword argument is not provided, the number of arguments consumed is determined by the action. Generally this means a single command-line argument will be consumed and a single item (not a list) will be produced.

Edit (copied from a comment by @Acumenus) nargs='?' The docs say: '?'. One argument will be consumed from the command line if possible and produced as a single item. If no command-line argument is present, the value from default will be produced.

Matas Vaitkevicius
  • 58,075
  • 31
  • 238
  • 265
  • 3
    It should be noted however that `nargs='?'` does not produce a list. – Asclepius Oct 05 '16 at 05:59
  • @A-B-B Last line of the answer `Generally this means a single command-line argument will be consumed and a single item (not a list) will be produced.` Hope this helps... – Matas Vaitkevicius Oct 05 '16 at 06:55
  • 1
    The quoted line refers to the case of not defining `nargs`, but `nargs='?'` is defining it. The [docs](https://docs.python.org/3/library/argparse.html#nargs) say: _'?'. One argument will be consumed from the command line if possible, and produced as a single item. If no command-line argument is present, the value from default will be produced._ – Asclepius Oct 05 '16 at 07:01
  • @A-B-B Just edit the answer if you feel that something is missing. Thanks. – Matas Vaitkevicius Oct 05 '16 at 07:03
  • 1
    What is the difference between `nargs=argparse.REMAINDER` and `nargs='*'`, as it seems to me, they are identical in their effect (tested in Python 2.7.10 and Python 3.6.1)? –  Aug 08 '18 at 11:49
  • note also that `default` is *required* to make this work. without specifying a default value, executing the parsing command without a value in that position will complain that a value is required for that argument. – violet Dec 16 '21 at 19:21
17

Short Answer

As already shown in the previous two answers, you can accept an optional positional argument with nargs='?'. You could also turn the argument directly into a Path type and/or shorten the cwd to . if you wanted to:

myfile.py

import argparse
import pathlib

parser = argparse.ArgumentParser()
parser.add_argument("dir", nargs="?", default=".", type=pathlib.Path)
parsed_args = parser.parse_args()

print("Installing to", parsed_args.dir.resolve())
$ python myfile.py
Installing to /users/myname/myfolder

$ python myfile.py /usr/bin/
Installing to /usr/bin

Longer answer

Since you also mention the flag-style True/False options -h and -v in your question, these examples may be of use:

Flags (e.g. -v)

We might refer to optional options that take no arguments as "flags". With flags, we only care about whether they are given or not. -h is a flag that argparse adds automatically (along with the longer version --help) so we shouldn't really override that. If we consider -v then,

myfile.py

import argparse

parser = argparse.ArgumentParser()
parser.add_argument(
        "-v",
        "--version",
        action="store_true")
parsed_args = parser.parse_args()

if parsed_args.version:
    print("version flag given")
else:
    print("version flag not given")

Note that the second argument to add_argument() is a longer name for the option. It is not mandatory but it does make your subsequent code more readable (parsed_args.version vs parsed_args.v) and makes calls to your installer more explicit.

$ python myfile.py -v
version flag given

$ python myfile.py --version
version flag given

$ python myfile.py
version flag not given

Optional arguments (e.g. --installdir /usr/bin/)

One could argue that, in your case, you would be better off with an optional argument rather than a positional one.

myfile.py

import argparse
import pathlib

parser = argparse.ArgumentParser()
parser.add_argument(
        "-i",
        "--installdir",  # Optional (but recommended) long version
        type=pathlib.Path,
        default="/bin"
        )
parsed_args = parser.parse_args()

print("Installing to", parsed_args.installdir)
$ python myfile.py -i /usr/bin/
Installing to /usr/bin

$ python myfile.py --installdir /usr/bin/
Installing to /usr/bin

$ python myfile.py
Installing to /bin
FiddleStix
  • 3,016
  • 20
  • 21
0
import argparse
import os

parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('-i', '--input', type=str, required=True, help='Input directory')
parser.add_argument('-o', '--output', type=str, required=True, help='Output directory')
parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output')

args = vars(parser.parse_args())

input_dir = args['input']
output_dir = args['output']
verbose = args['verbose']

if verbose:
    print('Input directory:', input_dir)
    print('Output directory:', output_dir)

# Create the output directory and any necessary parent directories
os.makedirs(output_dir, exist_ok=True)

# Use input_dir and output_dir as needed

I use the vars() function to convert the Namespace object returned by parse_args() to a dictionary. This allows you to access the command-line arguments as dictionary keys instead of separate variables.

I also use os.makedirs() instead of os.mkdir() to create the output directory, and I pass the exist_ok=True argument to avoid raising an error if the directory already exists. This can make your code more efficient and robust.

SyntaxNavigator
  • 312
  • 3
  • 10
  • OP was asking about positional arguments being optional, not how to access the arguments, and they don't mention anything about creating the directory, so I'm not sure this addresses what they were asking. – bjg222 Aug 23 '23 at 22:52
-12

parser.add_argument also has a switch required. You can use required=False. Here is a sample snippet with Python 2.7:

parser = argparse.ArgumentParser(description='get dir')
parser.add_argument('--dir', type=str, help='dir', default=os.getcwd(), required=False)
args = parser.parse_args()
elena
  • 3,740
  • 5
  • 27
  • 38
rakhee
  • 109
  • 4
  • 18
    OP was asking about positional params, not '--dir'. 'required' is an invalid argument for positionals. And 'false' was a typo, she meant 'False'. +1 for newbie, -1 for sloppiness. – SoloPilot Dec 04 '16 at 20:37
  • 1
    We can not use `required` for positional argument. – Karthik Sunil Aug 19 '20 at 09:27