【问题标题】:Pseudo-terminal will not be allocated because stdin is not a terminal不会分配伪终端,因为 stdin 不是终端
【发布时间】:2011-10-30 05:00:57
【问题描述】:

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

脚本永远挂起。

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

【问题讨论】:

  • 您可以像ssh user@server /bin/bash &lt;&lt;EOT…一样简单地指定要使用的终端
  • @Buzut:您的意思可能是 shell,但是,是的,明确指定 /bin/bash 是避免该问题的一种方法。
  • @mklement0 确实,这就是我的意思。感谢您纠正该问题;)

标签: linux bash shell ssh


【解决方案1】:

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

tl;博士:

  • 请使用命令行参数传递要运行的命令:
    ssh jdoe@server '...'

    • '...' 字符串可以跨越多行,因此即使不使用此处文档,您也可以保持代码的可读性:
      ssh jdoe@server ' ... '
  • 不要通过 stdin 传递命令,就像使用 here-document:
    ssh jdoe@server &lt;&lt;'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 &gt;&amp;2' # OK - stdout and stderr separate

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

在没有这个参数的情况下,ssh 创建一个 interactive shell - 包括当你通过 stdin 发送命令时,这就是麻烦开始了:

  • 对于交互式 shell,ssh 通常默认分配一个 pty(伪终端),except 如果它的标准输入没有连接到(真实)终端.

    • 通过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 上,上述命令的明显等效项,通过标准输入提供命令并使用 -ttnot 是否正常工作;响应 read 提示后会话卡住:
        ssh -tt jdoe@server &lt;&lt;&lt;'read -p "Enter something: "; echo "Entered: [$REPLY]"'


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

在紧要关头,使用-T,并通过stdin 提供命令,尾随exit 命令,但请注意,如果您还需要交互功能,请使用-tt 代替的-T 可能不起作用。

【讨论】:

  • 这很好用,-tt 方式是让终端显示通过 ssh 发送的每个命令。
  • "将 -t 选项加倍以强制创建 pty:ssh -t -t ... 或 ssh -tt ... 表明您真的,真的是认真的。" - 哈,哈 - 谢谢,这很有趣,但也很严肃 - 我无法理解双 t 我所知道的是,如果我输入一个 t 它不会工作
  • 我无法让进程中止,当它通过将命令重定向到 stdin 启动时远程启动,但将命令创建为多行字符串,然后将该值作为最后一个参数传递给ssh 恢复了正常/预期的行为。感谢您的建议和澄清。
  • 很高兴听到这个消息,@WeakPointer。
【解决方案2】:

还有来自manual的选项-T

禁用伪终端分配

【讨论】:

  • 这个答案需要更多的分数——它是正确的答案,而且不像 zanco 的答案太长,“哦,无论如何用 -t -t 分配一个 TTY”得到 107 分,当你可以完全跳过它
  • 刚刚投了赞成票;它对我来说确实比 -t -t 效果更好
  • '-t -t' 有效,但是使用 '-T' 我得到 'sudo: sorry, you must have a tty to run sudo'
  • @nhed:-tt 选项似乎最适合我们这些真正想要 TTY 并收到 OP 错误消息的人。如果您不需要 TTY,这个-T 答案会更好。
  • @nhed - 当然 - 但我确信像我这样的其他人是通过谷歌搜索错误消息中的错误来到这里的。明确其他谷歌员工应该如何在 2 个相互竞争的答案之间进行选择似乎并非不合理。或者可能不是。我不知道
【解决方案3】:

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

另请参阅: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.

【讨论】:

  • 我在此处运行的脚本中遇到了类似的问题。我添加了 -t -t 但现在我收到了一个新错误。 “tcgetattr:设备的 ioctl 不合适”
  • 为什么是ssh -t -t 而不是ssh -tt?有我不知道的区别吗?
  • @MasterZ 这里也一样。很高兴得到这个Inappropriate IOCtl for device的答案
  • @Jack -tt-t -t 是等价的;单独指定 args 或将 args 组合在一起成为个人风格/偏好的问题,归根结底,无论哪种方式都有有效的论据。但实际上,这只是个人喜好。
  • “即使ssh没有本地tty”是什么意思?
【解决方案4】:

根据zanco's answer,您没有向ssh 提供远程命令,因为shell 是如何解析命令行的。要解决此问题,请更改 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,关闭局部变量插值以便命令文本将按原样传递给@ 987654332@.

