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

Mat*_*hew 316 linux ssh bash shell

我正在尝试编写一个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
Run Code Online (Sandbox Code Playgroud)

每当我运行它时,我收到此消息:

Pseudo-terminal will not be allocated because stdin is not a terminal.
Run Code Online (Sandbox Code Playgroud)

脚本永远挂起.

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

小智 463

即使stdin不是终端,也要尝试ssh -t -t(或ssh -tt简称)强制伪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,
        e.g. when implementing menu services.  Multiple -t options force tty
        allocation, even if ssh has no local tty.
Run Code Online (Sandbox Code Playgroud)

  • 我在这里运行的脚本中遇到了类似的问题.我添加了-t -t但现在我收到了一个新错误."tcgetattr:设备不适当的ioctl" (20认同)
  • @Jack` -tt`和`-t -t`是等价的; 单独指定args或一起指定arg成为个人风格/偏好的问题,当归结为它时,无论采用哪种方式都有有效的论据.但实际上,这只是个人偏好. (11认同)
  • 为什么`ssh -t -t`而不是`ssh -tt`?我不知道有什么区别吗? (5认同)
  • 当它说"即使ssh没有本地tty"这是什么意思? (5认同)
  • @MasterZ这里也是一样的.很高兴能得到一个答案:"不合适的设备IOCtl" (3认同)
  • @CMCDragonkai:如果你通过_stdin_向`ssh`提供命令,由于输入重定向,它的stdin不再连接到_terminal_,所以在这个意义上"ssh没有本地tty"了.在这种情况下,_single_` -t`不足以分配pty,而必须使用`-t -t`(`-tt`).也就是说,如果您不通过_stdin_提供命令,则可以避免所有这些,并使用命令行_argument_代替. (2认同)
  • 所以`-t`和`-t -t`都是关于在连接的远程端分配pty的.问题是本地端stdin不是pty,所以你可以用`-T`完全禁用它,或者强制远端pty.选择取决于您在远程端运行的命令是否依赖于是否存在tty.但令人费解的是,为什么ssh正如问题所声称的那样悬挂. (2认同)

小智 176

也有选项-T手动

禁用伪tty分配

  • '-t -t'有效,但是'-T'我得到'sudo:对不起,你必须有一个tty来运行sudo' (15认同)
  • 这个答案需要更多的分数 - 它是正确的答案,而不是像zanco的答案那么长,荒​​谬的是"无论如何用-t -t分配一个TTY"得到107分,当你完全可以跳过它时 (10认同)
  • 刚刚投票; 它对我来说比-t -t更好 (2认同)
  • @nhed:`-tt`选项最适合那些真正想要TTY并且正在获取OP错误消息的人.如果你不需要TTY,这个`-T`答案会更好. (2认同)
  • @nhed - 肯定 - 但我确定像我这样的人来自谷歌搜索错误信息中的错误.如何明确其他googlers应如何在两个相互竞争的答案之间做出选择似乎并非不合理.或者可能不是.我不知道 (2认同)
  • 答案是 - *这取决于*。如果您需要 TTY(如运行交互式 shell),则使用“-tt”,如果不需要 TTY(如运行脚本时),则使用“-T”。大多数人确实需要 shell,所以他们需要 `-tt`。 (2认同)

Dej*_*ton 79

根据zanco的回答,ssh鉴于shell如何解析命令行,你不会提供远程命令.要解决此问题,请更改ssh命令调用的语法,以便远程命令由语法正确的多行字符串组成.

可以使用各种语法.例如,由于命令可被输送进bashsh,大概等贝壳也一样,最简单的解决方法就是结合sshshell调用与here文档:

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

请注意,执行上述操作不会 /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
Run Code Online (Sandbox Code Playgroud)

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

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

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

上述解决方案以下列方式解决了此问题:

  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}"
Run Code Online (Sandbox Code Playgroud)

  • 一年半后+1!:) 非常清楚的解释。做得好 (3认同)

And*_*ock 62

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

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

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

  • 对我来说,事实证明它是PATH中的另一个ssh实现:`$ ssh/cygdrive/c/Program Files(x86)/ Git/bin/ssh` (10认同)

小智 30

警告消息Pseudo-terminal will not be allocated because stdin is not a terminal.是由于没有为sshstdin从here文档重定向时指定的命令.由于缺少指定的命令作为参数,ssh首先需要一个交互式登录会话(这需要在远程主机上分配一个pty),但是必须意识到它的本地stdin不是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未执行转义或单引号(即<<\EOT<<'EOT'),则本地文档中的变量将在执行之前由本地shell进行扩展ssh ....结果是here文档中的变量将保持为空,因为它们仅在远程shell中定义.

因此,如果$REL_DIR本地shell都可以访问并且在远程shell $REL_DIR中定义,则必须在ssh命令之前在here文档之外定义(下面的版本1); 或者,如果<<\EOT或者<<'EOT'使用,如果stdout命令的唯一输出由转义/单引号文档(下面的版本2)内部生成,ssh则可以分配命令的输出.REL_DIRsshecho "$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"
Run Code Online (Sandbox Code Playgroud)

  • 感谢您详细解释此错误的含义以及使用heredoc时出现的原因,这有助于我真正理解它而不仅仅是解决它. (5认同)

mkl*_*nt0 27

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

TL;博士:

  • 请使用命令行参数传递命令以运行:
    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(伪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创建一个交互式 shell - 包括当您通过stdin发送命令时,这是麻烦开始的地方:

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

    • 通过stdin发送命令意味着ssh的标准输入不再连接到终端,所以没有被创建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可能无效.


Hen*_*olm 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'
Run Code Online (Sandbox Code Playgroud)

(所有在一个巨型定'界的多行命令行参数).

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

-t无论如何你想要达到什么目的?

  • @Henning:请您详细说明或提供有关重定向或管道进入交互式ssh的缺点的链接? (4认同)

Wad*_* M. 5

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

用这个:

ssh user@machine /bin/bash <<'ENDSSH'
   hostname
ENDSSH
Run Code Online (Sandbox Code Playgroud)

而不是这个(给出错误):

ssh user@machine <<'ENDSSH'
   hostname
ENDSSH
Run Code Online (Sandbox Code Playgroud)

或者用这个:

ssh user@machine /bin/bash < run-command.sh
Run Code Online (Sandbox Code Playgroud)

而不是这个(给出错误):

ssh user@machine < run-command.sh
Run Code Online (Sandbox Code Playgroud)

额外:

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

ssh -t user@machine "$(<run-command.sh)"
Run Code Online (Sandbox Code Playgroud)

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

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