2017-04-08 185 views
6

我想從Python調用一個程序,並且認爲它的stdout是一個tty,即使Python的進程標準輸出附加到管道上。所以我用了pty.spawn功能來實現這一點,可以從驗證如下:python pty.spawn stdin沒有被回顯,但被重定向到了主標準輸出

$ python -c "import sys; from subprocess import call; call(sys.argv[1:])" python -c "import sys; print sys.stdout.isatty()" | cat 
False 

$ python -c "import sys; import pty; pty.spawn(sys.argv[1:])" python -c "import sys; print sys.stdout.isatty()" | cat 
True 

(我們看到的是,在第二個命令我們已經實現了我們的目標,即衍生的進程被誤以爲其標準輸出是一個tty。)

但問題是,如果我們使用pty.spawn那麼它的輸入不被回顯,而是被重定向到主站的標準輸出。這可以通過以下命令可以看出:

$ python -c "import sys; import pty; pty.spawn(sys.argv[1:])" cat > out.txt 
$ # Typed "hello" in input, but that is not echoed (use ^D to exit). It is redirected to output.txt 
$ cat out.txt 
hello 
hello 

(但是,當我們使用subprocess.call

$ python -c "import sys; from subprocess import call; call(sys.argv[1:])" cat > out1.txt 
hello 
$ cat out1.txt 
hello 

因爲它的標準輸入和標準輸出是否正確連接到主這個問題不存在。)

我無法找到一種方式,以便程序被Python調用,它將stdout看作tty(類似於pty.spawn),但其輸入正確回顯(類似於subprocess.call)。有任何想法嗎?

回答

3

您正在創建一個stdout連接到文件的終端,以便終端所執行的正常回顯被髮送到文件而不是屏幕。

我不確定spawn是否打算像這樣直接使用:pty庫提供pty.fork()來創建子進程並返回stdin/stdout的文件描述符。但是你需要更多的代碼來使用它。

爲了克服你與spawn具有當前問題,繼承人兩個簡單的選擇:

選項1:如果所有你關心的是發送催生命令的輸出到一個文件,那麼你可以做(我喜歡命名管道和here文件蟒蛇單行):

它看起來像這樣運行時:

start to stdout only 
hello 
complete to stdout only 

這表明輸入(我鍵入hello)和打印語句的結果將進入屏幕。 out.txt的內容爲:

$ cat out.txt 
hello 

也就是說,只有你輸入的內容。

選項2:如果在另一方面,你想出來的文件包含各地催生了命令輸出的蟒蛇輸出,那麼你就需要一些更復雜的,如:

python <(cat << EOF 
import sys 
import pty 
import os 
old_stdout = sys.stdout 
sys.stdout = myfdout = os.fdopen(4,"w") 
print 'start to out file only' 
myfdout.flush() 
pty.spawn(sys.argv[1:]) 
print 'complete to out file only' 
sys.stdout = old_stdout 
EOF 
) bash -c 'cat >&4' 4>out.txt 

這將唯一有此輸出運行時(不管你鍵入IE)終端:

hello 

但出文件將包含:

$ cat out.txt 
start to out file only 
hello 
complete to out file only 

背景: python pty庫功能強大:它創建一個連接到python進程stdout和stdin的終端設備。編號想象這將使用大部分的使用pty.fork()調用,以便真正的標準輸入/標準輸出不受影響。

但就你而言,在你的shell中,你將python進程的標準輸出重定向到一個文件。由此產生的pty也因此將其stdout附加到該文件,因此將stdin回顯到stdout的正常操作正在被重定向。常規stdout(屏幕)仍然存在,但未被新的pty使用。

選項1以上的主要區別是將標準輸出重定向到某處發生pty.spawn呼叫內,從而使PTY創建仍然具有實際終端標準輸出清晰的連接(當它試圖回聲標準輸入您鍵入)

選項2所不同的是,以創建一個任意的文件描述符的第二信道(即文件描述符4)和在地方標準輸出的利用這一點,一旦你內部蟒並在創建你生成的過程(即將你生成的過程的標準輸出重定向到相同的文件描述符)

這兩種差異都會阻止pty.spawn創建的pty將其stdout從實際終端更改或斷開連接。這可以讓stdin的回顯正常工作。

