简体   繁体   中英

Python argparse mutually exclusive with stdin being one of the options

I would like my script to receive these mutually exclusive input options:

  • an input file containing a JSON ( script.py -i input.json );
  • a string containing a JSON ( script.py '{"a":1}' );
  • a JSON from stdin ( echo '{"a":1}' | script.py or cat input.json | script.py ).

and these mutually exclusive output options :

  • an output file containing a JSON;
  • a JSON in stdout.

So I tried with this code

import json,sys,argparse
parser = argparse.ArgumentParser(description='Template for python script managing JSON as input/output format')

group = parser.add_mutually_exclusive_group()
group.add_argument('--input-file', '-i',  type=str, help='Input file name containing a valid JSON.', default=sys.stdin)
group.add_argument('json',    nargs='?',  type=str, help='Input string containing a valid JSON.' , default=sys.stdin)
parser.add_argument('--output-file', '-o',type=str, help='Output file name.')

args = parser.parse_args()

if not sys.stdin.isatty():
    data = sys.stdin.read()
else:
#    args = parser.parse_args()
    if args.input_file :
        data=open(args.input_file).read()
    elif args.json :
        data=args.json


datain=json.loads(data)

dataout=json.dumps(datain, indent=2)

if args.output_file :
        output_file=open(args.output_file, 'w')
        output_file.write(dataout+'\n')
        output_file.close()
else:
    print (dataout)

But it does not work with stdin as it requires at least one of the two group options.

How can I add stdin in the list of input options?

Adding the default=sys.stdin argument works if I call it like that

echo '{}' | ./script.py -

but not like that:

echo '{}' | ./script.py

I would take advantage of argparse.FileType with a default value of sys.stdin .

import json,sys,argparse
parser = argparse.ArgumentParser(description='Template for python script managing JSON as input/output format')

group = parser.add_mutually_exclusive_group()
group.add_argument(
    '--input-file', '-i',
    type=argparse.FileType('r'),
    default=sys.stdin,
    help='Input file name containing a valid JSON.')
group.add_argument(
    'json',
    nargs='?',
    type=str,
    help='Input string containing a valid JSON.')
parser.add_argument(
    '--output-file', '-o',
    type=argparse.FileType('w'),
    help='Output file name.',
    default=sys.stdout)

args = parser.parse_args()
data = args.json or args.input_file.read()

datain=json.loads(data)
dataout=json.dumps(datain, indent=2)
args.output_file.write(dataout)

With:

group.add_argument('--input-file', '-i')

You could test

if args.input_file is None:
   <-i wasn't supplied>
else:
    if args.input_file == '-':
        f = sys.stdin
    else:
        f = open(args.input_file)
    data = f.read()  # etc

Or may be better:

    if args.input_file == '-':
        data = sys.stdin.read()
    else
        with open(args.input_file) as f:
            f.read()

A tricky thing with stdin is that you don't want to close it after use like you would with a regular file name. And you can't use it in a with .

Similarly with stdout .

Some code sets a flag when it opens a file, as opposed to receiving an already open one, so it can remember to close the file at the end.

group.add_argument('--input-file','-i',nargs='?', default=None, const=sys.stdin)

would set arg.input_file to stdin when given -i without an argument. But I think looking for a plain - string is a better idea.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM