由于stdin不是终端,因此不会分配伪终端

时间:2011-08-18 22:38:40

标签: linux bash shell ssh

我正在尝试编写一个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.

脚本永远挂起。

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

10 个答案:

答案 0 :(得分:426)

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

另请参阅:Terminating SSH session executed by bash script

来自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.

答案 1 :(得分:153)

同时使用manual

中的选项-T
  

禁用伪tty分配

答案 2 :(得分:76)

Per zanco's answer,鉴于shell如何解析命令行,您不会向ssh提供远程命令。要解决此问题,请更改ssh命令调用的语法,以便远程命令由语法正确的多行字符串组成。

可以使用多种语法。例如,由于命令可以通过管道输入bashsh,也可能是其他shell,最简单的解决方案是将ssh shell调用与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命令< / p>

  2. "开始一个插值字符串,完成后将包含一个要传递给ssh命令的参数,在这种情况下,ssh将解释为user@server是作为$(

  3. 执行的远程命令
  4. cat开始执行命令,输出由周围的插值字符串捕获

  5. cat是一个输出后续文件内容的命令。 <<的输出将被传递回捕获插值字符串

  6. 'EOT'开始bash heredoc

  7. '指定heredoc的名称是EOT。围绕EOT的单引号<<'EOT'指定heredoc应该被解析为 nowdoc ,这是一种特殊形式的heredoc,其中的内容不会被bash插值,而是传递给它用文字格式

  8. <newline>EOT<newline>EOT之间遇到的任何内容都会附加到nowdoc输出

  9. cat终止nowdoc,导致创建nowdoc临时文件并将其传递回调用cat命令。 )输出nowdoc并将输出传递回捕获插值字符串

  10. "总结要执行的命令

  11. ssh结束捕获插值字符串。插值字符串的内容将作为单个命令行参数传递回sshuser@server将解释为要执行的远程命令cat

  12. 如果您需要避免使用read之类的外部工具,并且不介意使用两个语句而不是一个语句,请使用带有heredoc的内置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}" 来生成SSH命令:< / p>

    {{1}}

答案 3 :(得分:53)

我正在添加这个答案,因为它解决了我遇到的相关问题,并出现了相同的错误消息。

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

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

答案 4 :(得分:30)

警告消息Pseudo-terminal will not be allocated because stdin is not a terminal.是由于当从此处文档重定向stdin时没有为ssh指定任何命令。 由于缺少指定的命令作为参数ssh首先需要一个交互式登录会话(这需要在远程主机上分配一个pty)但是必须意识到它的本地stdin不是tty / pty 。从here文档重定向ssh的stdin通常需要将一个命令(例如/bin/sh)指定为ssh的参数 - 在这种情况下,不会分配pty默认情况下是远程主机。

由于ssh没有要求存在tty / pty的命令(例如vimtop-t切换到{ {1}}是多余的。 只需使用sshssh -T user@server <<EOT ...,警告就会消失。

如果ssh user@server /bin/bash <<EOT ...没有转义或单引号(即<<EOF<<\EOT),本文档中的变量将在执行前由本地shell进行扩展{{1} }。结果是here文档中的变量将保持为空,因为它们仅在远程shell中定义。

因此,如果本地shell可以访问<<'EOT'并在远程shell中定义,则必须在ssh ...命令之前在here文档之外定义$REL_DIR版本1 以下);或者,如果使用$REL_DIRssh<<\EOT命令的输出可以分配给<<'EOT',如果ssh命令的唯一输出到stdout由REL_DIR在转义/单引号文档(版本2 )中生成。

第三个选项是将here文档存储在变量中,然后将此变量作为命令参数传递给ssh(下面的版本3 )。

最后但并非最不重要的是,检查远程主机上的目录是否已成功创建(参见:check if file exists on remote host with ssh)。

echo "$REL_DIR"

答案 5 :(得分:23)

所有相关信息都在现有答案中,但让我尝试实用摘要

<强> TL; DR:

  • DO使用命令行参数传递命令
    ssh jdoe@server '...'

    • '...'字符串可以跨越多行,因此即使不使用here-document也可以保持代码的可读性:
      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选项启用与远程会话的交互; e.g:

    • 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会创建交互式 shell - 包括通过 stdin 发送命令时是麻烦开始的地方:

  • 对于交互式 shell,ssh通常默认分配pty(伪终端),外如果其stdin未连接到一个(真实的)终端。

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

    • 即使-t选项的明确目的是请求创建pty, in in这种情况 :你会得到同样的警告。

      • 有点奇怪的是,你必须 加倍 -t选项来强制创建一个pty:ssh -t -t ...或{{1 }}表明你真的,真的意思是

      • 或许要求这个非常慎重的步骤的理由是事情可能无法按预期工作。例如,在macOS 10.12上,上述命令的明显等价物,通过stdin提供命令并使用ssh -tt ...正常工作;会话在响应-tt提示后卡住:
        read

如果您希望作为参数传递的命令使命令行对您的系统来说太长(如果其长度接近ssh -tt jdoe@server <<<'read -p "Enter something: "; echo "Entered: [$REPLY]"' - 请参阅this article),请考虑将代码复制到首先是脚本形式的远程系统(使用例如getconf ARG_MAX),然后发送命令来执行该脚本。

在紧要关头,使用scp,并通过 stdin 提供命令,并使用尾随的-T命令,但请注意,如果您还需要交互式功能,请使用{ {1}}代替exit可能无效。

答案 6 :(得分:21)

我不知道挂起来自何处,但将重定向(或管道)命令重定向到交互式ssh通常是问题的处方。使用命令到run-as-a-last-argument样式并在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'

(All in one giant ' - 分隔的多行命令行参数)。

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

无论如何,你想用-t做什么?

答案 7 :(得分:5)

在阅读了很多这些答案后,我想我会分享我最终的解决方案。我在heredoc之前添加的只是/bin/bash,它不再给出错误。

使用此:

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

<强> EXTRA

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

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

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

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

答案 8 :(得分:0)

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

<强>解决方案

  1. M-x customize-variable(然后按Enter键)
  2. tramp-default-method(然后再按Enter键)
  3. 在文本字段中输入plink,然后应用并保存缓冲区
  4. 每当我尝试访问远程服务器时,我现在使用C-x-f / user @ host:然后输入密码。现在可以在Windows上的Emacs下正确连接到我的远程服务器。

答案 9 :(得分:-2)

ssh -t foobar @ localhost yourscript.pl