Using stdin in a mutually exclusive argument group - python

I'm trying to use argparse in a script such that I could pass a string either as an argument or via standard input. I figured I could use add_mutually_exclusive_group for that, and set required=True to enforce that just one argument is required. So, I created the following script (my_script.py):
#!/usr/bin/env python
import argparse
from sys import stdin
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('str_arg', nargs='?', type=str)
group.add_argument('str_in', nargs='?', type=argparse.FileType('r'), default=stdin)
args = parser.parse_args()
print args.str_arg or args.str_in.readline()
Passing the string as a parameter works fine. However, when I try to pipe the string from standard input like so:
$ echo Hello | python my_script.py
Python complains that one of the arguments str_arg str_in is required. What am I doing wrong? Is there a better way of achieving this?

It doesn't seem like you should try doing this with fancy argparse features, just some simple logic:
import argparse
from sys import stdin
parser = argparse.ArgumentParser()
parser.add_argument('str_arg', nargs='?', type=str)
args = parser.parse_args()
print(args.str_arg or stdin.readline())

2 positionals in a mutually exclusive group does not work.
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('str_arg', nargs='?')
group.add_argument('str_in', nargs='?')
args = parser.parse_args()
print(args)
sample runs:
0217:~/mypy$ python3 stack49705916.py
usage: stack49705916.py [-h] (str_arg | str_in)
stack49705916.py: error: one of the arguments str_arg str_in is required
0904:~/mypy$ python3 stack49705916.py foo
Namespace(str_arg='foo', str_in=None)
0904:~/mypy$ python3 stack49705916.py foo bar
usage: stack49705916.py [-h] (str_arg | str_in)
stack49705916.py: error: argument str_in: not allowed with argument str_arg
piping is not a substitute for a commandline argument. stdin has to be read separately.
import argparse, sys
print(sys.argv)
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('str_arg', nargs='?')
group.add_argument('str_in', nargs='?')
args = parser.parse_args()
print(args)
print(sys.stdin.read())
0909:~/mypy$ echo 'hello' | python3 stack49705916.py foo
['stack49705916.py', 'foo']
Namespace(str_arg='foo', str_in=None)
hello
argparse.FileType('r') recognizes - as stdin.
print(sys.argv)
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--str_in', type=argparse.FileType('r'))
args = parser.parse_args()
print(args)
print(args.str_in.read())
runs
0945:~/mypy$ python3 stack49705916.py -i test.txt
['stack49705916.py', '-i', 'test.txt']
Namespace(str_in=<_io.TextIOWrapper name='test.txt' mode='r' encoding='UTF-8'>)
2000+0
2001+2
2002+1
0946:~/mypy$ python3 stack49705916.py -i -
['stack49705916.py', '-i', '-']
Namespace(str_in=<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>)
typing hello on input line
typing hello on input line
0947:~/mypy$ echo Hello | python3 stack49705916.py -i -
['stack49705916.py', '-i', '-']
Namespace(str_in=<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>)
Hello
0947:~/mypy$

In a shell you can pass the output of echo Whatever as an argument using back quotes and not a pipe:
$ python my_script.py `echo whatever`

Related

argparse: nargs with multiple flags in one dash?

If I try running python test.py -bf with the below code, I get bar=f and foo=foo1 instead of the desired result (bar=bar1 and foo=foo1). How would I achieve the desired result?
import argparse
ap = argparse.ArgumentParser(description='test')
ap.add_argument('--bar', '-b', nargs='?', const='bar1')
ap.add_argument('--foo', '-f', nargs='?', const='foo1')
args = ap.parse_args()

Using argparse with a specified acceptable option

I am trying to use argparse to accept required command line options. I have defined a function like so
def get_args():
parser = argparse.ArgumentParser(description='Help Desk Calendar Tool')
parser.add_argument('-s', '--start', type=str, required=True, metavar='YYYY-MM-DD')
parser.add_argument('-e','--end', type=str, required=True, metavar='YYYY-MM-DD')
parser.add_argument('-m','--mode', type=str, required=True , metavar='add|del')
args = parser.parse_args()
start = args.start
end = args.end
mode = args.mode
return start,end,mode
What I am trying to do is for the option --mode I would like it to ONLY accept an parameter of either add or del. I could do this from an if statement but was wondering if argparse has a built in way of accomplishing this task. I looked at nargs but wasn't too clear if that's the path I need to go down
I think you are asking about choices:
parser.add_argument('-m','--mode', type=str, required=True, choices=['add', 'del'])
Demo:
$ python test.py -s 10 -e 20 -m invalid
usage: test.py [-h] -m {add,del}
test.py: error: argument -m/--mode: invalid choice: 'invalid' (choose from 'add', 'del')

Python 2.7 optparse not reading 2nd flag

Can someone help me out here? I'm not sure what I'm doing wrong but I can't seem to get my second option to be read from the command line.
from optparse import OptionParser
parser=OptionParser()
parser.add_option("-s", action="store", type="string", dest="scenario")
parser.add_option("-l", action="store", type="string", dest="logger")
(options, args)=parser.parse_args()
print options.scenario
print options.logger
print options
Print Results
>>python test.py -sfoo -lbar
foo
None
{'logger': None, 'scenario': 'foo'}
Additionally, I cannot put a space in between the flag and argument -sfoo is ok but -s foo is not. It's pretty annoying. Can anyone see what I am doing wrong? Thanks in advance.
As #user3757614 suggests in his comment, use argparse instead.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-s', '--scenario', required=True, dest="scenario")
parser.add_argument('-l', '--logger', required=True, dest="logger")
args = parser.parse_args()
print args
print args.scenario
print args.logger
And in the command line:
$ python test.py -s test1 -l test2
Namespace(logger='test2', scenario='test1')
test1
test2

