tags:

views:

248

answers:

4

Hi,

I have a CLI script and want him to read data from a file. It should be able to read it in two ways :

  • cat data.txt | ./my_script.py
  • ./my_script.py data.txt

A bit like grep, for example.

What I know:

  • sys.argv and optparse let me read any args and options easily.
  • sys.stdin let me read data piped in
  • fileinput make the full process automatic

Unfortunetly:

  • using fileinput use stdin and any args as input. So I can't use options that are not filenames as it tries to open them.
  • sys.stdin.readlines() work fine, but it I don't pipe any data, it hangs until I enter Ctrl + D
  • I don't know how to implement "if nothing in stdin, read from a file in args" because stdin is always True in a boolean context.

I'd like a portable way to do so is possible.

+2  A: 

There is no reliable way to detect if sys.stdin is connected to anything, nor is it appropriate do so (e.g., the user wants to paste the data in). Detect the presence of a filename as an argument, and use stdin if none is found.

Ignacio Vazquez-Abrams
Thanks to both of you. Since I lack of the necessary knowledge to choose between your answer and gnibbler's, what are the drawbacks of each ? Them seem both valid.
e-satis
Oh, just tried this approach but their is a problem: if there is no files and no stdin, I end up reading stdin and it hangs. How can I write an error message to the user to tell him to provide data?
e-satis
Note well that the hang-until-EOF is inevitable here if someone runs it without a filename -- this is standard behavior in the Unix world. see also grep, cat, etc.. If this isn't acceptable, the only portable way you can avoid it is to use another typical convention wherein providing a filename of '-' means 'read from stdin' (or write to stdout).
Nicholas Knight
Actually, I just fired grep without any input. I just fired grep and sed without any input and they don't hang.
e-satis
Ok you are right. Cat does wait for input But how does grep and sed do? I give them no args, I guess they check, they see no arg, then they look for stdin, why do they manage to display the message ?
e-satis
`grep` and `sed` need to be passed arguments in order to even begin to be useful. There's no point in waiting for stdin if there's nothing to do with it.
Ignacio Vazquez-Abrams
So I should, check my args, if no args given, give a message back, then if args but no file among them, check for stdin, then if no stdin, hangs for manual input. It seems logical...
e-satis
+1: This is the standard approach. Use sys.argv. No magic.
S.Lott
+2  A: 

For unix/linux you can detect whether data is being piped in by looking at os.isatty(0)

$ date | python -c "import os;print os.isatty(0)"
False
$ python -c "import os;print os.isatty(0)"
True

I'm not sure there is an equivalent for Windows.

edit Ok, I tried it with python2.6 on windows XP

C:\Python26>echo "hello" | python.exe -c "import os;print os.isatty(0)"  
False

C:\Python26> python.exe -c "import os;print os.isatty(0)"  
True

So maybe it it not all hopeless for windows

gnibbler
Thanks to both of you. Since I lack of the necessary knowledge to choose between your answer and Ignocio's, what are the drawbacks of each ? Them seem both valid.
e-satis
@e-satis: What happens if no filename is passed as an argument? When you can answer this question you'll know what you need to do.
Ignacio Vazquez-Abrams
For now, this seems to work so I accept it. Thanks again. I'm still interested in knowing the issues with what I'm doind. What is a tty, and why does this work? When will it not?
e-satis
Note that this is defined as Unix-only in the docs, so you hurt portability to Windows and possibly other operating systems. It also assumes you'll never want to just paste data in, which is a silly assumption.
Nicholas Knight
@e-satis: A tty technically stands for teletype terminal. These days it really just means it's an interactive terminal. The particular concept used by isatty() is non-portable outside Unix, however.
Nicholas Knight
@Ignacio Vazquez-Abrams: for now my workflow is: istty? no: fetch stdin / yes: try opening a file. try ok? carry on / except: display erro message and exit
e-satis
@Nicholas Knight: ah, this is not really my best choice then. Any other solutions?
e-satis
I remove "accepted", not becaused I don't find the solution useful, but because I hope somebody can come with something portable.
e-satis
Oh, I assumed it was unix/linux when I read `cat data.txt` :)
gnibbler
Yeah, I'm working under linux, but I not a fanatic. I like to play various os.
e-satis
-1: This is a non-standard approach.
S.Lott
+3  A: 

Argparse allows this to be done in a fairly easy manner, and you really should be using it instead of optparse unless you have compatibility issues.

The code would go something like this:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--input', type = argparse.FileType('r'), default = '-')

Now you have a parser that will parse your command line arguments, use a file if it sees one, or use standard input if it doesn't.

sykora
Thanks to you as well. I'm learning a lot today! Anyway, don't you think there is a way to do that with the standard lib? If not, I'm ok with argparse. But optparse does exist...
e-satis
argparse is pretty tiny, is pure python code and is also nicer to use than optparse. While I wouldn't normally want to add a new dependancy to a project just to read in the command line options, the three factors above have made argparse more than worthwhile in my experience.
mavnn
This can be done in the same volume of code in `optparse`.
S.Lott
@S.Lott I didn't know that, thanks for mentioning. In any case, I recommended argparse for its overall benefits anyway.
sykora
+4  A: 

Process your non-filename arguments however you'd like, so you wind up with an array of non-option arguments, then pass that array as the parameter to fileinput.input():

import fileinput
for line in fileinput.input(remaining_args):
    process(line)
Andrew Aylett
I like that, it seems highly effective. Is there anything bad I'm missing?
e-satis
I believe that this will provide behaviour similar to other Unix commands. In another comment you said you're slightly concerned about the hang-until-input effect when no arguments are given; unless you take steps to notice when no arguments are passed, this will still happen. There's no reason you shouldn't catch this case, though, as passing '-' as a parameter will still read from stdin.
Andrew Aylett
I will use a combination of Ignocio's advice and Andrew's solution.
e-satis