具有單獨的標準輸入、標準輸出、標準錯誤和 tty 的 ssh
問題
考慮這樣的命令:
<binary_input ssh user@server 'sudo tool' >binary_output 2>error.log
where
tool
是任意的,ssh
是一個包裝器或一些ssh-like-contraption
允許上述工作的包裝器。定期ssh
它不起作用。我
sudo
在這裡使用過,但這只是需要 tty 的命令的範例。我想要一個通用的解決方案,而不是特定於sudo
.研究:原因
使用正常
ssh
它不起作用,因為:
sudo
需要 tty 來詢問密碼(或根本工作),所以我需要ssh -t
;實際上在這種情況下我需要ssh -tt
.- 另一方面,
ssh -tt
將從中sudo
讀取密碼binary_input
。我想通過我的本地 tty 提供密碼。即使sudo
配置為在沒有密碼的情況下工作,或者如果我將密碼注入到 tty 中binary_input
,ssh -tt
也會從遠端 tty 讀取並寫入輸出sudo
、錯誤和提示到遠端 tty。我不僅無法在本地區分輸出和錯誤/提示。所有的流都將由遠端 tty 處理,這將破壞二進制數據。tool
研究:與有效命令的比較
- 該本地命令是參考點。假設它成功處理了一些二進制數據:
<binary_input tool >binary_output
- 如果我需要
tool
在伺服器上執行,我可以這樣做。即使ssh
詢問我的密碼,這也可以:<binary_input ssh user@server tool >binary_output
在這種情況下
ssh
,對於二進制數據是透明的。
- 同樣本地
sudo
也可以是透明的。即使sudo
要求我輸入密碼,以下命令也不會破壞數據:<binary_input sudo tool >binary_output
- 但是
tool
在伺服器上執行sudo
很麻煩:<binary_input ssh user@server 'sudo tool' >binary_output
在這種配置中
ssh
並sudo
在一起通常不能是透明的。找到一種使它們透明的方法是這個問題的要點。研究:類似問題
我發現了幾個類似的問題:
這個問題只關心標準輸出。現有的答案(來自問題的作者)建議
sudo -S
使用標準輸入。我真的不想改變我的binary_input
. 我希望有一個不特定於sudo
.這集中在傳遞
Ctrl
+c
和背景是 GNUparallel
。僅在沒有遠端 tty 的情況下使Ctrl
+c
工作的解決方法對我來說是不夠的。這是一個好的開始(我認為尤其是這個答案)。但是在這裡我想強調 tty 的必要性。我想要一個自動化的解決方案,並允許我使用遠端
sudo
(或其他),就好像它是本地的一樣。我的明確問題
在以下命令中:
<binary_input ssh user@server 'requires-tty' >binary_output 2>error.log
requires-tty
是需要 tty 但處理從其標準輸入到其標準輸出的二進制數據的程式碼的佔位符。看來我需要ssh -tt
,否則requires-tty
將無法工作;同時我不能使用ssh -tt
,否則二進制數據會被破壞。我怎樣才能以方便的方式解決這個問題?
requires-tty
可以,sudo …
但我不想要特定於sudo
.我想理想的(?)解決方案將是一個腳本/工具,它
ssh
在上述呼叫中替換並且可以正常工作。它應該(?)將遠端標準輸入、標準輸出和標準錯誤分別連接到其本地對應項,並將遠端 tty 連接到本地 tty。如果可能的話,我更喜歡不需要任何伺服器端配套程序的客戶端解決方案。
腳本
我是這個問題的作者,這是我嘗試建構一個解決問題的腳本。該腳本旨在在客戶端工作,它
ssh
在相關命令中替換。這是實驗性的。我稱之為sshe
。這是腳本:#!/bin/sh - # the name of the script me="${0##*/}" # error handling functions scream() { printf '%s\n' >&2 "$1"; } die() { scream "$2"; exit "$1"; } # edge cases [ -e /dev/tty ] || { scream "$me: no tty detected, falling back to regular ssh" exec ssh "$@"; } [ "$#" -lt 2 ] && die 1 "usage: $me [options] [user@]hostname command" # initialization of variables redir0='' redir1='' redir2='' tty="/dev/$(ps -p "$$" -o tty=)" # see what needs to be redirected exec 7>&1 if [ "$(<&0 tty 2>/dev/null)" != "$tty" ]; then redir0=y; fi if [ "$(<&7 tty 2>/dev/null)" != "$tty" ]; then redir1=y; fi if [ "$(<&2 tty 2>/dev/null)" != "$tty" ]; then redir2=y; fi exec 7>&- # edge case [ "$redir0$redir1$redir2" ] || { scream "$me: no redirection detected, falling back to ssh -t" exec ssh -t "$@"; } # command line parsing, extract two last arguments: ... host command z="$#" n="$z" for arg do if [ "$n" -eq "$z" ]; then set -- fi case "$n" in 1) command="$arg" ;; 2) host="$arg" ;; *) set -- "$@" "$arg" esac n="$(($n - 1))" done # prepare to clean on exit trap 'status="$?"; rm -r "$tmpd" 2>/dev/null; trap - EXIT; exit "$status"' EXIT HUP INT QUIT PIPE TERM # temporary directory and socket tmpd="$(mktemp -d)" [ "$?" -eq 0 ] || exit 1 sock="$tmpd/sock" # main pipe: ssh master connection -> background cat ( [ "$redir0" ] || exec 0</dev/null # ssh master connection, it will report the remote PID of the remote shell via its stdout ssh -M -S "$sock" "$@" -T "$host" '</dev/null echo "$$"; exec sleep 2147483647' ) | { # read the remote PID IFS= read -r rpid || exit 1 # background process to pass data exec 6<&0 cat <&6 2>/dev/null & # move original descriptors out of the way exec </dev/tty >/dev/tty 6>&- # prepare remote redirections if [ "$redir0" ]; then redir0="<&6"; fi if [ "$redir1" ]; then redir1=">&7"; fi if [ "$redir2" ]; then redir2="2>&8"; fi # ssh to run the command, with remote tty ssh -S "$sock" -t "$host" " trap 'status=\"\$?\"; kill $rpid 2>/dev/null; trap - EXIT; exit \"\$status\"' EXIT HUP INT QUIT PIPE TERM exec 6</proc/$rpid/fd/0 7>/proc/$rpid/fd/1 8>/proc/$rpid/fd/2 9>/dev/tty $redir0 $redir1 $redir2 || exit 3; $command" }
一般免責聲明
- 該腳本在許多情況下執行良好。我並不是說它隨機失敗。它不會隨機失敗。我並不是說它不能處理一些特定的數據。它處理任意數據。
它“不能很好地工作”的情況僅來自它與本地 tty 的互動。通過不包括任何 tty 的通道流動的數據(包括任意二進制數據)總是可以的。請閱讀此答案的其餘部分,尤其是“障礙和警告”部分,以了解問題並了解應避免的情況。
- 像這樣的命令:
<binary_input sshe user@server 'sudo tool' >binary_output 2>error.log
避免了這個問題。如果僅滿足技術要求,它應該可以正常工作(請參閱下面的“要求”)。
- 該腳本是實驗性的,我嘗試設置
trap
s,以保持退出狀態並以理智(?)的方式在退出時進行清理。我不確定我是否成功了。- 該腳本從未打算萬無一失。將其視為概念證明。
用法
像這樣使用
sshe
:sshe … [user@]hostname command
where
…
表示如果執行檔是ssh
. 沒有必要把-t
nor-tt
(nor-T
) 放在這裡。該腳本假定您希望 tty 在遠端端(否則只需使用ssh
)。該腳本期望本地 stdin、stdout、stderr 中的至少一個被重定向離開本地 tty。ssh -t
如果一切都連接到本地 tty ,腳本將回退到。重要的事情:
command
是您要在伺服器上執行的 shell 程式碼。它必須是單個參數,是 的最後一個參數sshe
。不能省略。hostname
或user@hostname
必須是倒數第二個參數。不能省略。在內部,腳本需要知道
command
在前面添加一些程式碼。它需要知道[user@]hostname
,因為它使用了兩次。該腳本僅分別選擇最後一個和倒數第二個參數,因此存在上述限制。並非每個有效
ssh
的呼叫都可以sshe
通過替換ssh
為sshe
. 但我相信任何ssh
執行程式碼的有效呼叫(而不是生成互動式 shell)都可以重新排列為有效sshe
命令。例子:ssh user@server -p 1234 echo foo
應重新排列為:
sshe -p 1234 user@server 'echo foo'
sshe
(除非你在這種情況下真的不需要;這只是正確語法的一個例子)。如果你使用sshe user@server -p 1234 echo foo
了,那麼腳本將echo
作為伺服器和foo
命令,因為它不會像那樣解析它的參數ssh
。下面有例子。
要求,可移植性問題
當地要求(在哪裡
sshe
執行):
/dev/$(ps -p "$$" -o tty=)
假定為控制終端的“真實姓名”。比較這個問題。mktemp -d
.ssh
支持-M
和-S
;該腳本創建主從連接。遠端要求(在伺服器上):
- SSH 伺服器能夠處理主從連接。
/proc
偽文件系統。- 能夠使用
/proc/nnnn/fd/N
屬於同一使用者的另一個程序。- 符合 POSIX 標準的外殼。
- 靜默啟動腳本(比較SCP 在中不起作用
echo``.bashrc
,sshe
情況類似)。在我的測試中,我成功地從 Kubuntu (18.04.5 LTS) 連接到各種 Debian 或 Debian 衍生伺服器。我
ssh
和sshd
來自 OpenSSH。手術
sshe
(除非它決定回退到ssh
或ssh -t
)執行ssh
兩次:
ssh -M … -T …
是一個不在遠端端分配 tty 的主連接。它在那裡執行的 shell 程式碼通過 stdout 和exec
s 向長期執行sleep
(大約 68 年)報告其 PID。此程序的標准文件描述符將被另一個程序使用。從主設備報告的 PID
ssh
由 獲取read
。在此之後,master 的 stdoutssh
將進入後台cat
,其唯一目的是將其中繼到sshe
. 2. 後來ssh … -t …
是一個從屬連接,它確實在遠端端分配 tty。已經從主連接知道遠端 PID,它設置重定向,因此提供給sshe
as 的程式碼command
可以在遠端端使用單獨的 stdin、stdout、stderr(通過主ssh
連接)和 tty(通過從ssh
連接)。從站ssh
不使用原始的標準輸入或標準輸出sshe
,而是使用本地的/dev/tty
。這個想法類似於這個答案(已經在問題中連結到)。連結答案中的程式碼執行
ssh
(隱式ssh -T
)兩次以提供額外的描述符。我的腳本執行ssh -T
並ssh -t
提供標準描述符和 tty。並且它使用 的主從功能ssh
,因此它只進行一次身份驗證(例如要求輸入密碼)。如果本地 stdin、stdout、stderr 都不是本地 tty,那麼這就是數據流動的方式:
- 本地標準輸入到 master
ssh
,沒有其他本地程序從腳本的標準輸入中讀取。通過從(遠端)讀取/proc/nnnn/fd/0
遠端程序可以訪問本地標準輸入。從ssh
連接預先重定向到command
,因此遠端端的 shell/proc/nnnn/fd/0
用作其標準輸入。- 類似地,遠端端的 shell
/proc/nnnn/fd/1
用作其標準輸出。無論去那裡,都會從當地的主人那裡出來ssh
。這是在主機ssh
從它執行的(遠端)shell 程式碼中檢索到正確的 PID 之後。PID 被消耗,隨後的任何數據都通過後台read
進入原始標準輸出。sshe``cat
- 類似地,遠端端的 shell
/proc/nnnn/fd/2
用作它的標準錯誤。流將直接從本地 masterssh
到sshe
. 腳本生成的一些本地程序使用腳本的 stderr 作為它們的 stderr,所以如果你這樣做,sshe … 2>error.log
那麼日誌也會包含它們的錯誤消息。特別期待Shared connection to server closed.
。這類似於ssh -T … 2>error.log
日誌從遠端命令和ssh
自身收集消息的位置。我認為可以sshe
通過與另一個 stdout 關聯的通道從遠端命令傳遞 stderr的變體ssh
;在這種情況下,可以將遠端 stderr 與本地工俱生成的診斷消息區分開來。腳本沒有這樣做。- 本地 tty 可供 master 使用
ssh
(如果它需要詢問密碼),然後可供 slave 使用ssh
。(坦率地說,腳本使用的更多本地工具可以訪問 local/dev/tty
,他們只是不使用它。)從站ssh -t
用作/dev/tty
它的標準輸入和標準輸出。這樣,/dev/tty
儘管有其他重定向(例如ssh -t
在沒有重定向的終端中執行),它仍然連接本地和遠端。從它們讀取的遠端程序/dev/tty
將獲得本地從屬ssh
從本地讀取的內容/dev/tty
。寫入它們的遠端程序/dev/tty
將使本地從屬ssh
寫入本地/dev/tty
。如果本地 stdin、stdout 或 stderr 是本地 tty,則其在遠端端的對應對應項(用於
command
從 slave 遠端執行ssh
)將不會被重定向到/proc/nnnn/fd/N
,它將保持與遠端 tty 的連接。無論哪種方式,它都會到達本地 tty。關鍵是它不應該繞過遠端tty。稍後將清楚其原因。幾乎不需要
sshe
工作的本地和遠端重定向。這是因為我的其他實驗性事情。我決定保留額外的重定向,以防它們對鞋底來說sshe
比我記得的更重要。障礙和注意事項
整個概念並不像看起來那麼容易。tty 可以處理您輸入的內容(例如翻譯
^M
成^J
)和將要列印的內容(例如,如果我cat
的文件以 *nix 行結尾到終端,則每個換行符都會像carriage_return + newline 一樣工作)。呼叫stty -a
以查看大量設置。這就是為什麼在處理任意二進制數據時不需要 tty。而且你在互動時確實想要它。
程序可以配置 tty 以滿足它們的需要。見生與熟。
當您
ssh
以某種方式在伺服器上分配 tty 時,那裡的程序會將其視為它們的 tty。如果他們需要配置他們的 tty,他們將配置他們在伺服器上看到的 tty。他們沒有辦法直接配置本地 ttyssh
。所有(?)“烹飪”由遠端 tty 完成並ssh
配置本地 tty,因此它不會干擾。這就是
sshe
重定向最終應該連接到本地 tty 的遠端描述符時不應繞過遠端 tty 的原因。如果遠端 tty 被繞過,那麼將沒有實體來“烹飪”流。通過將 stdin、stdout 或 stderr 連接sshe
到本地 tty,您表明您希望它“熟”。這sshe
類似於ssh … command
在互動式 shell 中執行,即所有標準流都由本地 tty “烹製”的情況(注意這ssh
很像ssh -T
,它不會將本地終端置於“原始”模式)。所以
sshe
“烹飪”你顯然想要“烹飪”的東西。當您執行以下操作時會出現問題:sshe … command | whatever
數據將從遠端
command
流向本地whatever
,而不會被“煮熟”(正如人們所期望的那樣ssh … command | whatever
),但 的輸出whatever
不會在本地“煮熟”。sshe
可以重新配置本地 tty 以便它“烹飪”,但如果command
碰巧列印到它的 tty(即到可能或不“烹飪”的遠端 tty,取決於其設置),那麼本地 tty 不應該“烹飪”。
sshe
不試圖解決這個問題。它基本上旨在支持最終輸出到終端以外的其他地方的情況(例如,到正常文件或塊設備)。下面的程式碼在這個問題上更好:sshe … command | whatever >some_file
儘管 stderr from
whatever
不會被“煮熟”。預計診斷消息看起來很奇怪。請注意,您可以將它們重定向到一個文件(或另一個將“烹飪”它們的本地 tty)。輸入端的情況更糟。如果另一個本地程序嘗試從本地 tty 讀取,它不僅會讀取原始數據,還會與
sshe
輸入競爭。這是兩個程序從同一個終端讀取的普遍問題。總結一下:建構一個本地命令(管道),所以只有(單個)
sshe
想從本地 tty 讀取;不要讓sshe
列印到本地 tty 以外的工具,除非你能忍受“原始”輸出。我開發
sshe
能夠傳遞或處理二進制數據。在我的情況下,幾乎不需要從本地 tty 讀取數據或將數據寫入本地 tty。我可以忍受來自本地工具的診斷資訊還不夠“熟”。作為回報sshe
,我可以sudo
像使用本地一樣使用遠端。例子
讀取或寫入需要
sudo
訪問的遠端塊設備。
- 閱讀:
sshe user@server 'sudo cat /dev/sdx1' >local_file # or sshe user@server 'sudo pv /dev/sdx1' >local_file
- 寫作:
<local_file sshe user@server 'sudo tee /dev/sdx1 >/dev/null'
在我的測試中,本地
pv
顯然不介意本地 tty 是“原始的”;或者更確切地說,它施加的配置和配置sshe
施加的配置並不(?)矛盾,並且(?)哪個工具首先配置本地 tty 並不重要。所以這似乎有效:pv local_file | sshe user@server 'sudo tee /dev/sdx1 >/dev/null'
請注意,如果設置
pv
受到干擾sshe
,那麼您可能無法向遙控器提供密碼sudo
。如果設置sshe
受到干擾pv
,那麼pv
列印到終端的內容可能看起來很混亂。即使在這些假設的情況下,內容也會逐字逐句local_file
地到達遠端。/dev/sdx1
本地
sudo
和遠端sudo
一起。如果本地
sudo
要詢問您的密碼,sudo … | sshe … 'sudo …'
或者sshe … 'sudo …' | sudo …
不是一個好主意,因為本地sudo
和sshe
將同時從本地 tty 讀取。完全本地sudo … | sudo …
工作,因為sudo
實現了鎖定機制,所以兩個 localsudo
不會同時與同一個終端互動。這不適用於本地和遠端sudo
的混合。希望您的本地
sudo
允許 timeout。如果是這樣,請事先呼叫sudo -v
以提供本地密碼(如果需要)而不受干擾;然後用管道:
- 從遠端設備複製到本地設備:
sudo -v # input local password if needed sshe user@server 'sudo cat /dev/sdx1' | sudo tee /dev/sdy1 >/dev/null
從本地設備複製到遠端設備:
do -v # input local password if needed do cat /dev/sdy1 | sshe user@server 'sudo tee /dev/sdx1 >/dev/null'
* 正是問題所要求的。 與`sudo`:
t;binary_input sshe user@server 'sudo tool' >binary_output 2>error.log
或更一般地說:
t;binary_input sshe user@server 'requires-tty' >binary_output 2>error.log
--- ### 最後說明 我曾經認為將 stdin 隧道傳輸到 stdin、stdout 到 stdout、stderr 到 stderr*和* `/dev/tty`to`/dev/tty`是微不足道的。我曾經想知道為什麼`ssh`不提供一個選項(類似於`-t`)來做到這一點。現在我知道這不是那麼簡單;我懷疑*也許*我仍然缺少一些東西。