如果你是管道爱好者,可以将上面的内容改写如下:

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 命令,后跟要传递给 ssh 命令的参数 user@server

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

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

  4. cat 是一个输出任何文件内容的命令。 cat 的输出将被传递回捕获的插值字符串

  5. &lt;&lt; 开始一个 bash heredoc

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

  7. &lt;&lt;'EOT'&lt;newline&gt;EOT&lt;newline&gt; 之间遇到的任何内容都将附加到 nowdoc 输出中

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

  9. )结束要执行的命令

  10. " 结束捕获插值字符串。内插字符串的内容将作为单个命令行参数传回sshssh 将解释为远程命令以执行user@server

如果您需要避免使用cat 等外部工具,并且不介意使用两条语句而不是一条语句,请使用带有heredoc 的内置read 来生成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 一年半后! :) 非常清楚的解释。干得好
  • 对我来说(而且我绝不是“DevOps”人),这就是有效的方法(我正在使用 Jenkins)。我也尝试了“-t -t”和“-T”建议,但遇到了开放流问题和非执行问题。
  • 2年后,真的很有帮助
  • +1 很棒的解释。但是如果你让最后一个代码 sn-p (IFS='' *) 成为执行远程命令的函数,你将如何将 $1 传递给 IFS='' *?它的讲解员似乎完全认出了 $1 的内容。
  • @mklement0 当然,您可以根据需要转义字符串,但是谁愿意不得不修改脚本语句的麻烦,您可能只是从另一个 shell 脚本或 stackoverflow 复制和粘贴?此外,cat 解决方案的优雅之处在于它是在单个(尽管是复合)语句中完成的,并且不会导致临时变量污染 shell 环境。
【解决方案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

额外

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

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

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

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

【讨论】:

    【解决方案6】:

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

    解决方案

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

    【讨论】:

      【解决方案7】:

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

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

      如果&lt;&lt;EOF 没有转义或单引号(即&lt;&lt;\EOT&lt;&lt;'EOT'),则here 文档中的变量将在本地shell 执行ssh ... 之前展开。效果是此处文档中的变量将保持为空,因为它们仅在远程 shell 中定义。

      因此,如果 $REL_DIR 应该可以被本地 shell 访问并在远程 shell 中定义,则 $REL_DIR 必须在 ssh 命令之前在此处文档之外定义(版本 1 下面);或者,如果使用&lt;&lt;\EOT&lt;&lt;'EOT',则ssh 命令的输出可以分配给REL_DIR,如果ssh 命令到stdout 的唯一输出是由echo "$REL_DIR" 在转义/此处单引号引用的文档(版本 2 如下)。

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

      最后但并非最不重要的一点是,检查远程主机上的目录是否已成功创建也不错(请参阅:check if file exists on remote host with 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"
      

      【讨论】:

      • 感谢您非常详细地解释了这个错误的含义以及为什么在使用 heredoc 时会出现它,这有助于我真正理解它,而不仅仅是解决它。
      【解决方案8】:

      ssh -t foobar@localhost yourscript.pl

      【讨论】:

      • OP 也使用了-t,但在他们的特定场景中这还不够,这促使问题开始。
      【解决方案9】:

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

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

      解决方案:原来我没有安装了 openssh 客户端程序和实用程序。因为 cygwin 使用的是 ssh 的 Windows 实现,而不是 cygwin 版本。解决方案是安装 openssh cygwin 包。

      【讨论】:

      • 对我来说,这是 PATH 中的另一个 ssh 实现:$ which ssh/cygdrive/c/Program Files (x86)/Git/bin/ssh
      • 并安装openssh这可能是Windows的方式:superuser.com/a/301026/260710
      • 这个解决方案对我有用。安装openssh后,问题就消失了。
      【解决方案10】:

      我不知道挂起的原因是什么,但是将命令重定向(或管道)到交互式 ssh 通常会导致问题。使用 command-to-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 '-delimited 多行命令行参数)。

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

      你到底想用-t 达到什么目的?

      【讨论】:

      • -t 选项试图修复 Psuedo 终端问题(它不起作用)。我试过你的解决方案,它摆脱了伪终端的东西,但现在它只是挂起......
      • @Henning:您能否详细说明或提供有关重定向或管道进入交互式 ssh 的缺点的链接?
      • 有点晚了,但是:这里不需要伪终端,所以请改用选项-T
      猜你喜欢
      • 1970-01-01
      • 2018-07-16
      • 2014-03-10
      • 1970-01-01
      • 1970-01-01
      • 2019-08-13
      • 2011-06-07
      • 2017-10-31
      相关资源
      最近更新 更多