linux 将不会分配伪终端,因为stdin不是终端

jjjwad0x  于 2022-11-28  发布在  Linux
关注(0)|答案(9)|浏览(368)

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

剧本就这样永远挂着。
我的公钥在服务器上是可信的,我可以运行脚本之外的所有命令。有什么想法吗?

7kjnsjlb

7kjnsjlb1#

尝试ssh -t -t(或简称为ssh -tt)强制伪tty分配,即使stdin不是终端。
另请参阅: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.
2o7dmzc5

2o7dmzc52#

也可使用手册中的选件-T
禁用伪tty分配

cnjp1d6j

cnjp1d6j3#

Per zanco's answer , you're not providing a remote command to ssh , given how the shell parses the command line. To solve this problem, change the syntax of your ssh command invocation so that the remote command is comprised of a syntactically correct, multi-line string.
There are a variety of syntaxes that can be used. For example, since commands can be piped into bash and sh , and probably other shells too, the simplest solution is to just combine ssh shell invocation with heredocs:

ssh user@server /bin/bash <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT

Note that executing the above without/bin/bash will result in the warning Pseudo-terminal will not be allocated because stdin is not a terminal . Also note that EOT is surrounded by single-quotes, so that bash recognizes the heredoc as a nowdoc, turning off local variable interpolation so that the command text will be passed as-is to ssh .
If you are a fan of pipes, you can rewrite the above as follows:

cat <<'EOT' | ssh user@server /bin/bash
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT

The same caveat about /bin/bash applies to the above.
Another valid approach is to pass the multi-line remote command as a single string, using multiple layers of bash variable interpolation as follows:

ssh user@server "$( cat <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT
)"

The solution above fixes this problem in the following manner:

  1. ssh user@server is parsed by bash, and is interpreted to be the ssh command, followed by an argument user@server to be passed to the ssh command
  2. " begins an interpolated string, which when completed, will comprise an argument to be passed to the ssh command, which in this case will be interpreted by ssh to be the remote command to execute as user@server
  3. $( begins a command to be executed, with the output being captured by the surrounding interpolated string
  4. cat is a command to output the contents of whatever file follows. The output of cat will be passed back into the capturing interpolated string
  5. << begins a bash heredoc
  6. 'EOT' specifies that the name of the heredoc is EOT. The single quotes ' surrounding EOT specifies that the heredoc should be parsed as a nowdoc, which is a special form of heredoc in which the contents do not get interpolated by bash, but rather passed on in literal format
  7. Any content that is encountered between <<'EOT' and <newline>EOT<newline> will be appended to the nowdoc output
  8. EOT terminates the nowdoc, resulting in a nowdoc temporary file being created and passed back to the calling cat command. cat outputs the nowdoc and passes the output back to the capturing interpolated string
  9. ) concludes the command to be executed
  10. " concludes the capturing interpolated string. The contents of the interpolated string will be passed back to ssh as a single command line argument, which ssh will interpret as the remote command to execute as user@server
    If you need to avoid using external tools like cat , and don't mind having two statements instead of one, use the read built-in with a heredoc to generate the SSH command:
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}"
kwvwclae

kwvwclae4#

我之所以添加此答案,是因为它解决了我遇到的一个相关问题,该问题也出现了相同的错误消息。

    • 问题**:我在Windows下安装了cygwin,并得到这个错误:Pseudo-terminal will not be allocated because stdin is not a terminal
    • 分辨率**:我发现我没有安装openssh客户端程序和实用程序。因为cygwin使用的是ssh的Windows实现,而不是cygwin版本。解决方案是安装openssh cygwin包。
xxe27gdn

xxe27gdn5#

All relevant information is in the existing answers, but let me attempt a pragmatic summary:

tl;dr:

  • DO pass the commands to run using a command-line argument:

ssh jdoe@server '...'

  • '...' strings can span multiple lines, so you can keep your code readable even without the use of a here-document:

ssh jdoe@server ' ... '

  • Do NOT pass the commands via stdin, as is the case when you use a here-document :

ssh jdoe@server <<'EOF' # Do NOT do this ... EOF

