argparse command line -option after given path

2019-08-06 06:04发布

I'm new to python and currently experimenting using argparse to add command line options. However my code is not working, despite looking at various online tutorials and reading up on argparse I still don't fully understand it. My problem is whenever I try to call my -option it gives me a find.py error: argument regex:

Here is my call:

./find.py ../Python -name '[0-9]*\.txt'

../Python is one directory behind my current one and has a list of files/directories. Without the -name option I print out the files with their path (this works fine) but with the -name option I want to print out files matching the regex but it won't work. Here is what I currently have:

#!/usr/bin/python2.7

import os, sys, argparse,re 
from stat import *

def regex_type(s, pattern=re.compile(r"[a-f0-9A-F]")):
   if not pattern.match(s):
      raise argparse.ArgumentTypeError
   return s

def main():
   direc = sys.argv[1]

   for f in os.listdir(direc):
      pathname = os.path.join(direc, f)
      mode = os.stat(pathname).st_mode
      if S_ISREG(mode):
          print pathname

   parser = argparse.ArgumentParser()
   parser.add_argument(
    '-name', default=[sys.stdin], nargs="*")
   parser.add_argument('regex', type=regex_type) 
   args = parser.parse_args()



if __name__ == '__main__': 

      main()

1条回答
男人必须洒脱
2楼-- · 2019-08-06 06:42

I tweaked your type function to be more informative:

def regex_type(s, pattern=re.compile(r"[a-f0-9A-F]")):
   print('regex string', s)
   if not pattern.match(s):
      raise argparse.ArgumentTypeError('pattern not match')
   return s

Called with

2104:~/mypy$ python2 stack50072557.py .

I get:

<director list>
('regex string', '.')
usage: stack50072557.py [-h] [-name [NAME [NAME ...]]] regex
stack50072557.py: error: argument regex: pattern not match

So it tries to pass sys.argv[1], the first string after the script name, to the regex_type function. If it fails it issues the error message and usage.

OK, the problem was the ..; I'll make a directory:

2108:~/mypy$ mkdir foo
2136:~/mypy$ python2 stack50072557.py foo
('regex string', 'foo')
Namespace(name=[<open file '<stdin>', mode 'r' at 0x7f3bea2370c0>], regex='foo')

2138:~/mypy$ python2 stack50072557.py foo -name a b c
('regex string', 'foo')
Namespace(name=['a', 'b', 'c'], regex='foo')

The strings following '-name' are allocated to that attribute. There's nothing in your code that will test them or pass them through the regex_type function. Only the first non-flag string does that.

Reading sys.argv[1] initially does not remove it from the list. It's still there for use by the parser.

I would set up a parser that uses a store_true --name argument, and 2 positionals - one for the dir and the other for regex.

After parsing check args.name. If false print the contents of args.dir. If true, perform your args.regex filter on those contents. glob might be useful.

The parser finds out what your user wants. Your own code acts on it. Especially as a beginner, it is easier and cleaner to separate the two steps.


With:

def parse(argv=None):
    parser = argparse.ArgumentParser()
    parser.add_argument('-n', '--name', action='store_true')
    parser.add_argument('--dir', default='.')
    parser.add_argument('--regex', default=r"[a-f0-9A-F]")
    args = parser.parse_args(argv)
    print(args)
    return args

def main(argv=None):
    args = parse(argv)
    dirls = os.listdir(args.dir)
    if args.name:
        dirls = [f for f in dirls if re.match(args.regex, f)]
        print(dirls)
    else:
        print(dirls)

I get runs like:

1005:~/mypy$ python stack50072557.py 
Namespace(dir='.', name=False, regex='[a-f0-9A-F]')
['test.npz', 'stack49909128.txt', 'stack49969840.txt', 'stack49824248.py', 'test.h5', 'stack50072557.py', 'stack49963862.npy', 'Mcoo.npz', 'test_attribute.h5', 'stack49969861.py', 'stack49969605.py', 'stack49454474.py', 'Mcsr.npz', 'Mdense.npy', 'stack49859957.txt', 'stack49408644.py', 'Mdok', 'test.mat5', 'stack50012754.py', 'foo', 'test']
1007:~/mypy$ python stack50072557.py -n
Namespace(dir='.', name=True, regex='[a-f0-9A-F]')
['foo']
1007:~/mypy$ python stack50072557.py -n --regex='.*\.txt'
Namespace(dir='.', name=True, regex='.*\\.txt')
['stack49909128.txt', 'stack49969840.txt', 'stack49859957.txt']

and help:

1007:~/mypy$ python stack50072557.py -h
usage: stack50072557.py [-h] [-n] [--dir DIR] [--regex REGEX]

optional arguments:
  -h, --help     show this help message and exit
  -n, --name
  --dir DIR
  --regex REGEX

If I change the dir line to:

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

help is now

1553:~/mypy$ python stack50072557.py -h
usage: stack50072557.py [-h] [-n] [--regex REGEX] dir

positional arguments:
  dir

optional arguments:
  -h, --help     show this help message and exit
  -n, --name
  --regex REGEX

and runs are:

1704:~/mypy$ python stack50072557.py -n
usage: stack50072557.py [-h] [-n] [--regex REGEX] dir
stack50072557.py: error: too few arguments

1705:~/mypy$ python stack50072557.py . -n
Namespace(dir='.', name=True, regex='[a-f0-9A-F]')
['foo']

1705:~/mypy$ python stack50072557.py ../mypy -n --regex='.*\.txt'
Namespace(dir='../mypy', name=True, regex='.*\\.txt')
['stack49909128.txt', 'stack49969840.txt', 'stack49859957.txt']

I get the error because it now requires a directory, even it is '.'.

Note that the script still uses:

if __name__ == '__main__': 
    main()

My main loads the dir, and applies the regex filter to that list of names. My args.dir replaces your direc.

查看更多
登录 后发表回答