Pseudo-terminal will not be allocated because stdin is not a terminal
我正在尝试编写一个shell脚本,该脚本在远程服务器上创建一些目录,然后使用scp将文件从本地计算机复制到远程服务器上。以下是我目前为止的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 |
每次运行它,我都会收到以下消息:
1 | Pseudo-terminal will not be allocated because stdin is not a terminal. |
剧本永远挂着。
我的公钥在服务器上是可信的,我可以在脚本之外运行所有的命令。有什么想法吗?
尝试使用
另请参见:终止由bash脚本执行的ssh会话
从ssh手册页:
1 2 3 4 5 6 | -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. |
也可从手动选择
Disable pseudo-tty allocation
根据Zanco的回答,考虑到shell如何解析命令行,您不会向
可以使用多种语法。例如,由于命令可以通过管道传输到
1 2 3 4 | ssh user@server /bin/bash <<'EOT' echo"These commands will be run on: $( uname -a )" echo"They are executed by: $( whoami )" EOT |
请注意,在不使用
如果您是管道爱好者,可以将上述内容改写为:
1 2 3 4 | cat <<'EOT' | ssh user@server /bin/bash echo"These commands will be run on: $( uname -a )" echo"They are executed by: $( whoami )" EOT |
关于
另一种有效的方法是将多行远程命令作为单个字符串传递,使用多个层的
1 2 3 4 5 | ssh user@server"$( cat <<'EOT' echo"These commands will be run on: $( uname -a )" echo"They are executed by: $( whoami )" EOT )" |
上述解决方案以以下方式解决此问题:
在
如果您需要避免使用诸如
1 2 3 4 5 6 | 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,并得到了这个错误:
解决方案:原来我没有安装OpenSSH客户机程序和实用程序。因为cygwin使用的是ssh的Windows实现,而不是cygwin版本。解决方案是安装openssh cygwin包。
警告消息
由于没有需要TTY/PTY(如
如果
因此,如果
第三种选择是将here文档存储在变量中,然后将该变量作为命令参数传递给
而且,最后但并非最不重要的是,检查远程主机上的目录是否已成功创建(请参阅:检查具有ssh的远程主机上是否存在文件)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | # 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" |
所有相关信息都在现有答案中,但让我尝试一个务实的总结:
DR:
使用命令行参数传递要运行的命令:
ssh jdoe@server '...' '...' 字符串可以跨多行,因此即使不使用here文档,也可以保持代码的可读性:ssh jdoe@server '
...
'
不要通过stdin传递命令,就像使用here文档时一样:
ssh jdoe@server <<'EOF' # Do NOT do this ... EOF
将命令作为参数传递可以按原样工作,并且:
- 伪终端的问题甚至不会出现。
- 您不需要在命令末尾使用
exit 语句,因为在处理完命令后,会话将自动退出。
简而言之:通过stdin传递命令是一种与
默认情况下,这些命令在非交互shell中以无人参与的方式运行,而不使用(伪)终端(隐含选项
-T ),当最后一个命令完成处理时,会话自动结束。如果您的命令需要用户交互(如响应交互提示),您可以使用
-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
在没有这个参数的情况下,
对于交互shell,默认情况下,
ssh 通常分配一个pty(伪终端),除非其stdin未连接到(real)终端。通过stdin发送命令意味着
ssh 的stdin不再连接到终端,因此不会创建pty,ssh 会相应地警告您:Pseudo-terminal will not be allocated because stdin is not a terminal. 在这种情况下,即使是明确目的是请求创建一个PTY的
-T 选项也不够:您将得到同样的警告。有点奇怪的是,你必须加倍选择
-T 强制创建一个私人公司:ssh -t -t ... 或ssh -tt ... 表明你真的,真的是认真的。也许需要这个深思熟虑的步骤的理由是事情可能不会像预期的那样工作。例如,在MacOS 10.12上,通过stdin和使用
-tt 提供命令的上述命令的明显等价物不能正常工作;会话在响应read 提示后被卡住:ssh -tt jdoe@server <<<'read -p"Enter something:"; echo"Entered: [$REPLY]"'
在不太可能的情况下,您希望作为参数传递的命令会使命令行对您的系统太长(如果其长度接近
在紧急情况下,使用
我不知道挂起是从哪里来的,但是将(或管道)命令重定向到交互式ssh通常是解决问题的方法。使用command-to-run-as-a-last参数样式并在ssh命令行上传递脚本更为健壮:
1 2 3 4 5 6 7 8 | 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' |
(全部在一个巨大的
伪终端消息是因为您的
你到底想用
在阅读了大量这些答案之后,我想我会分享我的解决方案。我所添加的是在Heredoc之前的
使用此:
1 2 3 | ssh user@machine /bin/bash <<'ENDSSH' hostname ENDSSH |
而不是这个(给出错误):
1 2 3 | ssh user@machine <<'ENDSSH' hostname ENDSSH |
或使用此:
1 | ssh user@machine /bin/bash < run-command.sh |
而不是这个(给出错误):
1 | ssh user@machine < run-command.sh |
额外的:
如果仍然需要远程交互提示,例如,如果正在远程运行的脚本提示您输入密码或其他信息,因为以前的解决方案不允许您键入提示。
1 | ssh -t user@machine"$(<run-command.sh)" |
如果您还想将整个会话记录在文件
1 | ssh -t user@machine"$(<run-command.sh)" | tee -a logfile.log |
我在Windows下也遇到了同样的错误,我使用emacs 24.5.1通过/ssh:user@host连接到一些公司服务器。解决我的问题的是将"tramp default method"变量设置为"plink",并且每当我连接到服务器时,我都会命令ssh协议。您需要安装Putty的plink.exe才能工作。
解决方案
ssh-t foobar@localhost yourscript.pl