Flexible UNIX command line interface with Python

I was wondering how to create a flexible CLI interface with Python. So far I have come up with the following:
$ cat cat.py
#!/usr/bin/env python
from sys import stdin
from fileinput import input
from argparse import ArgumentParser, FileType
def main(args):
for line in input():
print line.strip()
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument('FILE', nargs='?', type=FileType('r'), default=stdin)
main(parser.parse_args())
This handles both stdin and file input:
$ echo 'stdin test' | ./cat.py
stdin test
$ ./cat.py file
file test
The problem is it doesn't handle multiple input or no input the way I would like:
$ ./cat.py file file
usage: cat.py [-h] [FILE]
cat.py: error: unrecognized arguments: file
$ ./cat.py
For multiple inputs it should cat the file multiple times and for no input input should ideally have same the behaviour as -h:
$ ./cat.py -h
usage: cat.py [-h] [FILE]
positional arguments:
FILE
optional arguments:
-h, --help show this help message and exit
Any ideas on creating a flexible CLI interface with Python?
Use nargs='*' to allow for 0 or more arguments:
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument('FILE', nargs='*', type=FileType('r'), default=stdin)
main(parser.parse_args())
The help output now is:
$ bin/python cat.py -h
usage: cat.py [-h] [FILE [FILE ...]]
positional arguments:
FILE
optional arguments:
-h, --help show this help message and exit
and when no arguments are given, stdout is used.
If you want to require at least one FILE argument, use nargs='+' instead, but then the default is ignored, so you may as well drop that:
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument('FILE', nargs='+', type=FileType('r'))
main(parser.parse_args())
Now not specifying a command-line argument gives:
$ bin/python cat.py
usage: cat.py [-h] FILE [FILE ...]
cat.py: error: too few arguments
You can always specify stdin still by passing in - as an argument:
$ echo 'hello world!' | bin/python cat.py -
hello world!
A pretty good CLI interface the handles file input, standard input, no input, file output and inplace editing:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
def main(args, help):
'''
Simple line numbering program to demonstrate CLI interface
'''
if not (select.select([sys.stdin,],[],[],0.0)[0] or args.files):
help()
return
if args.output and args.output != '-':
sys.stdout = open(args.output, 'w')
try:
for i, line in enumerate(fileinput.input(args.files, inplace=args.inplace)):
print i + 1, line.strip()
except IOError:
sys.stderr.write("%s: No such file %s\n" %
(os.path.basename(__file__), fileinput.filename()))
if __name__ == "__main__":
import os, sys, select, argparse, fileinput
parser = argparse.ArgumentParser()
parser.add_argument('files', nargs='*', help='input files')
group = parser.add_mutually_exclusive_group()
group.add_argument('-i', '--inplace', action='store_true',
help='modify files inplace')
group.add_argument('-o', '--output',
help='output file. The default is stdout')
main(parser.parse_args(), parser.print_help)
The code simply emulates nl and numbers the lines but should serve as a good skeleton for many applications.

How to let argparse check mutually exclusive groups of arguments

I have a test code as follows, that shall take EITHER the positional argument file OR all the optional arguments time, expression and name:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-t","--time")
parser.add_argument("-x","--expression")
parser.add_argument("-n","--name")
parser.add_argument("file")
print parser.parse_args()
The following combination should work
test.py filename
test.py -t 5 -x foo -n test
but NOT these:
test.py filename -t 5 # should raise error because the positional and the optional -t argument cannot be used together
test.py -t 5 -x foo # should raise an error because all three of the optional arguments are required
Any simple solution to that problem?
The first issue is that you have specified that file is positional which will make it required. You will probably need to convert it to a optional argument.
Here is a simple way to check that the correct arguments have been provided:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-t","--time")
parser.add_argument("-x","--expression")
parser.add_argument("-n","--name")
parser.add_argument("-f", "--file")
args = parser.parse_args()
has_file = args.file is not None
has_txn = None not in frozenset([args.time, args.expression, args.name])
assert (has_file ^ has_txn), "File or time, expression and name must be provided"
You might find the following approach useful.
import argparse, sys
print(sys.argv)
if len(sys.argv) == 2:
sys.argv += ['-t', 'time', '-x', 'expression', '-n', 'name']
else:
sys.argv.append('FILE')
parser = argparse.ArgumentParser()
group = parser.add_argument_group('Required Group', 'All 3 are required else provide "file" argument.')
group.add_argument("-t","--time", required=True)
group.add_argument("-x","--expression", required=True)
group.add_argument("-n","--name", required=True)
parser.add_argument("file", help='file name')
print(parser.parse_args())
Here is some example output..
$ ./t.py -t 2 -x "a + b" -n George
['./t.py', '-t', '2', '-x', 'a + b', '-n', 'George']
Namespace(expression='a + b', file='FILE', name='George', time='2')
$ ./t.py FILE
['./t.py', 'FILE']
Namespace(expression='expression', file='FILE', name='name', time='time')
$ ./t.py -h
usage: t.py [-h] -t TIME -x EXPRESSION -n NAME file
positional arguments:
file file name
optional arguments:
-h, --help show this help message and exit
Required Group:
All 3 are required else provide "file" argument.
-t TIME, --time TIME
-x EXPRESSION, --expression EXPRESSION
-n NAME, --name NAME

Categories