Passing the commands as an argument works as-is, and:

  • the problem with the pseudo-terminal will not even arise.
  • you won't need an exit statement at the end of your commands, because the session will automatically exit after the commands have been processed.

In short: passing commands via stdin is a mechanism that is at odds with ssh 's design and causes problems that must then be worked around.
Read on, if you want to know more.

Optional background information:

ssh 's mechanism for accepting commands to execute on the target server is a command-line argument: the final operand (non-option argument) accepts a string containing one or more shell commands.

  • By default, these commands run unattended, in a non-interactive shell, without the use of a (pseudo) terminal (option -T is implied), and the session automatically ends when the last command finishes processing.
  • In the event that your commands require user interaction, such as responding to an interactive prompt, you can explicitly request the creation of a pty (pseudo-tty) , a pseudo terminal, that enables interacting with the remote session, using the -t option; e.g.:
  • ssh -t jdoe@server 'read -p "Enter something: "; echo "Entered: [$REPLY]"'
  • Note that the interactive read prompt only works correctly with a pty, so the -t option is needed.
  • Using a pty has a notable side effect: stdout and stderr are combined and both reported via stdout; in other words: you lose the distinction between regular and error output; e.g.:
  • 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 - including when you send commands via stdin, which is where the trouble begins:
  • For an interactive shell, ssh normally allocates a pty (pseudo-terminal) by default, except if its stdin is not connected to a (real) terminal.
  • Sending commands via stdin means that ssh 's stdin is no longer connected to a terminal, so no pty is created, and sshwarns 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: you'll get the same warning.
  • Somewhat curiously, you must then double the -t option to force creation of a pty: ssh -t -t ... or ssh -tt ... shows that you really, really mean it.
  • Perhaps the rationale for requiring this very deliberate step is that things may not work as expected. For instance, on macOS 10.12, the apparent equivalent of the above command, providing the commands via stdin and using -tt , does not work properly; the session gets stuck after responding to the read prompt:

ssh -tt jdoe@server <<<'read -p "Enter something: "; echo "Entered: [$REPLY]"'
In the unlikely event that the commands you want to pass as an argument make the command line too long for your system (if its length approaches getconf ARG_MAX - see this article ), consider copying the code to the remote system in the form of a script first (using, e.g., scp ), and then send a command to execute that script.
In a pinch, use -T , and provide the commands via stdin, with a trailing exit command, but note that if you also need interactive features, using -tt in lieu of -T may not work.

tv6aics1

tv6aics16#

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.
Since there are no commands to be executed via ssh that require the presence of a tty/pty (such as vim or top ) the -t switch to ssh is superfluous. Just use ssh -T user@server <<EOT ... or ssh user@server /bin/bash <<EOT ... and the warning will go away.
If <<EOF is not escaped or single-quoted (i. e. <<\EOT or <<'EOT' ) variables inside the here document will be expanded by the local shell before it is executing ssh ... . The effect is that the variables inside the here document will remain empty because they are defined only in the remote shell.
So, if $REL_DIR should be both accessible by the local shell and defined in the remote shell, $REL_DIR has to be defined outside the here document before the ssh command (version 1 below); or, if <<\EOT or <<'EOT' is used, the output of the ssh command can be assigned to REL_DIR if the only output of the ssh command to stdout is genererated by echo "$REL_DIR" inside the escaped/single-quoted here document (version 2 below).
A third option would be to store the here document in a variable and then pass this variable as a command argument to ssh -t user@server "$heredoc" (version 3 below).
And, last but not least, it would be no bad idea to check if the directories on the remote host were created successfully (see: 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"
polhcujo

polhcujo7#

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

(All在一个巨大的'分隔的多行命令行参数中)。
伪终端消息是因为您的-t要求ssh尝试使它在远程机器上运行的环境看起来像是在那里运行的程序的实际终端。您的ssh客户端拒绝这样做,因为它 * 自己 * 的标准输入不是终端,所以它没有办法将特殊的终端API从远程机器传递到本地端的实际终端。
你到底想用-t达到什么目的?

t98cgbkg

t98cgbkg8#

在阅读了大量的答案后,我想我会分享我的结果解决方案。所有我添加的是/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
2izufjch

2izufjch9#

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

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

相关问题