19

I am creating a Python script that asks for input from the command line. The user will have the ability to edit a part of a file. I can ask for the new information and overwrite it in the file, no problem. But I would rather have the to-edit part of the file already put in the command line, so it does not have to be typed completely over. Is this possible?

File:

1|This file
2|is not empty

Example:

>>>edit line 2
Fetching line 2
Edit the line then hit enter
>>>is not empty                  #This is written here by the script, not by the user

Which then can be changed to

>>>is not full either
Edited file

Afther which the file has changed to:

1|This file
2|is not full either

I hope it's clear what I am trying to accomplish.

This question has been said to answer my question, it does to a certain extent. It does when I am running Linux with readline. However, I am not. I am using Windows and am not using readline. I would like to only use the standard library.
An answer for Windows is also provided with that question. However, I get an ImportError with win32console, it might be because mentioned question is not about Python3.4, but mine is. Also, I was wondering if this was possible with the standard library, not with an external library.

Community
  • 1
  • 1
ikhebgeenaccount
  • 353
  • 6
  • 20
  • 1
    So the question is: How to ask for user input on the command line while providing a 'default' answer that the user can edit or delete? – tobias_k May 12 '15 at 14:34
  • 2
    I can't say for sure that this is impossible, but in all my years, I've never seen anything in the standard libraries that could do this. (unless it's in an OS-specific module like `curses`) – Kevin May 12 '15 at 14:37
  • @Kevin Not sure what you mean with `curses`, but I do not use any different libraries than the default. – ikhebgeenaccount May 12 '15 at 14:40
  • One possible alternative is to write your own CLI shell using a GUI library, which you can then configure to do whatever you want. May be more trouble than it's worth, though, since you'd basically be implementing all behavior from the ground up. – Kevin May 12 '15 at 14:45
  • 2
    See e.g. http://stackoverflow.com/q/2533120/3001761 – jonrsharpe May 12 '15 at 14:48
  • @jonrsharpe That does not answer my question, unfortunately. See my edit. – ikhebgeenaccount May 12 '15 at 15:00
  • @ikhebgeenaccount there is [a Windows answer](http://stackoverflow.com/a/5888246/3001761) there. If that doesn't do it for you, maybe it isn't possible in the standard library and you need to make some compromises. – jonrsharpe May 12 '15 at 15:01
  • @jonrsharpe I just found it, yes. I cannot seem to find the `win32console` import. Maybe it is only Python2.x? – ikhebgeenaccount May 12 '15 at 15:04
  • @ikhebgeenaccount ah; it's not in the standard library: http://stackoverflow.com/q/2559731/3001761 (note you can check what's in the standard lib at e.g. https://docs.python.org/2/library/index.html) – jonrsharpe May 12 '15 at 15:05
  • @jonrsharpe Thank you for providing the link. I will not use it though, but it will prove itself useful in the future, I am sure. – ikhebgeenaccount May 12 '15 at 15:13
  • Have you seen http://stackoverflow.com/a/13615802/208880? – Ethan Furman May 18 '15 at 19:29

4 Answers4

7

Unfortunately, I don't know if kind of input() with default value is available in standard library.

There is an external solution - use win32console as mentioned in this answer. However, it has two pitfalls as far as I can see. First, the import is bundled in a package pywin32. So you would use pip install pywin32, except it does not work, because of the second pitfall: the information about the package at pypi is outdated, it says that package is incompatible with Python 3.4...

But in fact, it can work! You should follow the "Download URL" visible at pypi project page (i.e. https://sourceforge.net/projects/pywin32/files/pywin32/ ) and install latest build. I just installed build 219 for Py3.4, as I myself also use this Python version. On the page installers are provided for several Python versions for 32bit and 64bit Windows.

Also, I've tweaked the code from above-linked SO answer to work in Python 3:

import win32console

_stdin = win32console.GetStdHandle(win32console.STD_INPUT_HANDLE)

def input_def(prompt, default=''):
    keys = []
    for c in str(default):
        evt = win32console.PyINPUT_RECORDType(win32console.KEY_EVENT)
        evt.Char = c
        evt.RepeatCount = 1
        evt.KeyDown = True
        keys.append(evt)

    _stdin.WriteConsoleInput(keys)
    return input(prompt)

if __name__ == '__main__':
    name = input_def('Folder name: ', 'it works!!!')
    print()
    print(name)

This works on my Windows machine... If this does not work on yours, can you provide the error message?

Community
  • 1
  • 1
mbdevpl
  • 4,605
  • 4
  • 30
  • 42
  • OP asks for **stdlib** solution (in principle, you could [make the calls using `ctypes` module](http://stackoverflow.com/a/13615802/4279)). If we are installing 3-rd party libraries; I would try `pyreadline` [with this answer](http://stackoverflow.com/a/2533142/4279) (and `win-unicode-console` to support arbitrary Unicode input/output in the console). – jfs Aug 22 '15 at 13:32
  • From the wording in the question, I think OP simply "wonders" if there is a stdlib solution, but asks for any working solution :) Package `readline` is unavailable on Windows, and replacement package `pyreadline` is not in the core. So is `win-unicode-console`. Nevertheless, I've just tested `pyreadline` because it seemed simpler, but [this gist](https://gist.github.com/mbdevpl/ed9a64eed9903cc43286) does not work in my case. – mbdevpl Aug 23 '15 at 00:34
  • this code sometimes adds random characters at the beginning of my default string, any idea why? (I guess the culprit is `evt = win32console.PyINPUT_RECORDType(win32console.KEY_EVENT)` – Sulli Dec 30 '18 at 19:43
  • Actually the `keys` variable seems fine so I guess the problem is with `_stdin.WriteConsoleInput(keys)` – Sulli Dec 30 '18 at 19:52
1

I have written a line editor which hopefully does what you are looking for. But it is a quick-and-dirty hack. It is Windows only and written with CPython 3.6.5 on Windows 10, so its use might be limited. It has been tested on codepage 1252 (ANSI Latin 1; Western European (Windows)) and codepage 65001 (utf-8). It is very basic and a bit sluggish as it is not speed-optimized. (I should rewrite it in C but I do not have the time.) It is hardly tested and poorly documented.

import msvcrt
import os
import sys

if os.name != 'nt':
    raise NotImplementedError('This module works only on MS Windows!')

CTRL_00         = 0
CTRL_E0         = 224
KEY_BACKSPACE   = 8
KEY_DELETE      = 83                                            # CTRL
KEY_END         = 79                                            # CTRL
KEY_ESC         = 27
KEY_HOME        = 71                                            # CTRL
KEY_INSERT      = 82                                            # CTRL
KEY_LEFT        = 75                                            # CTRL
KEY_RETURN      = 13
KEY_RIGHT       = 77                                            # CTRL

flush = sys.stdout.flush
write = sys.stdout.write

mode    = ('[OVR]> ', '[INS]> ')                                # overwrite, insert
prefix  = len(mode[0])

def _update_line(insert, source, length, line, target):
    """Write a new line and position the cursor.
    source: previous cursor position
    length: old line length
    line:   edited line
    target: next cursor position
    """
    write('\b' * source)                                        # set cursor to start of line
    write(' ' * length)                                         # erase old line
    write('\b' * length)                                        # again, set cursor to start of line
    write(mode[insert] + line[prefix:])                         # write updated line
    write('\b' * (len(line) - target))                          # set cursor to new position
    flush()                                                     # write buffer to screen

def mswin_line_edit(default_string, insert=True):
    """Edit a MS Windows CLI line."""

    insert = insert
    line = mode[insert] + default_string
    count = len(line)
    before = line[:count]
    after = line[count:]
    print(line, end='', flush=True)
    cursor = count

    while True:
        key = msvcrt.getwch()
        num = ord(key)
        if num == KEY_ESC:                                      # abort edit
            return default_string
        if num == KEY_RETURN:                                   # finish edit
            return line
        if num == KEY_BACKSPACE:                                # delete character before cursor
            if cursor > prefix:
                before = line[:cursor - 1]
                after = line[cursor:]
                line = before + after
                _update_line(insert, cursor, count, line, cursor - 1)
                cursor -= 1
                count = len(line)
        elif num == CTRL_E0 or num == CTRL_00:                  # CTRL
            ctrl = ord(msvcrt.getwch())
            if ctrl == KEY_END:                                     # set cursor after last character
                if cursor < count:
                    before = line
                    after = ''
                    _update_line(insert, cursor, count, line, count)
                    cursor = count
            elif ctrl == KEY_HOME:                                  # set cursor before first character
                if cursor > prefix:
                    before = ''
                    after = line
                    _update_line(insert, cursor, count, line, prefix)
                    cursor = prefix
            elif ctrl == KEY_LEFT:                                  # move cursor 1 character to the left
                if cursor > prefix:
                    before = line[:cursor]
                    after = line[cursor:]
                    _update_line(insert, cursor, count, line, cursor - 1)
                    cursor -= 1
            elif ctrl == KEY_RIGHT:                                 # move cursor 1 character to the right
                if cursor < count:
                    before = line[:cursor]
                    after = line[cursor:]
                    _update_line(insert, cursor, count, line, cursor + 1)
                    cursor += 1
            elif ctrl == KEY_DELETE:                                # delete character after cursor
                if cursor < count:
                    before = line[:cursor]
                    after = line[cursor + 1:]
                    line = before + after
                    _update_line(insert, cursor, count, line, cursor)
                    count = len(line)
            elif ctrl == KEY_INSERT:                                # switch insert/overwrite mode
                insert ^= True
                _update_line(insert, cursor, count, line, cursor)
        else:                                                   # ordinary character
            before = line[:cursor] + key
            if insert:
                after = line[cursor:]
            else:
                after = line[cursor + 1:]
            line = before + after
            _update_line(insert, cursor, count, line, cursor + 1)
            cursor += 1
            count = len(line)

if __name__ == '__main__':
    test_string = input('test string: ')
    result = mswin_line_edit(test_string)
    print(f'\n{result}')
  • Too bad, it doesn't work out-of-the-box (with some minimal adjustments) on a Mac. Perhaps you could adjust the "universal `getch` answer in [Python read a single character from the user](https://stackoverflow.com/a/510364/2564301)? – Jongware Apr 13 '18 at 22:04
  • 1
    I apologize. I very seldom use Linux (Unix) and I don't have any access to a Mac. So I'm not able to write--and especially test-- an OS-agnostic version. –  Apr 14 '18 at 05:59
0

You could do it with tkinter:

from tkinter import *
def enter():
    global commandEntry
    command = commandEntry.get()
    # Do stuff with command
    commandEntry.delete(0, END)
def edit_line(line):
    global commandEntry
    commandEntry.insert(0, line)
root = Tk()
messageVar = StringVar()
messageVar.set("Enter a command:")
message = Label(root, textvariable=messageVar)
commandEntry = Entry(root)
enterButton = Button(root, text="Enter", command=enter)
root.mainloop()
88888
  • 31
  • 5
-1

You should just have 2 variables: one for standard string, one for string that will user change by itself. Like:

str1 = 'String that is standard'
str2 = str1 #it usually will be standard string
usr = input('your text goes here')
if len(usr) != 0:
    str2 = usr
#and here goes code for writing string into file
Rajeev Desai
  • 144
  • 2
  • 12
Ignas Kiela
  • 171
  • 3
  • 11
  • This may solve the issue of having a default value, but it does not allow the user to edit the default, which I think was more central to the question. – Zoey Hewll Oct 15 '16 at 00:25