Can I set stdin as required with Python argparse? - python

I have a script that uses some arguments and some stdin data.
For checking arguments I use argparse.ArgumentParser
Is it possible to check if any stdin data is given? Something like that:
parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin, required=True)
but this example gives this error:
TypeError: 'required' is an invalid argument for positionals

No. It wont't read from whatever file you pass it, be it given on the command line, or stdin. You will get an open file handle, with not even a single byte/char consumed.
Simply read the data yourself, for instance with data = args.infile.read() (assuming args is the result of parsing`).
You can then test if it is empty, with a simple if not data:...
But usually, if you expect data in a specific format, the best is to simply try to parse it, and raise an error if you fail. Either empty data is invalid (json for instance), or it is valid but then it should be an acceptable input.
(as for the error, required only tells whether some option must be given on the command line or not, for --options and -o options. Positionals are always required unless you change their numbers with nargs).

The error is just because of the required=True parameter; and the message tells you what is wrong. It should be:
parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin)
By 'calling' this infile, as opposed to '--infile', you've created a positional argument. argparse itself determines whether it is required or not. With nargs='?' it can't be required. It's by definition optional (but not an optionals argument :) ).
The FileType type lets you name a file (or '-') in the commandline. It will open it (stdin is already open) and assign it to the args.infile attribute. It does nothing more.
So after parsing, using args.infile gives you access to this open file, which you can read as needed (and optionally close if not stdin).
So this is a convenient way of letting your users specify which file should be opened for use in your code. It was intended for simple scripts that read one file, do something, and write to another.
But if all you are looking at is stdin, there isn't any point in using this type. sys.stdin is always available for reading. And there isn't any way of making the parser read stdin. It parses sys.argv which comes from the commandline.
There is an # prefix file feature that tells the parser to read commandline strings from a file. It parses the file and splices the values into sys.argv. See the argparse docs.

From the docs The add_argument() method
required - Whether or not the command-line option may be omitted (optionals only).
The required keyword is only used for options (e.g., -f or --foo) not for positional arguments. Just take it out.
parser.add_argument('infile', nargs='?', type=argparse.FileType('r'),
default=sys.stdin)
When parsed infile will either be a string or the sys.stdin file object. You would need to read that file to see if there is anything in it. Reading can be risky... you may block forever. But that just means that the user didn't follow instructions.

Related

How could I default to nothing with argparse pointing to a pathname?

Currently, I'm using argparse to output the following file:
import argparse
....
parser = argparse.ArgumentParser(description='do cool stuff')
parser.add_argument('--output_text_file',
default="outputs/output_file1.txt",
help='file path for the output text file')
This is already an optional argument. However, I would like the default option to be do not output anything. If the user would like a file output, they can call the argument with the pathname above, e.g. `--output_text_file "outputs/my_text.txt"
What is normally the correct way to do this?
I suspect that if I use:
default=""
there will be an error above.
Quite simply, just omit the default:
parser.add_argument('--output-text-file')
In the args, check whether the output_text_file is None. The name will always be present.
You could set your default to os.devnull, it's a cross-platform value which corresponds to the file path of a null device
parser.add_argument('--output_text_file',
default=os.devnull,
help='file path for the output text file')

Can Python argparse take its input from a file?

Can my Python script take its arguments from a file, rather than the command line? I don't mind passing the file containing the arguments on the command line.
I am using argparse.
The reason is that I have a very complex argument list. I suppose that I could just wrap the call in a batch file, or another Python script, but I wondered if this is possible and thought that I would ask and maybe learn something.
So, instead of myScript.py --arg_1=xxx --arg2_=xxx ... --arg_n=xxx, can I
myScript.py --file args.txt where args.txt contains
--arg_1=xxx
--arg_2=xxx
...
--arg_n=xxx
You can tell the parser that arguments beginning with certain characters are actually names of files containing more arguments. From the documentation:
>>> with open('args.txt', 'w') as fp:
... fp.write('-f\nbar')
>>> parser = argparse.ArgumentParser(fromfile_prefix_chars='#')
>>> parser.add_argument('-f')
>>> parser.parse_args(['-f', 'foo', '#args.txt'])
Namespace(f='bar')
The parser reads from args.txt and treats each line as a separate argument.
You can do this by taking command line argument as filename and then opening it.
like
file_name=sys.argv[1]
f=open(file_name)
arguments= f.read()
user_input=arguments.split()
user_argument=[]
for i in range():
user_argument.append(user_input[i])
Here you get the list of user argument in the list user_argument. Perheps you will get what you want!

How can I call in2csv from a Python script?

When I try calling the below code, I run into the following error: "You must specify a format when providing data via STDIN (pipe)."
subprocess.call(["in2csv", "--format", "xls", a_file, ">", output_file], shell=True)
I'm not sure why this is the case because I am telling it what the initial format is. I've looked at the docs, which isn't clear about the distinction between --format and -f.
Update: I've changed it to use argparse to simplify passing the arguments following this recommendation. I'm also using Popen as used here, which is apparently safer than using shell=true flag according to the docs.
parser = argparse.ArgumentParser()
parser.add_argument('in2csv')
parser.add_argument('--format')
parser.add_argument('xls')
parser.add_argument(a_file)
parser.add_argument(">")
parser.add_argument(output_file)
args = parser.parse_args()
print args
subprocess.Popen(args)
Errors like what you've seen are a symptom of the shell getting confused by the string passed in, for instance because of a space in a filename. It is indeed best to avoid using the shell when spawning processes from Python.
Instead of adding ">" and output_file as arguments, try redirecting the output using the stdout keyword argument, which takes a file that output will be written to.
Assuming:
a_file is a string with the name of your input file, and
output_file is a string with the name of your desired output file,
a working call might look like:
with open(output_file, 'wb') as of:
subprocess.check_call(["in2csv", "--format", "xls", a_file],
stdout=of)
It's not necessary to use argparse here; it's meant for handling command lines coming in to your program, rather than going out from it.

Multiple files for one argument in argparse Python 2.7

Trying to make an argument in argparse where one can input several file names that can be read.
In this example, i'm just trying to print each of the file objects to make sure it's working correctly but I get the error:
error: unrecognized arguments: f2.txt f3.txt
. How can I get it to recognize all of them?
my command in the terminal to run a program and read multiple files
python program.py f1.txt f2.txt f3.txt
Python script
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument('file', nargs='?', type=file)
args = parser.parse_args()
for f in args.file:
print f
if __name__ == '__main__':
main()
I used nargs='?' b/c I want it to be any number of files that can be used . If I change add_argument to:
parser.add_argument('file', nargs=3)
then I can print them as strings but I can't get it to work with '?'
If your goal is to read one or more readable files, you can try this:
parser.add_argument('file', type=argparse.FileType('r'), nargs='+')
nargs='+' gathers all command line arguments into a list. There must also be one or more arguments or an error message will be generated.
type=argparse.FileType('r') tries to open each argument as a file for reading. It will generate an error message if argparse cannot open the file. You can use this for checking whether the argument is a valid and readable file.
Alternatively, if your goal is to read zero or more readable files, you can simply replace nargs='+' with nargs='*'. This will give you an empty list if no command line arguments are supplied. Maybe you might want to open stdin if you're not given any files - if so just add default=[sys.stdin] as a parameter to add_argument.
And then to process the files in the list:
args = parser.parse_args()
for f in args.file:
for line in f:
# process file...
More about nargs:
https://docs.python.org/2/library/argparse.html#nargs
More about type: https://docs.python.org/2/library/argparse.html#type
Just had to make sure there was at least one argument
parser.add_argument('file',nargs='*')

How do get parameter without name in python

I am trying to write a python script which accepts optional input parameters plus an input file:
./script --lines 1 file.txt
should take 1 for the number of lines (--lines) and then take file.txt as an input file. However, getopt does not even see "file.txt" since it does not have a parameter name in front of it.
How can I get the filename? I already considered using sys.arv[-1], but this means when I run:
./script --lines 1
then 1 will be taken as the input filename. My script will then throw an error (if no file named 1 exists):
error: file '1' no found
This works, but is not a great solution. Is there a way around this?
Definitely use argparse -- It's included in python2.7+ and can easily be installed for older versions:
parser = argparse.ArgumentParser()
parser.add_argument('--lines', type=int, default=0, help='number of lines')
parser.add_argument('file', help='name of file')
namespace = parser.parse_args()
print namespace.lines
print namespace.file
In a call to getopts:
opts, args = getopts(...)
the second element is a list of arguments not recognized by getopts. In your example, it will be a list containing the single item 'file.txt'.

Categories