Home Articles

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

Asked
Viewed 1230 times
248

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

  • 390

    所有相关信息都在现有答案中,但让我尝试 pragmatic summary

    tl;dr:

    • DO pass the commands to run using a command-line argument
      ssh jdoe@server '...'

    • '...' 字符串可以跨越多行,因此即使不使用here-document也可以保持代码可读:
      ssh jdoe@server ' ... '

    • Do NOT pass the commands via stdin ,就像使用here-document时一样:
      ssh jdoe@server <<'EOF' # Do NOT do this ... EOF

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

    • the problem with the pseudo-terminal will not even arise.

    • you won't need an exit statement 在命令末尾,因为会话将在处理完命令后自动退出 .

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


    可选背景资料:

    ssh's mechanism for accepting commands to execute on the target server is a command-line argument :最终操作数(非选项参数)接受包含一个或多个shell命令的字符串 .

    • 默认情况下,这些 commands run unattended, in an non-interactive shell, without the use of a (pseudo) terminal (option -T is implied), and the session automatically ends 在最后一个命令完成处理时 .

    • 如果您的命令需要用户交互,例如响应交互式提示,您可以使用 -t 选项显式请求创建pty (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

    In the absence of this argument, ssh creates an interactive shell - 包括当您通过stdin发送命令时,这是麻烦开始的地方:

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

    • Sending commands via stdin means that ssh's stdin is no longer connected to a terminal, so no pty is created, and ssh warns you accordingly
      Pseudo-terminal will not be allocated because stdin is not a terminal.

    • Even the -t option, whose express purpose is to request creation of a pty, is not enough in this case :你会得到同样的警告 .

    • 有点好奇,你必须 double the -t option 强制创建一个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 - 请参阅this article),请考虑首先以脚本的形式将代码复制到远程系统(使用例如 scp ),然后发送命令来执行该脚本 .

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

  • 134

    在阅读了很多这些答案后,我想我会分享我最终的解决方案 . 我在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
    
  • 68

    即使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.
    
  • 40

    根据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,关闭局部变量插值,以便命令文本按原样传递给 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
    )"
    

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

    • ssh user@server 由bash解析,并被解释为 ssh 命令,后跟参数 user@server 以传递给 ssh 命令

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

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

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

    • << 开始了bash heredoc

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

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

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

    • ) 结束要执行的命令

    • " 结束捕获插值字符串 . 插值字符串的内容将作为单个命令行参数传递回 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}"
    
  • 27

    也可以选择 -T 来自manual

    禁用伪tty分配

  • 20

    ssh -t foobar @ localhost yourscript.pl

  • 19

    我不知道挂起来自何处,但将重定向(或管道)命令重定向到交互式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'
    

    (所有在一个巨大的 ' -delimited多行命令行参数) .

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

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

  • 5

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

    Solution

    • M-x customize-variable(然后按Enter键)

    • tramp-default-method(然后再次按Enter键)

    • 在文本字段中输入plink,然后应用并保存缓冲区

    • 每当我尝试访问远程服务器时,我现在使用C-x-f / user @ host:然后输入密码 . 现在,在Windows上的Emacs下正确连接到我的远程服务器 .

  • 0

    警告消息 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 切换到 ssh 是多余的 . 只需使用 ssh -T user@server <<EOT ...ssh user@server /bin/bash <<EOT ... ,警告就会消失 .

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

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

    第三种选择是将here文档存储在变量中,然后将此变量作为命令参数传递给 ssh -t user@server "$heredoc" (下面的 version 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"
    
  • -2

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

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

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

Related