I am creating a script that is executed from host-A to connect to a remote host-b, runs a bash script (which generates monitoring data from top etc.) on host-b, and appends the output back to a file on host-A. While I use ssh to connect, I will not be using ssh-keys as there are simply way too many servers in the environment.
My expect script:
"#!/usr/bin/expect
"#!/usr/bash
set timeout 10
set hostname [sample_hostname]
set user "(sample_user)"
set password "(sample_password)"
spawn ssh $user#$hostname /path/to/bash/script/script.sh | cat >> /tmp/file
expect "Password:"
send "$password\r";
expect "$"
====================================
The above script appends the output of the bash script into host-B /tmp/file instead of the desired host-A /tmp/file. How can I change to have script append to host-A instead of host-b?
P.S.
manually logging into host-A
cd to script location
run ssh user#hostname /path/to/script/script.sh | cat >> /tmp/file on host-A
cat /tmp/file
Above manual steps performs as desired; hence my idea that problem is with bash in expect.
Any ideas are appreciated!
Regards,
Alan
If your intention is to get just the output of that script, then proceed with the following way.
#!/usr/bin/expect
set timeout 10
set hostname host
set user dinesh
set password pwd
set script "/home/dinesh/mypgms/demo.sh"
spawn ssh $user#$hostname
expect "password:"
send "$password\r";
expect "\\\$"; # Matching the literal dollar prompt sign of terminal
send "$script\r"
expect -re "\r\n(.*)\\\$"; # Matching the output
# Getting first sub-match as result
puts "\nResult : --->$expect_out(1,string)"
This will give the output as
dinesh#dinesh-VirtualBox:~/stackoverflow$ ./alan
spawn ssh dinesh#host
dinesh#host's password:
Last login: Tue Apr 21 13:30:35 2015
[dinesh# ~]$ /home/dsivaji/mypgms/demo.sh
{\"INFO\":0}\0\r
{\"INFO\":0}\0\r
[dinesh#~]$
Result : --->{\"INFO\":0}\0\r
{\"INFO\":0}\0\r
[dinesh#~]
dinesh#dinesh-VirtualBox:~/stackoverflow$
Note that the result as
{\"INFO\":0}\0\r
{\"INFO\":0}\0\r
[dinesh#~]
Since we are matching upto the prompt $, it contains till that point. You can customize the regular expression based on the shell output also.
I'm assuming you are using the expect/set method beacuse of the password.
I found another application called sshpass once installed you can just run the command like this inside your bash.
sshpass -p PASSWORDHERE ssh user#hostname /path/to/bash/script/script.sh | cat >> /tmp/file
http://linux.die.net/man/1/sshpass
enjoy
Related
I am working on a script in linux in which i need to SSH to another server.
I am using this syntax ssh user#ip
When doing a manual SSH when we type "ssh user#ip" a few second, a second prompt will show and ask what environment are we choosing ex 1 to 4
(4) - this is the number of the environment i will be choosing.
But when doing ssh user#ip 4 - error received (bash command not found)
Here is the image on what is the prompt when using ssh
Without knowing what happens after you select the environment, its probably something like:
echo 4 | ssh user#ip
Feed 4 to whatever is prompting for environment, then exit.
If you need to stay connected, then something like
echo 4 | ssh -tt user#ip "ksh -l"
ksh -l could be replaced by some other shell or command like vi foo.txt (here when the command exits, the connection closes).
The above is similar to this previous question:
Run ssh and immediately execute command
I'm trying to use expect in a Bash script to provide the SSH password. Providing the password works, but I don't end up in the SSH session as I should. It goes back strait to Bash.
My script:
#!/bin/bash
read -s PWD
/usr/bin/expect <<EOD
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com'
expect "password"
send "$PWD\n"
EOD
echo "you're out"
The output of my script:
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com
usr#$myhost.example.com's password: you're out
I would like to have my SSH session and, only when I exit it, to go back to my Bash script.
The reason why I am using Bash before expect is because I have to use a menu. I can choose which unit/device to connect to.
To those who want to reply that I should use SSH keys, please abstain.
Mixing Bash and Expect is not a good way to achieve the desired effect. I'd try to use only Expect:
#!/usr/bin/expect
eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com
# Use the correct prompt
set prompt ":|#|\\\$"
interact -o -nobuffer -re $prompt return
send "my_password\r"
interact -o -nobuffer -re $prompt return
send "my_command1\r"
interact -o -nobuffer -re $prompt return
send "my_command2\r"
interact
Sample solution for bash could be:
#!/bin/bash
/usr/bin/expect -c 'expect "\n" { eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com; interact }'
This will wait for Enter and then return to (for a moment) the interactive session.
The easiest way is to use sshpass. This is available in Ubuntu/Debian repositories and you don't have to deal with integrating expect with Bash.
An example:
sshpass -p<password> ssh <arguments>
sshpass -ptest1324 ssh user#192.168.1.200 ls -l /tmp
The above command can be easily integrated with a Bash script.
Note: Please read the Security Considerations section in man sshpass for a full understanding of the security implications.
Add the 'interact' Expect command just before your EOD:
#!/bin/bash
read -s PWD
/usr/bin/expect <<EOD
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no usr#$myhost.example.com
expect "password"
send -- "$PWD\r"
interact
EOD
echo "you're out"
This should let you interact with the remote machine until you log out. Then you'll be back in Bash.
After looking for an answer for the question for months, I finally find a really best solution: writing a simple script.
#!/usr/bin/expect
set timeout 20
set cmd [lrange $argv 1 end]
set password [lindex $argv 0]
eval spawn $cmd
expect "assword:" # matches both 'Password' and 'password'
send -- "$password\r"; # -- for passwords starting with -, see https://stackoverflow.com/a/21280372/4575793
interact
Put it to /usr/bin/exp, then you can use:
exp <password> ssh <anything>
exp <password> scp <anysrc> <anydst>
Done!
A simple Expect script:
File Remotelogin.exp
#!/usr/bin/expect
set user [lindex $argv 1]
set ip [lindex $argv 0]
set password [lindex $argv 2]
spawn ssh $user#$ip
expect "password"
send "$password\r"
interact
Example:
./Remotelogin.exp <ip> <user name> <password>
Also make sure to use
send -- "$PWD\r"
instead, as passwords starting with a dash (-) will fail otherwise.
The above won't interpret a string starting with a dash as an option to the send command.
Use the helper tool fd0ssh (from hxtools, source for ubuntu, source for openSUSE, not pmt). It works without having to expect a particular prompt from the ssh program.
It is also "much safer than passing the password on the command line as sshpass does" ( - comment by Charles Duffy).
Another way that I found useful to use a small Expect script from a Bash script is as follows.
...
Bash script start
Bash commands
...
expect - <<EOF
spawn your-command-here
expect "some-pattern"
send "some-command"
...
...
EOF
...
More Bash commands
...
This works because ...If the string "-" is supplied as a filename, standard input is read instead...
sshpass is broken if you try to use it inside a Sublime Text build target, inside a Makefile. Instead of sshpass, you can use passh
With sshpass you would do:
sshpass -p pa$$word ssh user#host
With passh you would do:
passh -p pa$$word ssh user#host
Note: Do not forget to use -o StrictHostKeyChecking=no. Otherwise, the connection will hang on the first time you use it. For example:
passh -p pa$$word ssh -o StrictHostKeyChecking=no user#host
References:
Send command for password doesn't work using Expect script in SSH connection
How can I disable strict host key checking in ssh?
How to disable SSH host key checking
scp without known_hosts check
pam_mount and sshfs with password authentication
I have to create a file functon.txt under a particular directory with hello world in it in lots of machine. This is what I was doing so far manually one by one logging into each box and creating the file. That directory is own by root so I have to make sure that new file is also owned by root user.
david#machineA:~$ sudo su
[sudo] password for david:
root#machineA:/home/david# cd /opt/Potle/ouyt/wert/1
root#machineA:/opt/Potle/ouyt/wert/1# vi functon.txt
root#machineA:/opt/Potle/ouyt/wert/1# ssh david#machineB
david#machineB:~$ sudo su
[sudo] password for david:
root#machineB:/home/david# cd /opt/Potle/ouyt/wert/1
root#machineB:/opt/Potle/ouyt/wert/1# vi functon.txt
root#machineB:/opt/Potle/ouyt/wert/1# ssh david#machineC
.....
Now I have to do this in around 200 machines. Is there any way I can do these things through some script? I am ok typing passwords multiple times if I have to but I don't want to manually login into those box and do all the other steps by hand.
I have a file hosts.txt which contains each machine line by line. I can read this file line by line and do above things but I am not sure how?
This is just one time exercise for me so any easy or simple way should be fine. I can even hardcode my password in the script to do this job. What is the best way to accomplish this task?
After installing Ansible:
ansible -i /path/to/hosts.txt -m ping -u david --ask-pass all
See if you can ping the machines successfully. If it is successful, then try the following with 2 machines (create another txt file with just 2 machines and pass it to -i option). Then you can run this for all machines. If the directory does not exist, the command will fail and you will see the failed machines in summary.
ansible -i /path/to/hosts.txt -m copy -a "src=/path/to/functon.txt dest=/opt/Potle/ouyt/wert/1/functon.txt" -u david --ask-pass --become --become-user root --ask-become-pass all
I didn't test this. So use caution.
-i: input host(s)
-m: module
-a: module arguments
-u: user
--ask-pass: Ask for user password
--become: become another user
--become-user: new user
--ask-become-pass: Ask for become user password
You can use expect to automate SSH copy / SSH login :
#!/usr/bin/expect
set password [lindex $argv 1]
spawn scp -P 22 [lindex $argv 2] [lindex $argv 0]
expect "*password:*"
send -- "$password\r"
send -- "\r"
expect eof
The expect command will wait for the string you give in arguments to be received.
You can iterate over your hosts from hosts.txt and run this script like this for each one :
./create_config.sh david#machineA:/opt/Potle/ouyt/wert/1/ somePassword functon.txt
If you dont have possibility to do SSH copy but only SSH, you can still send command with expect :
#!/usr/bin/expect
set password [lindex $argv 1]
spawn ssh -p 22 [lindex $argv 0]
expect "*password:*"
send -- "$password\r"
send -- "\r"
# expect the command prompt : change this if needed
expect "*$*"
# execute some commands
send -- "echo 'some text to write to some file' > ~/some_file.txt\r"
# exit vm
send -- "exit\r"
expect eof
You can run this with :
./create_config.sh david#machineA somePassword
You could use sshfs: mount a machine, do what you want, unmount and pass to the next.
I am working on automating some telnet related tasks, using Bash scripts.
Once automated, there will be no interaction of the user with telnet (that is, the script will be totally automated).
The scripts looks something like this:
# execute some commands on the local system
# access a remote system with an IP address: 10.1.1.1 (for example)
telnet 10.1.1.1
# execute some commands on the remote system
# log all the activity (in a file) on the local system
# exit telnet
# continue with executing the rest of the script
There are two problems I am facing here:
How to execute the commands on the remote system from the script (without human interaction)?
From my experience with some test code, I was able to deduce that when telnet 10.1.1.1 is executed, telnet goes into an interactive session and the subsequent lines of code in the script are executed on the local system. How can I run the lines of code on the remote system rather than on the local one?
I am unable to get a log file for the activity in the telnet session on the local system. The stdout redirect I used makes a copy on the remote system (I do not want to perform a copy operation to copy the log to the local system). How can I achieve this functionality?
While I'd suggest using expect, too, for non-interactive use the normal shell commands might suffice. telnet accepts its command on stdin, so you just need to pipe or write the commands into it through heredoc:
telnet 10.1.1.1 <<EOF
remotecommand 1
remotecommand 2
EOF
(Edit: Judging from the comments, the remote command needs some time to process the inputs or the early SIGHUP is not taken gracefully by telnet. In these cases, you might try a short sleep on the input:)
{ echo "remotecommand 1"; echo "remotecommand 2"; sleep 1; } | telnet 10.1.1.1
In any case, if it's getting interactive or anything, use expect.
Write an expect script.
Here is an example:
#!/usr/bin/expect
#If it all goes pear shaped the script will timeout after 20 seconds.
set timeout 20
#First argument is assigned to the variable name
set name [lindex $argv 0]
#Second argument is assigned to the variable user
set user [lindex $argv 1]
#Third argument is assigned to the variable password
set password [lindex $argv 2]
#This spawns the telnet program and connects it to the variable name
spawn telnet $name
#The script expects login
expect "login:"
#The script sends the user variable
send "$user "
#The script expects Password
expect "Password:"
#The script sends the password variable
send "$password "
#This hands control of the keyboard over to you (Nice expect feature!)
interact
To run:
./myscript.expect name user password
Telnet is often used when you learn the HTTP protocol. I used to use that script as a part of my web scraper:
echo "open www.example.com 80"
sleep 2
echo "GET /index.html HTTP/1.1"
echo "Host: www.example.com"
echo
echo
sleep 2
Let's say the name of the script is get-page.sh, then this will give you an HTML document:
get-page.sh | telnet
I hope this will be helpful to someone ;)
This worked for me..
I was trying to automate multiple telnet logins which require a username and password. The telnet session needs to run in the background indefinitely since I am saving logs from different servers to my machine.
telnet.sh automates telnet login using the 'expect' command. More info can be found here: http://osix.net/modules/article/?id=30
telnet.sh
#!/usr/bin/expect
set timeout 20
set hostName [lindex $argv 0]
set userName [lindex $argv 1]
set password [lindex $argv 2]
spawn telnet $hostName
expect "User Access Verification"
expect "Username:"
send "$userName\r"
expect "Password:"
send "$password\r";
interact
sample_script.sh is used to create a background process for each of the telnet sessions by running telnet.sh. More information can be found in the comments section of the code.
sample_script.sh
#!/bin/bash
#start screen in detached mode with session-name 'default_session'
screen -dmS default_session -t screen_name
#save the generated logs in a log file 'abc.log'
screen -S default_session -p screen_name -X stuff "script -f /tmp/abc.log $(printf \\r)"
#start the telnet session and generate logs
screen -S default_session -p screen_name -X stuff "expect telnet.sh hostname username password $(printf \\r)"
Make sure there is no screen running in the backgroud by using the
command 'screen -ls'.
Read
http://www.gnu.org/software/screen/manual/screen.html#Stuff to read
more about screen and its options.
'-p' option in sample_script.sh
preselects and reattaches to a specific window to send a command via
the ‘-X’ option otherwise you get a 'No screen session found' error.
You can use expect scripts instaed of bash.
Below example show how to telnex into an embedded board having no password
#!/usr/bin/expect
set ip "<ip>"
spawn "/bin/bash"
send "telnet $ip\r"
expect "'^]'."
send "\r"
expect "#"
sleep 2
send "ls\r"
expect "#"
sleep 2
send -- "^]\r"
expect "telnet>"
send "quit\r"
expect eof
The answer by #thiton was helpful but I wanted to avoid the sleep command. Also telnet didn't exit the interactive mode, so my script got stuck.
I solved that by sending telnet command with curl (which seems to wait for the response) and by explicitly telling telnet to quit like this:
curl telnet://10.1.1.1:23 <<EOF
remotecommand 1
remotecommand 2
quit
EOF
Following is working for me...
put all of your IPs you want to telnet in IP_sheet.txt
while true
read a
do
{
sleep 3
echo df -kh
sleep 3
echo exit
} | telnet $a
done<IP_sheet.txt
#!/bin/bash
ping_count="4"
avg_max_limit="1500"
router="sagemcom-fast-2804-v2"
adress="192.168.1.1"
user="admin"
pass="admin"
VAR=$(
expect -c "
set timeout 3
spawn telnet "$adress"
expect \"Login:\"
send \"$user\n\"
expect \"Password:\"
send \"$pass\n\"
expect \"commands.\"
send \"ping ya.ru -c $ping_count\n\"
set timeout 9
expect \"transmitted\"
send \"exit\"
")
count_ping=$(echo "$VAR" | grep packets | cut -c 1)
avg_ms=$(echo "$VAR" | grep round-trip | cut -d '/' -f 4 | cut -d '.' -f 1)
echo "1_____ping___$count_ping|||____$avg_ms"
echo "$VAR"
Use ssh for that purpose. Generate keys without using a password and place it to .authorized_keys at the remote machine. Create the script to be run remotely, copy it to the other machine and then just run it remotely using ssh.
I used this approach many times with a big success. Also note that it is much more secure than telnet.
Here is how to use telnet in bash shell/expect
#!/usr/bin/expect
# just do a chmod 755 one the script
# ./YOUR_SCRIPT_NAME.sh $YOUHOST $PORT
# if you get "Escape character is '^]'" as the output it means got connected otherwise it has failed
set ip [lindex $argv 0]
set port [lindex $argv 1]
set timeout 5
spawn telnet $ip $port
expect "'^]'."
Script for obtain version of CISCO-servers:
#!/bin/sh
servers='
192.168.34.1
192.168.34.3
192.168.34.2
192.168.34.3
'
user='cisco_login'
pass='cisco_password'
show_version() {
host=$1
expect << EOF
set timeout 20
set host $host
set user $user
set pass $pass
spawn telnet $host
expect "Username:"
send "$user\r"
expect "Password:"
send "$pass\r"
expect -re ".*#"
send "show version\r"
expect -re ".*-More-.*"
send " "
expect -re ".*#"
send "exit\r"
EOF
}
for ip in $servers; do
echo '---------------------------------------------'
echo "$ip"
show_version $ip | grep -A3 'SW Version'
done
Here is a solution that will work with a list of extenders. This only requires bash - some of the answers above require expect and you may not be able to count on expect being installed.
#!/bin/bash
declare -a Extenders=("192.168.1.48" "192.168.1.50" "192.168.1.51")
# "192.168.1.52" "192.168.1.56" "192.168.1.58" "192.168.1.59" "192.168.1.143")
sleep 5
# Iterate the string array using for loop
for val in ${Extenders[#]}; do
{ sleep 0.2; echo "root"; sleep 0.2; echo "ls"; sleep 0.2; } | telnet $val
done
Play with tcpdump or wireshark and see what commands are sent to the server itself
Try this
printf (printf "$username\r\n$password\r\nwhoami\r\nexit\r\n") | ncat $target 23
Some servers require a delay with the password as it does not hold lines on the stack
printf (printf "$username\r\n";sleep 1;printf "$password\r\nwhoami\r\nexit\r\n") | ncat $target 23**
I am trying to write a shell script that creates some directories on a remote server and then uses scp to copy files from my local machine onto the remote. Here's what I have so far:
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
Whenever I run it I get this message:
Pseudo-terminal will not be allocated because stdin is not a terminal.
And the script just hangs forever.
My public key is trusted on the server and I can run all the commands outside of the script just fine. Any ideas?
Try ssh -t -t(or ssh -tt for short) to force pseudo-tty allocation even if stdin isn't a terminal.
See also: Terminating SSH session executed by bash script
From 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.
Also with option -T from manual
Disable pseudo-tty allocation
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:
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
" 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
$( begins a command to be executed, with the output being captured by the surrounding interpolated string
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
<< begins a bash heredoc
'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
Any content that is encountered between <<'EOT' and <newline>EOT<newline> will be appended to the nowdoc output
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
) concludes the command to be executed
" 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}"
I'm adding this answer because it solved a related problem that I was having with the same error message.
Problem: I had installed cygwin under Windows and was getting this error: Pseudo-terminal will not be allocated because stdin is not a terminal
Resolution: It turns out that I had not installed the openssh client program and utilities. Because of that cygwin was using the Windows implementation of ssh, not the cygwin version. The solution was to install the openssh cygwin package.
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 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: 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.
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"
I don't know where the hang comes from, but redirecting (or piping) commands into an interactive ssh is in general a recipe for problems. It is more robust to use the command-to-run-as-a-last-argument style and pass the script on the ssh command line:
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 in one giant '-delimited multiline command-line argument).
The pseudo-terminal message is because of your -t which asks ssh to try to make the environment it runs on the remote machine look like an actual terminal to the programs that run there. Your ssh client is refusing to do that because its own standard input is not a terminal, so it has no way to pass the special terminal APIs onwards from the remote machine to your actual terminal at the local end.
What were you trying to achieve with -t anyway?
After reading a lot of these answers I thought I would share my resulting solution. All I added is /bin/bash before the heredoc and it doesn't give the error anymore.
Use this:
ssh user#machine /bin/bash <<'ENDSSH'
hostname
ENDSSH
Instead of this (gives error):
ssh user#machine <<'ENDSSH'
hostname
ENDSSH
Or use this:
ssh user#machine /bin/bash < run-command.sh
Instead of this (gives error):
ssh user#machine < run-command.sh
EXTRA:
If you still want a remote interactive prompt e.g. if the script you're running remotely prompts you for a password or other information, because the previous solutions won't allow you to type into the prompts.
ssh -t user#machine "$(<run-command.sh)"
And if you also want to log the entire session in a file logfile.log:
ssh -t user#machine "$(<run-command.sh)" | tee -a logfile.log
I was having the same error under Windows using emacs 24.5.1 to connect to some company servers through /ssh:user#host. What solved my problem was setting the "tramp-default-method" variable to "plink" and whenever I connect to a server I ommit the ssh protocol. You need to have PuTTY's plink.exe installed for this to work.
Solution
M-x customize-variable (and then hit Enter)
tramp-default-method (and then hit Enter again)
On the text field put plink and then Apply and Save the buffer
Whenever I try to access a remote server I now use C-x-f /user#host: and then input the password. The connection is now correctly made under Emacs on Windows to my remote server.