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

2024-10-09 09:11:00
admin
原创
77
摘要:问题描述:我正在尝试编写一个 shell 脚本,在远程服务器上创建一些目录,然后使用 scp 将文件从本地计算机复制到远程服务器。以下是我目前所做的:ssh -t user@server<<EOT DEP_ROOT='/home/matthewr/releases...

问题描述:

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

而脚本就永远悬而未决。

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


解决方案 1:

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

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

解决方案 2:

也可以选择-T手动

禁用伪tty分配

解决方案 3:

根据zanco 的回答ssh,考虑到 shell 如何解析命令行,您没有向 提供远程命令。要解决此问题,请更改ssh命令调用的语法,以便远程命令由语法正确的多行字符串组成。

有多种语法可供使用。例如,由于命令可以通过管道传输到bashsh,也可能传输到其他 shell 中,最简单的解决方案就是将sshshell 调用与 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`

  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}"

解决方案 4:

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

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

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

解决方案 5:

所有相关信息都在现有答案中,但让我尝试进行务实的总结

总结:

  • 请通过命令行参数传递要运行的命令

ssh jdoe@server '...'

+ **`'...'`字符串可以跨越多行**,因此即使不使用此处文档,也可以保持代码的可读性:  

`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;例如:

+ `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 不再连接到终端,因此*不会*创建 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),然后发送命令来执行该脚本。

在紧急情况下,使用,并通过stdin-T提供命令,并带有尾随命令,但请注意,如果您还需要交互式功能,则使用代替可能不起作用。exit`-tt`-T

解决方案 6:

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

由于没有需要sshtty/pty 的命令(例如vimtop),因此-t切换到ssh是多余的。只需使用ssh -T user@server <<EOT ...ssh user@server /bin/bash <<EOT ...,警告就会消失。

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

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

第三种选择是将此处的文档存储在一个变量中,然后将该变量作为命令参数传递给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"

解决方案 7:

我不知道挂起的原因是什么,但将命令重定向(或管道)到交互式 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'

(所有内容都在一个巨大的'以 -delimited 开头的多行命令行参数中)。

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

你到底想实现什么目标-t

解决方案 8:

在阅读了很多这些答案之后,我想分享我最终的解决方案。我添加的所有内容都/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

额外的

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

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

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

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

解决方案 9:

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

解决方案

  1. Mx customize-variable(然后按 Enter)

  2. tramp-default-method (然后再次按 Enter)

  3. 在文本字段上输入 plink,然后应用并保存缓冲区

  4. 每当我尝试访问远程服务器时,我现在都会使用 Cxf /user@host:,然后输入密码。现在,在 Windows 上的 Emacs 下可以正确连接到我的远程服务器。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用