有跡象表明,使用pty庫,給你更多的控制包,但你會發現大多數的這些使用pty.fork()(有趣的是我還沒有發現一個尚未實際使用pty.spawn

編輯這裏有一個例子

import sys 
import pty 
import os 
import select 
import time 
import tty 
import termios 

print 'start' 
try: 
    pid, fd = pty.fork() 
    print 'forked' 
except OSError as e: 
    print e 

if pid == pty.CHILD: 
    cmd = sys.argv[1] 
    args = sys.argv[1:] 
    print cmd, args 
    os.execvp(cmd,args) 
else: 
    tty.setraw(fd, termios.TCSANOW) 
    try: 
     child_file = os.fdopen(fd,'rw') 
     read_list = [sys.stdin, child_file] 
     while read_list: 
      ready = select.select(read_list, [], [], 0.1)[0] 
      if not ready and len(read_list) < 2: 
       break 
      elif not ready: 
       time.sleep(1) 
      else: 
       for file in ready: 
        try: 
         line = file.readline() 
        except IOError as e: 
         print "Ignoring: ", e 
         line = None 
        if not line: 
         read_list.remove(file) 
        else: 
         if file == sys.stdin: 
          os.write(fd,line) 
         else: 
          print "from child:", line 
    except KeyboardInterrupt: 
     pass 

編輯questionpty.fork()

一些很好的鏈接:使用pty.fork()的

UPDATE:應該把一些註釋代碼 如何pty.fork()示例工作:

當解釋器執行調用pty.fork()的處理加工分成兩個:現在有兩個線程都出現剛剛執行了pty.fork()調用。

一個線程是您最初在(父)和一個是新線程(子)的線程。

在父,在pidfd設置爲孩子的進程ID和connnected到TEH孩子的標準輸入和標準輸出文件decriptor:父,當你從fd你正在閱讀的內容已被寫入讀孩子們stdout;當你寫信給fd時,你正在給孩子寫信。所以現在,在父類中,我們有一種通過stdout/stdin與其他線程進行通信的方式。

在孩子中,pid設置爲0,而fd未設置。如果我們想要與父線程交談,我們可以讀寫標準輸入/標準輸出,知道父母可以並且應該對此做些什麼。

這兩個線程將從這一點開始執行相同的代碼,但我們可以根據pid中的值來判斷我們是處於父代還是子線程中。如果我們想要做的兒童和家長的線程不同的東西,然後我們只需要發送孩子下來一個代碼路徑和家長下了不同的代碼路徑條件語句。那是這條線做什麼:

if pid == pty.CHILD: 
    #child thread will execute this code 
    .... 
else 
    #parent thread will execute this code 
    ... 

在孩子,我們只是想在一個新的PTY產卵新的命令。該os.execvp用來監守我們將在pty的控制,用這種方法但本質上它一樣pty.spawn()'. This means the child stdin/stdout are now connected to the command you wanted via a pty. IMmportantly, any input or output from the command (or the pty for that matter) will be available to the parent thread by reading from FD . And the parent can write to the command via pty by writing to fd`

所以現在,在父母,我們需要連接的真正標準輸入/輸出端子通過閱讀和寫入到fd的孩子標準輸入/標準輸出。那就是現在的母代碼(else部分)。任何出現在真實stdin上的數據都會寫入fd。從fd(由父級)讀取的任何數據都寫入stdout。所以父線程現在做的唯一事情就是在真正的stdin/stdout和fd之間進行代理。如果您想以編程方式對您的輸入和輸出進行操作,這就是您要做的地方。

這種情況發生在父母唯一的其他事情是這樣的呼籲:

tty.setraw(fd, termios.TCSANOW) 

這是給我們說說這個孩子PTY停止做回聲回的一種方式。

這解決了你最初遇到的問題: - 你的本地終端僅連接到父線程 - 正常回聲回到位(即之前的輸入被傳遞到處理) - 標準輸出的過程可以被重定向 - 無論你與你的終端標準輸出做對孩子的過程 的標準輸入/輸出沒有影響 - 子進程已被告知不要做它的標準輸入

這似乎是當地的回聲回很多解釋 - 如果任何人有任何清晰的編輯?

+0

解決方案option1和option2不適用於我,因爲對於外部python進程(即調用/衍生其他程序的那個進程),'stdout'被重定向到一個文件/管道,並且這不在我的控制之下(因爲它由其他人調用;我只有在我的控制下運行的python腳本)。所以我不能選擇並將輸出重定向到內部進程。 (但是,是的,如果我有這樣的自由,然後選項1&2本來是可以接受的) –

+0

我跑過去的例子代碼(**編輯**),這似乎真正解決我的問題。但我仍然在努力總結我的頭周圍的實際工作:( –

+0

@阿布舍克 - 伊夫KEDIA增加了一個交代 – spacepickle