因为stdin不是终端,所以不会分配伪终端


345

我试图编写一个Shell脚本,该脚本在远程服务器上创建一些目录,然后使用scp将文件从本地计算机复制到远程服务器上。这是我到目前为止的内容:

ssh -t user@server<<EOT
DEP_ROOT='/home/matthewr/releases'
datestamp=$(date +%Y%m%d%H%M%S)
REL_DIR=$DEP_ROOT"/"$datestamp
if [ ! -d "$DEP_ROOT" ]; then
    echo "creating the root directory"
    mkdir $DEP_ROOT
fi
mkdir $REL_DIR
exit
EOT

scp ./dir1 user@server:$REL_DIR
scp ./dir2 user@server:$REL_DIR

每当我运行它时,我都会收到以下消息:

Pseudo-terminal will not be allocated because stdin is not a terminal.

脚本将永远挂起。

我的公钥在服务器上是受信任的,我可以在脚本之外运行所有命令。有任何想法吗?


4
您只需指定要使用的终端即可,例如ssh user@server /bin/bash <<EOT…
Buzut 2016年

3
@Buzut:您可能是说shell,但是,是的,/bin/bash显式指定是避免该问题的一种方法。
mklement0

1
@ mklement0确实是我的意思。Thx来纠正这个问题;)
Buzut

Answers:


512

尝试ssh -t -t(或ssh -tt简称)强制伪tty分配,即使stdin不是终端。

另请参阅:终止bash脚本执行的SSH会话

从ssh联机帮助页:

-T      Disable pseudo-tty allocation.

-t      Force pseudo-tty allocation.  This can be used to execute arbitrary 
        screen-based programs on a remote machine, which can be very useful,
        e.g. when implementing menu services.  Multiple -t options force tty
        allocation, even if ssh has no local tty.

21
我在此处运行的脚本中存在类似问题。我添加了-t -t,但是现在出现了新错误。“ tcgetattr:设备的不适当的ioctl”
MasterZ

5
为什么ssh -t -tssh -tt呢?我不知道有什么区别吗?
2014年

3
@MasterZ在这里也是一样。得到这个答案很高兴Inappropriate IOCtl for device
krb686

11
@Jack -tt-t -t等价;分别指定args或一起指定args成为个人风格/偏好的问题,当涉及到它时,有两种方法都可以使用有效参数。但实际上,这只是个人喜好。
JDS 2015年

5
当它说“即使ssh没有本地tty时”又是什么意思?
CMCDragonkai '16

192

也可以-T手册中选择

禁用伪tty分配


11
这个答案需要更多的分数-正确的答案,并且不像zanco的答案那样长,它的荒谬之处是“哦,无论如何都要用-t -t分配一个TTY”获得107分数,而您可以完全跳过它
nhed

2
刚刚投票;它比-t -t工作对我来说更好
文森特Hiribarren

15
“-t -t的作品,但与“-T”我得到:“须藤对不起,你必须有一个tty运行sudo的”
伊万巴拉瑟夫

3
@nhed:该-tt选项最适合那些实际需要TTY并收到OP错误消息的用户。-T如果您不需要TTY,则此答案更好。
ErichBSchulz

3
@nhed-可以-但是我敢肯定像我这样的人是从Google搜索错误消息中的错误得到的。弄清楚其他Google员工应该如何在2个相互竞争的答案之间进行选择,似乎并非没有道理。或者可能不是。我不知道
ErichBSchulz

90

根据zanco的回答ssh鉴于外壳如何解析命令行,您没有向提供远程命令。要解决此问题,请更改ssh命令调用的语法,以使远程命令由语法正确的多行字符串组成。

可以使用多种语法。例如,由于命令可以通过管道传递给bashsh,可能也传递给其他shell,所以最简单的解决方案是将sshshell调用与heredocs 结合在一起:

ssh user@server /bin/bash <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT

需要注意的是执行上面没有 /bin/bash将导致警告Pseudo-terminal will not be allocated because stdin is not a terminal。还要注意,EOT被单引号引起来,因此bash将heredoc识别为nowdoc ,关闭了局部变量插值,以便将命令文本原样传递给。ssh

如果您是管道迷,则可以将以下内容重写为:

cat <<'EOT' | ssh user@server /bin/bash
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT

同样的警告/bin/bash也适用于上述情况。

另一种有效的方法是使用多层bash变量插值,将多行远程命令作为单个字符串传递,如下所示:

ssh user@server "$( cat <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT
)"

