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

剧本永远挂着。

我的公钥在服务器上是可信的,我可以在脚本之外运行所有命令。什么好主意吗?


当前回答

The warning message Pseudo-terminal will not be allocated because stdin is not a terminal. is due to the fact that no command is specified for ssh while stdin is redirected from a here document. Due to the lack of a specified command as an argument ssh first expects an interactive login session (which would require the allocation of a pty on the remote host) but then has to realize that its local stdin is no tty/pty. Redirecting ssh's stdin from a here document normally requires a command (such as /bin/sh) to be specified as an argument to ssh - and in such a case no pty will be allocated on the remote host by default.

由于通过ssh执行的命令不需要tty/pty(如vim或top),所以切换到ssh的-t是多余的。 只需使用ssh -T user@server <<EOT…或ssh user@server /bin/bash <<EOT…警告就会消失。

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

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

第三种方法是将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"

其他回答

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

解决方案

M-x自定义变量(然后按Enter) tramp-default-method(然后再次按Enter键) 在文本框中输入plink,然后应用并保存缓冲区 每当我尝试访问远程服务器时,我现在使用C-x-f /user@host:,然后输入密码。现在在Windows上的Emacs下正确地建立了到远程服务器的连接。

我不知道挂起的原因是什么,但是将命令重定向(或管道)到交互式ssh通常会出现问题。使用command-to-run-as- 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'

(所有这些都包含在一个以'分隔的多行命令行参数中)。

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

你想用-t实现什么呢?

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

请参见:终止bash脚本执行的SSH会话

从ssh manpage:

-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.

The warning message Pseudo-terminal will not be allocated because stdin is not a terminal. is due to the fact that no command is specified for ssh while stdin is redirected from a here document. Due to the lack of a specified command as an argument ssh first expects an interactive login session (which would require the allocation of a pty on the remote host) but then has to realize that its local stdin is no tty/pty. Redirecting ssh's stdin from a here document normally requires a command (such as /bin/sh) to be specified as an argument to ssh - and in such a case no pty will be allocated on the remote host by default.

由于通过ssh执行的命令不需要tty/pty(如vim或top),所以切换到ssh的-t是多余的。 只需使用ssh -T user@server <<EOT…或ssh user@server /bin/bash <<EOT…警告就会消失。

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

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

第三种方法是将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"

在看了很多这样的答案之后,我想分享一下我的解决方案。我只是在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

额外的:

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

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

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

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