epipe的實現原理是什麼?

最近發現一個好玩的腳本,可以在管道中調用外部編輯器來編輯內容:

https://github.com/cute-jumper/epipe5

它的實現方式只有寥寥數行:

    #$!/usr/bin/env bash  -*- mode: sh; -*-
    tty="/dev/$(ps -o tty= -p $$)"
    temp_file=$(mktemp)
    default_editor="emacs"
    [ ! -t 0 ] && cat > $temp_file
    ${EDITOR:-${VISUAL:-$default_editor}} $temp_file <$tty >$tty && cat $temp_file
    rm $temp_file

然而就這麼幾行的腳本,我卻發現看不懂~~~

主要有三點:

  1. [ ! -t 0 ] 是什麼意思
  2. 用編輯器來編輯臨時文件時爲什麼要重定向stdio呢?
  3. 爲什麼重定向的時候不直接重定向到 /dev/tty 而要顯示的獲取編輯器實際的tty呢?

在經過與大夥的討論後, 最終引來原作者的解釋,才最終恍然大悟. 現在把討論的結果整理如下(其中大量引用了討論時的原文):

首先第一個問題其實很簡單, -t fd 用來檢查 fd 是否是是表示tty設備的文件描述符,所以這句話就是確保只有在該腳本的stdin被管道連接後才用cat從管道中讀出內容放到臨時文件中.

第二個問題,爲什麼要重定向編輯器的stdio呢? 這個問題可以通過幾個實驗來解答.

  1. Vim 要求 stdout 必須是 terminal

     ~ $ ( vim tmp && cat tmp ) | nl 
    Vim: Warning: Output is not to a terminal
    

    解決方案是將stdout重定向到terminal

    ~ $ tty=`tty`
    ~ $ ( 1>$tty vim tmp && cat tmp ) | nl
    
  2. Vim 要求 stdin 也必須是 terminal

    ~ $ pwd | ( vim tmp && cat tmp )
    Vim: Warning: Input is not from a terminal
    

    解決方案是將stdin重定向到terminal

    ~ $ pwd | ( 0<$tty vim tmp && cat tmp )
    

也就是說,至少對於某些編輯器而言,是要求stdio必須爲terminal的.

那麼爲什麼不能直接重定向到 /dev/tty 呢? 畢竟即使 stdin 和 stdout 被重定向過了,/dev/tty 仍然還是指向原來的 terminal (嚴格地說,/dev/tty 是當前 process 的 terminal),這也是“找回”原有的標準輸入輸出的一種很普遍的做法啊.

對於大部分情況來說,這樣是沒問題的,比如 Vim,Emacs。但是對於 emacsclient,這樣卻行不通,這實際上與emacsclient的實現有關.

查看 emacsclient.c,能看見裏面會根據當前的 stdout 來獲取 ttyname:

ttyname(fileno(stdout))

emacsclient將當前的 ttyname 發送給 server後。如果此時 stdout 是 /dev/tty 的話 ,這時候發送的就是 /dev/tty,然而 /dev/tty 對於 client 和 server 來說,含義是不一樣的(他們不在同一個 terminal)。發送到 server 後,server 會以爲是 server 本身的 process 所在的 tty,而這個 tty 和 client 所在的 tty 不一樣,這就會導致無法創建 client 的frame。

解決方案是:顯式獲得 emacsclient 實際的 tty,然後將 stdout 設成該 tty,而不是發送類似別名一樣的 /dev/tty,這也就是第一行的作用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章