上面的解决方案通过以下方式解决了此问题:

  1. ssh user@server由bash解析,并解释为ssh命令,后跟user@server要传递给ssh命令的参数

  2. "开始一个内插字符串,完成后将包含要传递给ssh命令的参数,在这种情况下,该参数将解释ssh为要执行为的远程命令user@server

  3. $( 开始执行命令,输出由周围的内插字符串捕获

  4. cat是用于输出后面任何文件的内容的命令。的输出cat将传递回捕获的内插字符串中

  5. <<开始bash Heredoc

  6. 'EOT'指定heredoc的名称为EOT。'EOT周围的单引号指定将Heredoc解析为nowdoc,这是Heredoc的一种特殊形式,其中内容不会被bash插值,而是以文字格式传递

  7. 即遇到之间的任何内容<<'EOT'<newline>EOT<newline>将附加到所述nowdoc输出

  8. EOT终止nowdoc,导致nowdoc临时文件被创建并传递回调用cat命令。cat输出nowdoc并将输出传递回捕获的内插字符串

  9. ) 总结要执行的命令

  10. "得出捕获的内插字符串。内插字符串的内容将ssh作为单个命令行参数传递回,该命令行参数ssh将解释为以以下方式执行的远程命令:user@server

如果您需要避免使用外部工具(例如)cat,并且不介意使用两个语句代替一个语句,请使用read带有heredoc 的内置语句来生成SSH命令:

IFS='' read -r -d '' SSH_COMMAND <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT

ssh user@server "${SSH_COMMAND}"

一年半后+1!:)真的很清楚的解释。做得好
yaroslavTir 2014年

1
对于我(我绝不是“ DevOps”人),这是行得通的(我正在使用Jenkins)。我也尝试了“ -t -t”和“ -T”建议,但是遇到了开放流问题和非执行问题。
瑞安·克鲁斯

2年后,非常有帮助
jack-nie

+1很棒的解释。但是,如果您将最后一个代码段(IFS =''*)用作执行远程命令的函数,那么如何将$ 1传递给IFS =''*?它似乎完全识别$ 1的内容。
克里斯蒂安·马蒂亚斯·安巴克

1
@ mklement0当然,您可以根据需要对字符串进行转义,但是谁想麻烦您修改脚本语句,而这些脚本语句可能只是从另一个Shell脚本复制或粘贴而来,或者是stackoverflow?同样,cat解决方案的精妙之处在于它是在单个(尽管是复合的)语句中完成的,并且不会导致临时变量污染外壳环境。
Dejay Clayton

63

我添加此答案是因为它通过相同的错误消息解决了我遇到的一个相关问题。

问题:我已经在Windows下安装了cygwin,并收到此错误:Pseudo-terminal will not be allocated because stdin is not a terminal

解决方法:事实证明我没有安装openssh客户端程序和实用程序。因此,cygwin使用的是Windows的ssh实现,而不是cygwin版本。解决方案是安装openssh cygwin软件包。


10
对我来说,这是PATH中的另一个ssh实现:$ which ssh/cygdrive/c/Program Files (x86)/Git/bin/ssh
FelixJongleur42

并安装openssh它可能是Windows的必经之路:superuser.com/a/301026/260710
Andreas Dietrich

这个解决方案对我有用。安装openssh之后,问题就消失了。
jdhao

34

警告消息Pseudo-terminal will not be allocated because stdin is not a terminal.是由于在ssh从此处文档重定向stdin时未指定命令而导致的。由于缺少指定的命令作为参数,因此ssh首先需要进行交互式登录会话(这需要在远程主机上分配pty),但随后必须意识到其本地标准输入不是tty / pty。ssh从here文档重定向stdin通常要求将命令(例如/bin/sh)指定为-的参数ssh,在这种情况下,默认情况下不会在远程主机上分配pty。

由于没有要执行的命令ssh需要tty / pty(例如vimtop)的存在,因此-t切换ssh到此命令是多余的。只需使用ssh -T user@server <<EOT ...ssh user@server /bin/bash <<EOT ...,警告就会消失。

如果<<EOF未进行转义,则在执行此文档之前,将在本地文档中扩展here文档中的单引号(即<<\EOTor <<'EOT')变量ssh ...。结果是,here文档中的变量将保持为空,因为它们仅在远程shell中定义。

因此,如果$REL_DIR既应由本地外壳程序访问又应在远程外壳程序$REL_DIR中定义,则必须在ssh命令(以下版本1)之前在here文档之外定义;或者(如果使用<<\EOT<<'EOT'则),如果通过stdout命令的唯一输出是在此处的转义/单引号文档(下面的版本2)内部生成的sshREL_DIR则可以将命令的输出分配给该ssh命令。echo "$REL_DIR"

第三种选择是将here文档存储在一个变量中,然后将此变量作为命令参数传递给ssh -t user@server "$heredoc"(下面的版本3)。

