2

I want to create interactive mode code like git rebase -i HEAD~6 but for PIPE editing, stdin redirection. another example is vipe of moreutils.

To do that, I learn this code below.

# Source: https://stackoverflow.com/a/39989442/20307768
import sys, tempfile, os
from subprocess import call

EDITOR = os.environ.get('EDITOR', 'vim')  # that easy!

initial_message = b'something'  # if you want to set up the file somehow

with tempfile.NamedTemporaryFile(suffix=".tmp") as tf: 
  tf.write(initial_message)
  tf.flush()
  call([EDITOR, tf.name])

To get PIPE and edit it, I added two lines.

text = sys.stdin.read()
initial_message = text.encode()

The problematic full code is below.

import sys, tempfile, os
from subprocess import call

EDITOR = os.environ.get('EDITOR', 'vim')

text = sys.stdin.read()
initial_message = text.encode()

with tempfile.NamedTemporaryFile(suffix=".tmp") as tf: 
    tf.write(initial_message)
    tf.flush()
    call([EDITOR, tf.name])

After running the second code with echo "some words" | python the_code.py in the shell and exiting vim:q!, the terminal is messed up. (reset in the shell command will fix it.)

Without reset, I can type in a shell of macOS, but the prompt is in a weird place. I can't even type in a shell of Linux.

I typed set -x, already.

[rockyos@localhost python-vipe]$ echo "asdfasd" | python vipe.py
+ python vipe.py
+ echo asdfasd
Vim: Warning: Input is not from a terminal
++ printf '\033]0;%s@%s:%s\007' rockyos localhost '~/TODO/python-vipe'
                                                                            ++ history -a
                                                                                         ++ history -c
                                                                                                      ++ history -r
                                                                                                                   [rockyos@localhost python-vipe]$ 

I just want to return normal terminal after running the second full code. Also, why is this happening?

I tried os.system('stty sane; clear;') and os.system('reset') at the end of the code.(https://stackoverflow.com/a/17452756/20307768) os.system('reset') gave me what I wanted. But the message is annoying. I mean I can do os.system('clear') again, but that is not what normal other program does.

Erase set to delete. 
Kill set to control-U (^U). 
Interrupt set to control-C (^C). 
Constantin Hong
  • 701
  • 1
  • 2
  • 16

1 Answers1

3

I want to create interactive mode code like git rebase -i HEAD~6 but for PIPE editing, stdin redirection. another example is vipe of moreutils.

vipe is an open-source tool, and the source code is less than 100 lines long, so let's take a moment to look at how it does this. It can't rely on stdin or stdout to be a terminal, because normally it's used in the middle of a series of pipes.

Here's how they solved that, in Perl. First, they close STDIN, or file descriptor 0. Then, they open /dev/tty in read mode at descriptor 0. They also do the same thing for STDOUT, but we don't need that.

close STDIN;
open(STDIN, "</dev/tty") || die "reopen stdin: $!";
open(OUT, ">&STDOUT") || die "save stdout: $!";
close STDOUT;
open(STDOUT, ">/dev/tty") || die "reopen stdout: $!";

So, how can we do the same thing in Python?

  1. Open /dev/tty in read mode.
  2. That might not have been opened at descriptor 0, so copy it to descriptor 0.
  3. Close the old FD.

Code:

import sys, tempfile, os
from subprocess import check_call

EDITOR = os.environ.get('EDITOR', 'vim')

text = sys.stdin.read()
initial_message = text.encode()

with tempfile.NamedTemporaryFile(suffix=".tmp") as tf: 
    tf.write(initial_message)
    tf.flush()

    stdin_fd = os.open('/dev/tty', os.O_RDONLY)
    os.dup2(stdin_fd, 0)
    os.close(stdin_fd)

    check_call([EDITOR, tf.name])

    print(open(tf.name).read())

This was tested on OSX 13.3.1.

Nick ODell
  • 15,465
  • 3
  • 32
  • 66
  • I just tested it in rocky os. It works. Thank you for the detailed explanation! – Constantin Hong Jun 21 '23 at 21:36
  • I actually never thought to make a [Python port](https://github.com/Constantin1489/pyvipe#thanks-to) of `vipe` before your answer, I made it. Thank you. The specific script file is [here](https://github.com/Constantin1489/pyvipe/blob/master/pyvipe/pyvipe.py). – Constantin Hong Jun 25 '23 at 11:34