伪terminal不会被分配,因为stdin不是terminal

我正在尝试编写一个在远程服务器上创build一些目录的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 -t -t (或简称ssh -tt )即使stdin不是terminal,也要强制伪tty分配。

另请参阅: 终止由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, eg when implementing menu services. Multiple -t options force tty allocation, even if ssh has no local tty. 

也从手动选项-T

禁用伪tty分配

根据zanco的回答 ,你没有提供一个远程命令ssh ,给定了shell如何parsing命令行。 为了解决这个问题,改变你的ssh命令调用的语法,以便远程命令由一个语法正确的多行string组成。

有很多可以使用的语法。 例如,因为命令可以传送到bashsh ,也可能是其他shell,所以最简单的解决scheme是将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,closures局部variables插值,以便将命令文本原样传递给ssh

如果你是pipe道迷,你可以重写上面的内容:

 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警告同样适用于上述情况。

另一个有效的方法是将多行远程命令作为单个string传递,使用多层bashvariables插值,如下所示:

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

上面的解决scheme以下面的方式解决了这个问题:

  1. ssh user@server被bashparsing,并被解释为ssh命令,后跟一个参数user@server以传递给ssh命令

  2. "开始一个内插的string,当完成时,它将包含一个parameter passing给ssh命令,在这种情况下,这个命令将被ssh解释为user@server执行的远程命令

  3. $(开始一个要执行的命令,输出被周围的内插string捕获

  4. cat是输出任何文件的内容的命令。 cat的输出将被传回到捕捉插入的string中

  5. <<开始了一个bash heredoc

  6. 'EOT'指定heredoc的名字是EOT。 EOT周围的单引号指定heredoc应该被parsing为一个nowdoc ,这是一个特殊的heredocforms,其中内容不被bash内插,而是以字面格式传递

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

  8. EOT终止nowdoc,导致一个nowdoc临时文件被创build并传递callback用cat命令。 cat输出nowdoc并将输出传递回捕获插入的string

  9. )结束要执行的命令

  10. "结束捕获插入的string,插入的string的内容将作为单个命令行parameter passing回ssh ,该ssh将解释为要作为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}" 

我添加了这个答案,因为它解决了我有相同的错误信息的相关问题。

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

解决scheme :事实certificate,我没有安装openssh客户端程序和实用程序。 因为cygwin使用的是ssh的Windows实现,而不是cygwin的版本。 解决scheme是安装openssh cygwin软件包。

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

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

如果<<EOF未被转义或单引号(即<<\EOT<<'EOT' ),则在执行ssh ...之前,本文档中的variables将被本地shell展开ssh ... 其效果是这里的文档中的variables将保持为空,因为它们只在远程shell中定义。

因此,如果$REL_DIR应该可以被本地shell访问并在远程shell中定义, $REL_DIR必须在ssh命令(下面的版本1 )之前的here文档之外定义。 或者,如果使用<<\EOT<<'EOT' ,如果ssh命令的唯一输出通过echo "$REL_DIR" / echo "$REL_DIR"echo "$REL_DIR"生成, ssh命令的输出可以分配给REL_DIR在这里引用文件(下面的版本2 )。

第三个选项是将这里的文档存储在一个variables中,然后把这个variables作为命令parameter passing给ssh -t user@server "$heredoc" (下面的版本3 )。

最后但并非最不重要的是,检查远程主机上的目录是否已成功创build(请参阅: 检查远程主机上是否存在使用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" 

我不知道挂起来自哪里,但redirect(或pipe道)命令成为一个交互式的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' 

(所有这一切都在一个巨大的'多行”命令行参数中)。

这个伪terminal消息是因为你的-t要求ssh试图让它在远程机器上运行的环境看起来像一个运行在那里的程序的实际terminal。 你的ssh客户端拒绝这么做,因为它自己的标准input不是terminal,所以无法从远程机器上把特殊的terminalAPI传递给本地的实际terminal。

无论如何,你想用什么来实现?

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

TL;博士:

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

    • '...'string可以跨越多行,所以即使不使用here-document,也可以保持代码的可读性:
      ssh jdoe@server ' ... '
  • 不要通过stdin传递命令 ,就像在使用here-document时一样 :
    ssh jdoe@server <<'EOF' # Do NOT do this ... EOF

将命令作为parameter passing,按原样运行,并且:

  • 伪terminal的问题甚至不会出现。
  • 您不需要在命令结束时使用exit语句 ,因为在处理完命令后会话将自动退出。

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


可选的背景信息:

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

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

  • 如果您的命令需要用户交互 (例如响应交互式提示),则可以使用-t选项明确请求创build一个能够与远程会话进行交互的伪terminalpty(pseudo-tty) ; 例如:

    • 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会创build一个交互式的 shell – 包括当你通过stdin发送命令的时候,

  • 对于交互式 shell,默认情况下, ssh通常分配一个pty(伪terminal), 除非 stdin没有连接到(真实)terminal。

    • 通过stdin发送命令意味着ssh的stdin不再连接到terminal,所以没有创buildpty, ssh会相应地提醒
      Pseudo-terminal will not be allocated because stdin is not a terminal.

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

      • 有点奇怪的是,你必须加倍 -t选项来强制创build一个pty: ssh -t -t ...ssh -tt ...显示你真的,真的是这个意思

      • 要求这个非常慎重的步骤的基本原理可能事情可能无法按预期工作 。 例如,在macOS 10.12上,通过stdin和使用-tt提供命令的上述命令的表面等同物不能正常工作; 响应read提示后会话卡住:
        ssh -tt jdoe@server <<<'read -p "Enter something: "; echo "Entered: [$REPLY]"'


如果想要作为parameter passing的命令使命令行太长(如果其长度接近getconf ARG_MAX ,请参阅本文 ),请考虑将代码以脚本的forms复制到远程系统首先(使用,例如, scp ),然后发送命令来执行该脚本。

在一个捏,使用-T ,并通过标准input提供命令,尾随exit命令,但请注意,如果您还需要交互function,使用-tt代替-T可能无法正常工作。

读了很多这些答案后,我想我会分享我的解决scheme。 我所添加的是/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 

额外

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

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

如果您还想将整个会话logging到文件logfile.log

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

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

  1. Mx自定义variables(然后按Enter键)
  2. tramp-default-method(然后再次按Enter)
  3. 在文本字段上放置plink,然后应用并保存缓冲区
  4. 每当我尝试访问远程服务器,我现在使用Cxf / user @ host:然后input密码。 这个连接现在可以在Windows上的Emacs下正确地build立到我的远程服务器上。

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