最后但并非最不重要的一点是,检查远程主机上的目录是否已成功创建(请参见:使用ssh检查文件在远程主机上是否存在)将是一个不错的主意。

# version 1

unset DEP_ROOT REL_DIR
DEP_ROOT='/tmp'
datestamp=$(date +%Y%m%d%H%M%S)
REL_DIR="${DEP_ROOT}/${datestamp}"

ssh localhost /bin/bash <<EOF
if [ ! -d "$DEP_ROOT" ] && [ ! -e "$DEP_ROOT" ]; then
   echo "creating the root directory" 1>&2
   mkdir "$DEP_ROOT"
fi
mkdir "$REL_DIR"
#echo "$REL_DIR"
exit
EOF

scp -r ./dir1 user@server:"$REL_DIR"
scp -r ./dir2 user@server:"$REL_DIR"


# version 2

REL_DIR="$(
ssh localhost /bin/bash <<\EOF
DEP_ROOT='/tmp'
datestamp=$(date +%Y%m%d%H%M%S)
REL_DIR="${DEP_ROOT}/${datestamp}"
if [ ! -d "$DEP_ROOT" ] && [ ! -e "$DEP_ROOT" ]; then
   echo "creating the root directory" 1>&2
   mkdir "$DEP_ROOT"
fi
mkdir "$REL_DIR"
echo "$REL_DIR"
exit
EOF
)"

scp -r ./dir1 user@server:"$REL_DIR"
scp -r ./dir2 user@server:"$REL_DIR"


# version 3

heredoc="$(cat <<'EOF'
# -onlcr: prevent the terminal from converting bare line feeds to carriage return/line feed pairs
stty -echo -onlcr
DEP_ROOT='/tmp'
datestamp="$(date +%Y%m%d%H%M%S)"
REL_DIR="${DEP_ROOT}/${datestamp}"
if [ ! -d "$DEP_ROOT" ] && [ ! -e "$DEP_ROOT" ]; then
   echo "creating the root directory" 1>&2
   mkdir "$DEP_ROOT"
fi
mkdir "$REL_DIR"
echo "$REL_DIR"
stty echo onlcr
exit
EOF
)"

REL_DIR="$(ssh -t localhost "$heredoc")"

scp -r ./dir1 user@server:"$REL_DIR"
scp -r ./dir2 user@server:"$REL_DIR"

5
感谢您就此错误的含义以及使用heredoc时为何出现此错误的原因进行了非常详细的解释,这有助于我真正理解它,而不仅仅是解决它。
2015年

29

所有相关信息都在现有答案中,但让我尝试一个务实的总结

tl; dr:

  • 务必使用命令行参数传递命令以运行
    ssh jdoe@server '...'

    • '...' 字符串可以跨越多行,因此即使不使用此处文档,也可以使代码保持可读性:
      ssh jdoe@server ' ... '
  • 不要使用stdin传递命令,就像使用here-document那样
    ssh jdoe@server <<'EOF' # Do NOT do this ... EOF

将命令作为参数传递按原样工作,并且:

  • 伪终端的问题甚至不会出现。
  • 您无需exit在命令末尾添加任何语句,因为在处理完命令后会话将自动退出。

简而言之:通过stdin传递命令是一种与ssh的设计不一致的机制,会导致必须解决的问题。
如果您想了解更多,请继续阅读。


可选背景信息:

