我正在尝试编写一个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 回答
ssh -t foobar @ localhost yourscript.pl
即使stdin不是终端,也要尝试
ssh -t -t
(或简称ssh -tt
)强制伪tty分配 .另见:Terminating SSH session executed by bash script
从ssh手册页:
也可以选择
-T
来自manual根据zanco's answer,您没有为
ssh
提供远程命令,考虑到shell如何解析命令行 . 要解决此问题,请更改ssh
命令调用的语法,以便远程命令由语法正确的多行字符串组成 .可以使用各种语法 . 例如,由于命令可以通过管道传输到
bash
和sh
,也可能是其他shell,最简单的解决方案是将ssh
shell调用与heredocs结合使用:请注意,在没有
/bin/bash
的情况下执行上述操作将导致警告Pseudo-terminal will not be allocated because stdin is not a terminal
. 另请注意,EOT
被单引号括起,因此bash
将heredoc识别为nowdoc,关闭局部变量插值,以便命令文本按原样传递给ssh
.如果您是管道的粉丝,可以按如下方式重写上述内容:
关于
/bin/bash
的相同警告适用于上述情况 .另一种有效的方法是将多行远程命令作为单个字符串传递,使用多个
bash
变量插值层,如下所示:上述解决方案以下列方式解决了此问题:
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并将输出传递回捕获插值字符串)
结束要执行的命令"
结束捕获插值字符串 . 插值字符串的内容将作为单个命令行参数传递回ssh
,ssh
将解释为要执行的远程命令user@server
如果您需要避免使用像
cat
这样的外部工具,并且不介意使用两个语句而不是一个语句,请使用带有heredoc的内置read
来生成SSH命令:我正在添加这个答案,因为它解决了我遇到的相同错误消息的相关问题 .
Problem :我在Windows下安装了cygwin并收到此错误:
Pseudo-terminal will not be allocated because stdin is not a terminal
Resolution :事实证明我已安装了openssh客户端程序和实用程序 not . 因为cygwin使用ssh的Windows实现,而不是cygwin版本 . 解决方案是安装openssh cygwin软件包 .
警告消息
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(例如vim
或top
),-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) .
我不知道挂起来自何处,但将重定向(或管道)命令重定向到交互式ssh通常是问题的处方 . 使用命令到run-as-a-last-argument样式并在ssh命令行上传递脚本更加健壮:
(所有在一个巨大的
'
-delimited多行命令行参数) .伪终端消息是因为您的
-t
要求ssh尝试使其在远程计算机上运行的环境看起来像是在那里运行的程序的实际终端 . 您的ssh客户端拒绝这样做,因为它自己的标准输入不是终端,因此无法将特殊终端API从远程计算机传递到本地端的实际终端 .无论如何你想用_1852209实现什么?
所有相关信息都在现有答案中,但让我尝试 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
可能无效 .在阅读了很多这些答案后,我想我会分享我最终的解决方案 . 我在heredoc之前添加的是
/bin/bash
,它不再给出错误 .用这个:
而不是这个(给出错误):
或者用这个:
而不是这个(给出错误):
EXTRA :
如果您仍想要远程交互式提示,例如如果您正在远程运行的脚本提示您输入密码或其他信息,因为之前的解决方案将不允许您键入提示 .
如果您还想将整个会话记录在文件
logfile.log
中:我在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下正确连接到我的远程服务器 .