3

Probably an easy question, but I am basically trying to replicate the behavior (very sparsely and simplistically) of the cat command but in python. I want the user to be able to enter all the text they wish, then when they enter an EOF (by pressing ctrl+d or cmd+d) the program should print out everything they enter.

import sys
for line in sys.stdin:
    print line

If I enter the input lines and follow the last line with a return character, and then press cmd+d:

never gonna give you up
never gonna let you down
never gonna run around
and desert you

then the output is

never gonna give you up
never gonna let you down
never gonna run around
and desert you

However, if I press cmd+d while I am still on the last line "and desert you" then the output is:

never gonna give you up
never gonna let you down
never gonna run around

How can I modify the program such that if the user presses the EOF on the last line, then it should still be included as part of the output?

Jeremy Fisher
  • 2,510
  • 7
  • 30
  • 59
  • Its environment specific... which operating system are you on? – tdelaney Feb 05 '16 at 03:58
  • You should perhaps read about [No final newline](https://stackoverflow.com/questions/12916352/). Granted, that discusses shell scripts rather than Python, but some of the issues are similar and arise from the same source; the code shows the last line, and doesn't show the trailing data after the last line. – Jonathan Leffler Feb 05 '16 at 04:04

3 Answers3

2

Okay that was wierd but it turns out there is an explanation. ctrl-d is "end of transmission" (EOT), not EOF. What it means is environment specific, but to a *nix terminal, it means to flush the input buffer to the program immediately.

If there are characters in the buffer, python's stdin gets them but since there is no \n, the stdin iterator buffers them and doesn't emit anything yet.

If there are no characters in the buffer, python's stdin gets an empty buffer and python follows the rule that an empty buffer means EOF and it should stop iteration.

If you type some characters and then 2 ctrl-d's in a row, you'll get the buffered characters and iteration will end. And, to keep things as complicated as possible, if you start another for line in sys.stdin it will happily continue taking input.

EDIT

This script will give you any pending characters immediately when you hit ctrl-d with data in the input buffer but it won't know to exit unless you hit a second ctrl-d

import sys

while True:
    c = sys.stdin.read(1)
    if not c:
        break
    sys.stdout.write(c)
    sys.stdout.flush()
tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • If I ran this from a *nix os like in lubuntu would I still have this problem? I was coding in OS X while my vbox was installing and that's when i noticed this issue. i have a lubuntu vm set up now and i only need to make this code work for this environment – Jeremy Fisher Feb 05 '16 at 04:03
  • I'm on linux mint (based on ubuntu) and I duplicated your problem with python 3.5, so yeah, i think its still a problem. @Curt has an interesting suggestion, but you may be able to do something like reading 1 character at a time to get around it. – tdelaney Feb 05 '16 at 04:05
  • 1
    I don't think you can really get the behavior you want - _" if the user presses the EOF on the last line"_ - because the EOT is swallowed by the terminal driver and all it does is flush the input buffer. `read(1)` will give you all of the characters when ctrl-d is pressed but you won't know to exit. The user will have to press it a second time. – tdelaney Feb 05 '16 at 04:14
  • i appreciate the edit but is there a way i can set it such that the user can enter a block of text and then that block is printed out rather than this soln which prints out each line as it is entered? it's a good solution i would just prefer the former – Jeremy Fisher Feb 05 '16 at 04:45
2

The behavior of Control-D is actually a function of the TTY driver of the operating system, not of Python. The TTY driver normally sends data to programs a full line at a time. You'll note that 'cat' and most other programs behave the same way.

To do what you're asking is non-trivial. It usually requires putting the TTY into raw mode, reading and processing data (you would get the Control-D character itself), and then taking it out of raw mode before exiting (you don't want to exit in raw mode!)

Procedures for doing that are O/S dependent, but you might be able to make use of tty.setraw() and tty.setcbreak().

Curt
  • 470
  • 3
  • 7
0

I think this is a python 2.7 problem. I tried your code on Python 3.5 and it works just fine.

Hope this helps!

Kem Andrew
  • 26
  • 4