ssh接受在目标服务器上执行的命令的机制是命令行参数:最终操作数(非选项参数)接受包含一个或多个shell命令的字符串。

  • 默认情况下,这些命令在非交互式 shell中无人值守运行,而不使用(伪)终端(-T隐含选项),并且当最后一个命令完成处理时,会话自动结束

  • 如果您的命令需要用户交互(例如响应交互式提示),则可以使用选项显式请求创建pty(pseudo-tty)(伪终端,该伪终端可以与远程会话进行交互-t。例如:

    • ssh -t jdoe@server 'read -p "Enter something: "; echo "Entered: [$REPLY]"'

    • 请注意,交互式read提示仅可在pty下正常工作,因此-t需要该选项。

    • 使用pty有一个明显的副作用:stdout和stderr 结合在一起,并且都通过stdout报告;换句话说:您将失去常规输出与错误输出之间的区别;例如:

      • ssh jdoe@server 'echo out; echo err >&2' # OK - stdout and stderr separate

      • ssh -t jdoe@server 'echo out; echo err >&2' # !! stdout + stderr -> stdout

在没有此参数的情况下,ssh创建一个交互式外壳程序 -包括当您通过stdin发送命令时(这是麻烦开始的地方):

  • 对于交互式 Shell,ssh通常默认情况下会分配pty(伪终端),除非其stdin未连接到(真实)终端。

    • 通过stdin发送命令意味着ssh的stdin不再连接到终端,因此不会创建pty,并相应地ssh 警告
      Pseudo-terminal will not be allocated because stdin is not a terminal.

    • 即使是-t选项,其明确目的是要求建立一个pty的,是足够在这种情况下:你会得到同样的警告。

      • 有点奇怪的是,您然后必须选项加倍-t以强制创建pty:ssh -t -t ...ssh -tt ...表明您确实,确实是故意的

      • 可能需要采取这一非常刻意的步骤的理由可能是,事情可能无法按预期进行。例如,在MacOS 10.12,上述命令的表观当量,从而提供通过stdin的命令,并使用-tt,就不能正常工作; 响应read提示后,会话被卡住:
        ssh -tt jdoe@server <<<'read -p "Enter something: "; echo "Entered: [$REPLY]"'


万一您想作为参数传递的命令使命令行对于系统来说太长(如果其长度接近getconf ARG_MAX-请参见本文),请考虑首先以脚本形式将代码复制到远程系统(使用(例如scp)),然后发送命令执行该脚本。

暂时请使用-T,并通过stdin提供命令,并在其后加上尾随exit命令,但请注意,如果您还需要交互功能,则使用-tt代替-T可能不起作用。


1
这很完美,-tt方式使终端显示通过ssh发送的每个命令。
Mark E

1
“将-t选项加倍以强制创建pty:ssh -t -t ...或ssh -tt ...表明您确实是真的。” -哈哈-感谢它很有趣,但也很严肃-我无法理解我所知道的全部,如果我放进一个t不会
扭曲

22

我不知道挂起来自何处,但是将命令重定向(或管道传递)到交互式ssh通常是解决问题的方法。使用命令运行作为最后一个参数的样式并在ssh命令行上传递脚本更加健壮:

ssh user@server 'DEP_ROOT="/home/matthewr/releases"
datestamp=$(date +%Y%m%d%H%M%S)
REL_DIR=$DEP_ROOT"/"$datestamp
if [ ! -d "$DEP_ROOT" ]; then
    echo "creating the root directory"
    mkdir $DEP_ROOT
fi
mkdir $REL_DIR'

(所有功能'都由巨号分隔的多行命令行参数组成)。

伪终端消息是由于您的原因,-t它要求ssh尝试使其在远程计算机上运行的环境看起来像是在此处运行的程序的实际终端。您的ssh客户端拒绝这样做,因为它自己的标准输入不是终端,因此它无法将特殊的终端API从远程机器向前传递到本地的实际终端。

您到底想达到什么目的-t


1
-t选项是试图解决Psuedo终端问题(不起作用)。我尝试了您的解决方案,它摆脱了伪终端的问题,但现在却挂了……
Matthew

4
@Henning:您能否详细说明或提供有关重定向或通过管道传递到交互式ssh的缺点的链接?
艾萨克·克莱曼

有点晚了,但是:这里您不需要伪终端,所以请使用option -T
弗洛里安·卡斯泰拉内

6

阅读了很多这些答案之后,我想我将分享我得出的解决方案。我添加的所有内容都/bin/bash在Heredoc之前,并且不再显示错误。

用这个:

ssh user@machine /bin/bash <<'ENDSSH'
   hostname
ENDSSH

代替这个(给出错误):

ssh user@machine <<'ENDSSH'
   hostname
ENDSSH

或使用此:

ssh user@machine /bin/bash < run-command.sh

代替这个(给出错误):

ssh user@machine < run-command.sh

额外

如果您仍需要远程交互式提示,例如正在远程运行的脚本提示您输入密码或其他信息,因为以前的解决方案不允许您在提示中键入内容。

ssh -t user@machine "$(<run-command.sh)"

如果您还想将整个会话记录在一个文件中logfile.log

ssh -t user@machine "$(<run-command.sh)" | tee -a logfile.log

0

在Windows下使用emacs 24.5.1通过/ ssh:user @ host连接到某些公司服务器时,我遇到了相同的错误。解决我问题的方法是将“ tramp-default-method”变量设置为“ plink”,并且每当我连接到服务器时,我都会省略ssh协议。您需要安装PuTTY的plink.exe才能运行。

  1. MX自定义变量(然后按Enter)
  2. tramp-default-method(然后再次按Enter)
  3. 在文本字段上,输入plink,然后应用并保存缓冲区
  4. 每当我尝试访问远程服务器时,我现在都使用Cxf / user @ host :,然后输入密码。现在可以在Windows的Emacs下正确建立到我的远程服务器的连接。

-3

ssh -t foob​​ar @ localhost yourscript.pl


2
OP也使用-t,但是在他们的特定情况下还不够,这提示了问题的开始。
mklement0
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.