vim + COPY + mac over SSH - macos

I access a sever over ssh on which I run vim for editing files. When I try to yank text from vim into an editor locally on my mac (lion) either with y OR "+y it does not work. I end up with the text I copied last locally. It does work if I just use p within vim alright.

To expand on Ray's answer…
When you are using Vim on a remote server via SSH, everything you do in Vim is done on the remote server. The remote server and the remote Vim that you are running on it have zero practical knowledge of your local computer and its system clipboard.
Because of that, y will never put the yanked text in your local clipboard.
In order to copy a chunk of text from the remote Vim to your local machine's clipboard you have three options:
Select the text with your mouse and hit Cmd+C like in any Mac OS X application.
Obviously, it seems to be the easiest but it has at least three limitations:
It is limited to the current screen. If the text you want to yank is not displayed entirely you won't be able to copy all of it.
It doesn't play well with set mouse=a. With this option, any attempt to select something with the mouse will result in a visual mode selection which can't be copied with Cmd+C. As a workaround, you can use Alt+mouse to select the text without entering visual mode or simply remove this setting from your remote ~/.vimrc.
Line numbers are copied as well.
Put the yanked text in a temporary file, scp it to your local machine and use pbcopy to put it in your system clipboard.
This solution seems to be a little convoluted but it works (and the problem itself is also a little bit convoluted). Over the years I've seen a lot of different implementations ranging from simple one liners to client/server setups. Here is one, feel free to google around for others.
Use X-forwarding to connect your local clipboard to the remote clipboard if available.

Had this problem - log in from OSX over SSH to a linux box and cannot copy text from a file, opened with vim.
My workaround is :set mouse=i
By default mouse is enabled in all modes. When you set it to be enabled only in Insert mode you can scroll around and copy when you are not editing (normal mode) but when you start editing (by hitting the I or Insert key) and enter insert mode the mouse acts as cursor placement and you cannot copy from terminal.
You can set that option in ~/.vimrc
See :help mouse for more information about the values you can set and the modes.

My first answer on stackoverflow, but I feel it's a cool (albeit tiny) trick and it's worth posting. So here's what I do :
cat <filename>
When the text is printed onto the terminal, I select all the text with my mouse (the mouse scroll works since we're on the terminal window). Then copy that text with Cmd+C and paste into my local text editor.
The only flaw with this trick is that it's impractical to use if your files are tens of thousands of lines long since selecting all the lines with your mouse would be a task in itself. But for a file of ~2k lines it works well.

My go-to solution is to edit the file with vim from your local machine via scp.
:e scp://remoteuser#server.tld//path/to/document
This keeps your buffer local and makes it easy to copy to your local clipboard.
The other advantage is that you get to use your local vim setup (.vimrc settings, plugins, etc.)

iTerm2 Shell Utilities come with it2copy which allows copying from a remote server to a client clipboard. Install Shell Utilities on the remote server and make sure you have Applications in terminal may access clipboard checked.
Then, go into visual mode, select the text and execute <,'>:w !it2copy

https://github.com/ojroques/vim-oscyank + iTerm2 + tmux worked in my case, this is my workflow:
ssh into the remote host
open a new tmux session
open vim (or neovim) with https://github.com/ojroques/vim-oscyank already installed and configured as the docs say
Some parts of my .vimrc
Plug 'ojroques/vim-oscyank' " clipboard over ssh through tmux
" yank operation don't need to use the *" register (system clipboard)
set clipboard+=unnamedplus
autocmd TextYankPost * if v:event.operator is 'y' && v:event.regname is '' | OSCYankReg " | endif

Yanking within vi in a terminal to which you ssh'd into copies the lines into vi's internal buffer on the remote machine, not into your Mac's clipboard.
Use your mouse. :)

On MacOS, when SSH from machine A to machine B and using vim in machine B, I add this to my .vimrc in machine B:
nmap yr :call system("ssh $machineA_IP pbcopy", #*)<CR>
That way, in normal mode, if you copy something to * register, then type yr, the content of * register in vim#machine_B is copied to machine A's local clipboard, assuming you have setup Vim correctly with +clipboard and * register

Here's an update on the solution #2 from romainl. It creates and alias of the ssh command and launches the remotecopyserver if it's not running and installs the remotecopy(rclip) in the remote server. In short, you don't have to do anything except paste the code snippet below into your bash_profile.
######################## For SSH Remote Copy #########################
export LC_SETUP_RC='command -v rclip >/dev/null 2>&1 || { echo "executing"; mkdir -p /usr/local/bin; if [ ! -f /usr/local/bin/rclip ];then wget https://raw.githubusercontent.com/justone/remotecopy/master/remotecopy -P /usr/local/bin/; ln -s /usr/local/bin/remotecopy /usr/local/bin/rclip; chmod +x /usr/local/bin/remotecopy; fi; if [[ \":\$PATH:\" == *\"/usr/local/bin:\"* ]]; then export PATH=/usr/local/bin:$PATH; fi } > /var/log/rclip.log 2>&1 || echo "Some error occured in setting up rclip. check /var/log/rclip.log"'
ssh_function() {
count="`ps -eaf | grep remotecopyserver | grep -v grep | wc -l`";
if [ "$count" -eq "0" ]; then
mkdir -p $HOME/bin;
if [ ! -f $HOME/bin/remotecopyserver ]; then
wget https://raw.githubusercontent.com/justone/remotecopy/master/remotecopyserver -P $HOME/bin;
chmod +x $HOME/bin/remotecopyserver;
fi;
nohup $HOME/bin/remotecopyserver &
fi;
ssh_cmd=`which ssh`
PARAMS=""
for PARAM in "$#"
do
PARAMS="${PARAMS} \"${PARAM}\""
done
bash -c "ssh ${PARAMS} -R 12345:localhost:12345 -t 'echo \$LC_SETUP_RC | sudo bash; bash -l'"
}
alias ssho=`which ssh`
alias ssh=ssh_function
alias ssh2=ssh_function
vssh_function() {
ssh_config=`vagrant ssh-config`;
if [ "$?" -eq "1" ]; then
echo "Problem with Vagrant config. run 'vagrant ssh-config' to debug"
return 1
fi
PORT=`echo "$ssh_config" | grep Port | grep -o "[0-9]\+"`;
ID_FILE=`echo "$ssh_config" | grep IdentityFile | awk '{print $2}'`
ssh2 -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PasswordAuthentication=no -i $ID_FILE vagrant#localhost -p $PORT "$#"
}
alias vssh=vssh_function
Once the alias is activated, you can normally use ssh and whenever you need to copy to local clipboard from vim, use
:w !rclip
to copy the whole file to clipboard
:'<,'> !rclip
to copy selected lines from visual mode. You have to press "Cmd+V" or "Ctrl+V" whenever it asks for the secret key.
Bonus
For those who work on Vagrant, there's a wrapper vssh which will execute vagrant ssh but also launches and install the necessary components.
Reference
Latest code snippet here - https://gist.github.com/ningsuhen/7933b206b92fc57364b2
http://endot.org/2011/12/04/remotecopy-copy-from-remote-terminals-into-your-local-clipboard/
https://github.com/justone/remotecopy
Caveats
The alias wraps the ssh command and there might be some unexpected issues. ssho is available if you need to execute ssh without the whole remotecopyserver thing. Alternatively, you can use the alias ssh2 and keep the ssh command as it is.

One trick which i use often during copying vim text using mouse if number of lines get little over-flown my screen is to minimize (Cmd + '-') the text. :) Minimize so much that you can not see by eyes but you can copy all the text in one go.

Instructions for mac + ssh + iterm2 + neovim:
Ssh to your server.
Install iterm shell integration.
Add ~/.iterm2 to the $PATH variable.
Create a ttyup command in e.g. ~/.local/bin.
Add ~/.local/bin to $PATH, if it's not already.
Add the g:clipboard declaration to your init.vim.
ttyup:
#!/bin/bash
# #medgar's ttyup script for passing the output to the parent shell
parent() { awk '{print $4}' "/proc/$1/stat"; }
leader() { awk '{print $6}' "/proc/$1/stat"; }
it2copy > "/proc/$(parent $(leader $$))/fd/0"
init.vim:
let g:clipboard = {
\ 'name': 'myClipboard',
\ 'copy': {
\ '+': ['ttyup'],
\ '*': ['ttyup'],
\ },
\ 'paste': {
\ '+': '+',
\ '*': '*',
\ },
\ 'cache_enabled': 0,
\ }

Or, from the terminal, write less [filename] to get it written to the terminal. Then start by selecting with your mouse, while you hold down-arrow-key down. Then you can select the whole bunch.

I was hoping to improve on my solution here, but instead will share it as it seems on par with some of the others.
When using iTerm2 on a Mac, sshing into machines, running tmux, and then editing with Vim, I have a vertical split iTerm2 window on the Mac that I pull all the way off to the side to make it as skinny as possible.
Then when I want to copy text from Vim, I will click into the tiny slice of iTerm2 window, and go back over and highlight and then copy the text from Vim. This works the best for single lines of text.
If there is a tmux vertical split, highlighting multiple lines in the Vim buffer won't wrap properly, and will copy text from the other tmux window, but otherwise this is great for copying 90% of what I need, without having to exit Vim, cat a file, or do something else.
I also have ample horizontal window space, making the small iTerm2 window not a space hog.

Just use MobaXterm.
I tried lots of ways and none of them is easy. So the only solution I could find is using
MobaXterm. You can see all of the files and open them any way you want.

The easiest way to copy to local machine is via selecting with mouse and then CMD + c to copy. As is noted in another answer this has a few limitations/drawbacks. Consequently, I sought out the alternatives, but after trying for hours (unsuccessfully) to get X11 forwarding working, I instead came up with the following simple workaround (at least for the line-numbering caveat) for aid in mouse copying.
Add the following to your .vimrc to easily toggle line numbers for mouse copying:
nnoremap L :set invnumber <CR>
You can easily change the L to any key of your choice that you wish to be the shortcut key.

#andrewgazelka had a great solution
I don't have it2copy script: https://github.com/gnachman/iTerm2-shell-integration/blob/main/utilities/it2copy
Here is the code I downloaded from :)
Hope someone will feel it is helpful :)
I moved it2copy in my /usr/bin directory for both my server and macbook

Try the other clipboard register - "*y.

Related

How to develop on a virtual machine?

I want to edit source code on a virtual machine running Ubuntu. I'm ssh'd into it to preserve my native (mac) key bindings (e.g. copy and paste). Can I route terminal data through to sublime on the host side? Can I invoke the sublime editor in the vm and use it with X11 forwarding?
I'm just looking for the common workflow where I can keep using the key-bindings and editor I've learned to love.
You can use Samba share for this. (Assuming that you are running windows).
That's the way I do it.
How to Create a Network Share Via Samba Via CLI (Command-line interface/Linux Terminal) - Uncomplicated, Simple and Brief Way!
or you can check How to map a network drive?
You have a few options:
You can install Samba for SMB network sharing, or Netatalk for AFP network sharing, as suggested in the answer by Ahmed Daou. Probably the most flexible solution once set up. If you want to get fancy you can also install avahi-daemon so that you can browse to network files from the Finder, as you would any other file share; otherwise you can use "Connect To Server..." from the Finder's Go menu.
You can round-trip it with scp. Install a public-private keypair for SSH so you don't have to enter a password every time out (even if you use a passphrase, your Mac will remember it in its keychain). When you quit Sublime, the saved file will be will copied back to the VM.
Example sublime_open script:
#!/bin/bash
serverAddress="username#myvm.local"
if [[ ! $1 ]]; then
echo "usage: sublime_open <remoteFilename> [username#serverAddress] [serverPort]"
exit 1
fi
[[ $2 ]] && serverAddress="$2"
[[ $3 ]] && serverPort="-P $3" || serverPort=
scp $serverPort $serverAddress:"$1" /tmp/myfile.txt
/Applications/"Sublime Text.app"/Contents/MacOS/"Sublime Text" /tmp/myfile.txt
scp $serverPort /tmp/myfile.txt $serverAddress:"$1"
rm /tmp/myfile.txt &> /dev/null
You can, as you suggested, use X11 and run the Linux version. Install XQuartz on your Mac, and then open an SSH session to your VM with the -X option, e.g. ssh -X myvm.local. Then in that Terminal window run ./sublime_text from whatever directory it lives in (if you install the Ubuntu/Debian package, it appears to be in /opt/sublime_text/sublime_text). It will open in an X window on your Mac.

Bash eval inside quotes

I have a bash script which takes one parameter and does something like this:
ssh -t someserver "setenv DISPLAY $1; /usr/bin/someprogram"
How can I force bash to substitute in the $1 instead of passing the literal characters "$1" as the display variable?
Based on your comment on sehe's answer, it sounds like you just want the remote command to use the local X display — so that the program is running on your remote server (someserver) but being displayed on the machine you ran the ssh command on.
This can be done by just passing -X, e.g.
ssh -X someserver /usr/bin/someprogram
For some reason, this doesn't work with a few programs, for example evince. I'm not really sure why. I'm pretty sure that evince is the only app I've had trouble forwarding back over an SSH connection.
If this isn't what you're aiming to do, please explain.
Edit Are you aware of
ssh -X ...
ssh -Y ...
which already support X forwarding out of the box? Also look at
xhost +
in case you need to increase permissions to 'guests'.
If you want to forward non-standard X display address, you could always use
DISPLAY=localhost:3 ssh -XCt user#remote xterm
Bonus: to make ssh background after authentication, add '-f'
What locally? That should already work as shown. Remotely? escape the $: \$
However, I'm not sure where the command would be taking it's arguments ($1) from

Opening a remote file with TextWrangler

My current solution for editing files on a remote web server is to use Fetch to browse the remote machine and TextWrangler to make the edits. But since I'm getting more comfortable navigating the command line on the remote machine (but not comfortable enough to use VIM...), I'd like to be able to type something like 'open filename.txt' on the remote machine and have TextWrangler open up on my local machine. I've heard the term "reverse tunneling" tossed around as an option, but I have no idea what to do next. Any suggestions are greatly appreciated - thanks!
Personally, I use Cyberduck as my S/FTP browser. In Cyberduck's preferences, you can define a default text editor to edit remote files. Now I can just hit Cmd+K when I have a file selected, and it will open up in TextWrangler. Whenever I save, the changes are automatically transferred to the remote file.
I was actually looking to do the same thing, and no one had written it up, so I figured this out today.
There's 2 required and 3 optional parts to this:
Enable ssh login on both computers (required)
Set up an ssh tunnel from the remote machine to your machine (required)
Set up an alias for the ssh tunnel (optional)
Set up an alias for TextWrangler on the remote machine (optional)
Set up ssh keys so you don't have to enter your password every time (optional)
You need to be able to ssh from local to remote to run the commands, and you need to be able to ssh from remote to local so it can send commands to TextWrangler.
To set up the ssh tunnel, you need to run a command on your local machine like:
ssh -f -N -R 10022:localhost:22 [username on remote machine]#[remote machine hostname]
The -f and -N flags put ssh into the background and leave you on your machine. The -R flag binds a port on the remote computer to a port on your local computer. Anything contacting the remote machine on port 10022 will be sent to port 22 on your local computer. The remote port can be anything you want, but you should choose a port > 1024 to avoid conflicts and so you don't have to be root. I chose 10022 because it's similar to ssh's default port of 22. Replace the brackets with your username and machine name.
You'll need to run that once after you log in. To make the command easier on yourself, you can add an alias in your bash profile. Add the following to your local ~/.bash_profile:
alias open-tunnel='ssh -f -N -R 10022:localhost:22 [username on remote machine]#[remote machine hostname]'
Of course, you can choose whatever alias name you like.
Once you've set up the tunnel, you can use a command like this on the remote machine:
ssh -p 10022 [username on local machine]#localhost "edit sftp://[username on remote machine]#[remote machine hostname]//absolute/path/to/file.txt"
The -p flag says to use port 10022 (or whichever port you chose earlier). This will cause the remote machine to connect to your local machine and execute the command in the double quotes without opening an interactive ssh session. The command in the quotes is the command you would run on your local machine to open the remote file in TextWrangler.
To make the command easier on yourself, you can add a function in your bash profile. Add the following to your remote ~/.bash_profile:
function edit { if [[ ${1:0:1} = "/" ]]; then abs_path="$1"; else abs_path="`pwd`/$1"; fi; ssh -p 10022 [username on local machine]#localhost "edit sftp://[username on remote machine]#[remote machine hostname]/$abs_path"; }
This is assuming that you don't have the TextWrangler command line tools installed on the remote machine. If you do, you should name the function something other than edit. For example, tw. Here, ${1:0:1} looks at the first character of the first parameter of the function, which should be the file path. If it doesn't begin with /, we figure out the absolute path by adding the current working directory (pwd) to the beginning. Now, if you're on the remote machine in /home/jdoe/some/directory/ and you run edit some/other/directory/file.txt, the following will be executed on your local machine:
edit sftp://[username on remote machine]#[remote machine hostname]//home/jdoe/some/directory/some/other/directory/file.txt
Lastly, you should set up ssh keys in both directions so you're not prompted for a password every single time. Here's a guide someone else wrote: http://pkeck.myweb.uga.edu/ssh/
I dont think this will allow opening from the command-line, but
Eclipse with Remote-System-Explorer also supports editing of files via ssh connection
I think what you're referring to is called "X11 forwarding" over ssh. Take a look at the ssh_config(5) manpage for configuration (or just use 'ssh' with the '-X' parameter). As far as i know, this does only work with X11 programs (gvim, xemacs, etc.), because the editor is actually running on the host you're connecting to - only the display stuff happens on your local machine. So TextWrangler is not an option, because it's not an X11 program.
I use Interarchy (from nolobe) for remote editing. It's a fairly advanced ftp/sftp client that gives you a finder-style view of your remote files and allows you to use your favourite editor to work on those files.
If you don't like to pay for such a program, there's an Open-Source program called "Fugu" available from the Univerity of Michigan which you can also use.
FileZilla offers this functionality as well. You can download it here. Once you've connected to your sftp you can right-click on the text file and open it with the text editor of your choice.
Minimal answer
You can use Applescript. So from the command line execute this:
osascript <<EOF
tell application "TextWrangler"
activate
open location {"sftp://myusername:#my.server:22222//home/username/.bashrc"}
end tell
EOF
Notes
Obviously you wouldn't want to type a here document on every invocation, so my suggestion would be to put this logic inside a regular shell script:
osascript <<EOF
tell application "TextWrangler"
activate
open location {"$1"}
end tell
EOF
Then invoke the script like this:
sh ~/bin/textwrangler.sh "sftp://myusername:#my.server:22222//home/username/.bashrc"
Specifying a host-qualified path can get tedious each time so either hardcode that in your script, or bind the script invocation to a keystroke via your shell. For bash:
bind '"\et":"sh ~/bin/textwrangler.sh \"sftp://myusername:#my.server:22222/\""'
Now you generate the majority of the command by pressing Alt-t

How can I ssh directly to a particular directory?

I often have to login to one of several servers and go to one of several directories on those machines. Currently I do something of this sort:
localhost ~]$ ssh somehost
Welcome to somehost!
somehost ~]$ cd /some/directory/somewhere/named/Foo
somehost Foo]$
I have scripts that can determine which host and which directory I need to get into but I cannot figure out a way to do this:
localhost ~]$ go_to_dir Foo
Welcome to somehost!
somehost Foo]$
Is there an easy, clever or any way to do this?
You can do the following:
ssh -t xxx.xxx.xxx.xxx "cd /directory_wanted ; bash --login"
This way, you will get a login shell right on the directory_wanted.
Explanation
-t Force pseudo-terminal 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.
If you don't use -t then no prompt will appear.
If you don't add ; bash then the connection will get closed and return control to your local machine
If you don't add bash --login then it will not use your configs because its not a login shell
You could add
cd /some/directory/somewhere/named/Foo
to your .bashrc file (or .profile or whatever you call it) at the other host. That way, no matter what you do or where you ssh from, whenever you log onto that server, it will cd to the proper directory for you, and all you have to do is use ssh like normal.
Of curse, rogeriopvl's solution works too, but it's a tad bit more verbose, and you have to remember to do it every time (unless you make an alias) so it seems a bit less "fun".
My preferred approach is using the SSH config file (described below), but there are a few possible solutions depending on your usages.
Command Line Arguments
I think the best answer for this approach is christianbundy's reply to the accepted answer:
ssh -t example.com "cd /foo/bar; exec \$SHELL -l"
Using double quotes will allow you to use variables from your local machine, unless they are escaped (as $SHELL is here). Alternatively, you can use single quotes, and all of the variables you use will be the ones from the target machine:
ssh -t example.com 'cd /foo/bar; exec $SHELL -l'
Bash Function
You can simplify the command by wrapping it in a bash function. Let's say you just want to type this:
sshcd example.com /foo/bar
You can make this work by adding this to your ~/.bashrc:
sshcd () { ssh -t "$1" "cd \"$2\"; exec \$SHELL -l"; }
If you are using a variable that exists on the remote machine for the directory, be sure to escape it or put it in single quotes. For example, this will cd to the directory that is stored in the JBOSS_HOME variable on the remote machine:
sshcd example.com \$JBOSS_HOME
SSH Config File
If you'd like to see this behavior all the time for specific (or any) hosts with the normal ssh command without having to use extra command line arguments, you can set the RequestTTY and RemoteCommand options in your ssh config file.
For example, I'd like to type only this command:
ssh qaapps18
but want it to always behave like this command:
ssh -t qaapps18 'cd $JBOSS_HOME; exec $SHELL'
So I added this to my ~/.ssh/config file:
Host *apps*
RequestTTY yes
RemoteCommand cd $JBOSS_HOME; exec $SHELL
Now this rule applies to any host with "apps" in its hostname.
For more information, see http://man7.org/linux/man-pages/man5/ssh_config.5.html
I've created a tool to SSH and CD into a server consecutively – aptly named sshcd. For the example you've given, you'd simply use:
sshcd somehost:/some/directory/somewhere/named/Foo
Let me know if you have any questions or problems!
Based on additions to #rogeriopvl's answer, I suggest the following:
ssh -t xxx.xxx.xxx.xxx "cd /directory_wanted && bash"
Chaining commands by && will make the next command run only when the previous one was successful (as opposed to using ;, which executes commands sequentially). This is particularly useful when needing to cd to a directory performing the command.
Imagine doing the following:
/home/me$ cd /usr/share/teminal; rm -R *
The directory teminal doesn't exist, which causes you to stay in the home directory and remove all the files in there with the following command.
If you use &&:
/home/me$ cd /usr/share/teminal && rm -R *
The command will fail after not finding the directory.
In my very specific case, I just wanted to execute a command in a remote host, inside a specific directory from a Jenkins slave machine:
ssh myuser#mydomain
cd /home/myuser/somedir
./commandThatMustBeRunInside_somedir
exit
But my machine couldn't perform the ssh (it couldn't allocate a pseudo-tty I suppose) and kept me giving the following error:
Pseudo-terminal will not be allocated because stdin is not a terminal
I could get around this issue passing "cd to dir + my command" as a parameter of the ssh command (to not have to allocate a Pseudo-terminal) and by passing the option -T to explicitly tell to the ssh command that I didn't need pseudo-terminal allocation.
ssh -T myuser#mydomain "cd /home/myuser/somedir; ./commandThatMustBeRunInside_somedir"
I use the environment variable CDPATH
going one step further with the -t idea. I keep a set of scripts calling the one below to go to specific places in my frequently visited hosts. I keep them all in ~/bin and keep that directory in my path.
#!/bin/bash
# does ssh session switching to particular directory
# $1, hostname from config file
# $2, directory to move to after login
# can save this as say 'con' then
# make another script calling this one, e.g.
# con myhost repos/i2c
ssh -t $1 "cd $2; exec \$SHELL --login"
My answer may differ from what you really want, but I write here as may be useful for some people. In my solution you have to enter into the directory once and then every new ssh session goes to the same dir (after the first logout).
How to ssh to the same directory you have been in your last login.
(I assume you use bash on the remote node.)
Add this line to your ~/.bash_logout on the remote node(!):
echo $PWD > ~/.bash_lastpwd
and these lines to the ~/.bashrc file (still on the remote node!)
if [ -f ~/.bash_lastpwd ]; then
cd $(cat ~/.bash_lastpwd)
fi
This way you save your current path on every logout and .bashrc put you into that directory after login.
ps: You can tweak it further like using the SSH_CLIENT variable to decide to go into that directory or not, so you can differentiate between local logins and ssh or even between different ssh clients.
Another way of going to directly after logging in is create "Alias". When you login into your system just type that alias and you will be in that directory.
Example : Alias = myfolder '/var/www/Folder'
After you log in to your system type that alias (this works from any part of the system)
this command if not in bashrc will work for current session. So you can also add this alias to bashrc to use that in future
$ myfolder => takes you to that folder
I know this has been answered ages ago but I found the question while trying to incorporate an ssh login in a bash script and once logged in run a few commands and log back out and continue with the bash script. The simplest way I found which hasnt been mentioned elsewhere because it is so trivial is to do this.
#!/bin/bash
sshpass -p "password" ssh user#server 'cd /path/to/dir;somecommand;someothercommand;exit;'
Connect With User
In case if you don't know this, you can use this to connect by specifying both user and host
ssh -t <user>#<Host domain / IP> "cd /path/to/directory; bash --login"
Example: ssh -t admin#test.com "cd public_html; bash --login"
You can also append the commands to be executed on every login by appending it in the double quotes with a ; before each command
Unfortunately, the suggested solution (of #rogeriopvl) doesn't work when you use multiple hops, so I found another one.
On remote machine add into ~/.bashrc the following:
[ "x$CDTO" != "x" ] && cd $CDTO
This allows you to specify the desired target directory on command line in this way:
ssh -t host1 ssh -t host2 "CDTO=/desired_directory exec bash --login"
Sure, this way can be used for a single hop too.
This solution can be combined with the usefull tip of #redseven for greater flexibilty (if no $CDTO, go to saved directory, if exists).
SSH itself provides a means of communication, it does not know anything about directories. Since you can specify which remote command to execute (this is - by default - your shell), I'd start there.
simply modify your home with the command:
usermod -d /newhome username

How can I automate running commands remotely over SSH to multiple servers in parallel?

I've searched around a bit for similar questions, but other than running one command or perhaps a few command with items such as:
ssh user#host -t sudo su -
However, what if I essentially need to run a script on (let's say) 15 servers at once. Is this doable in bash? In a perfect world I need to avoid installing applications if at all possible to pull this off. For argument's sake, let's just say that I need to do the following across 10 hosts:
Deploy a new Tomcat container
Deploy an application in the container, and configure it
Configure an Apache vhost
Reload Apache
I have a script that does all of that, but it relies on me logging into all the servers, pulling a script down from a repo, and then running it. If this isn't doable in bash, what alternatives do you suggest? Do I need a bigger hammer, such as Perl (Python might be preferred since I can guarantee Python is on all boxes in a RHEL environment thanks to yum/up2date)? If anyone can point to me to any useful information it'd be greatly appreciated, especially if it's doable in bash. I'll settle for Perl or Python, but I just don't know those as well (working on that). Thanks!
You can run a local script as shown by che and Yang, and/or you can use a Here document:
ssh root#server /bin/sh <<\EOF
wget http://server/warfile # Could use NFS here
cp app.war /location
command 1
command 2
/etc/init.d/httpd restart
EOF
Often, I'll just use the original Tcl version of Expect. You only need to have that on the local machine. If I'm inside a program using Perl, I do this with Net::SSH::Expect. Other languages have similar "expect" tools.
The issue of how to run commands on many servers at once came up on a Perl mailing list the other day and I'll give the same recommendation I gave there, which is to use gsh:
http://outflux.net/unix/software/gsh
gsh is similar to the "for box in box1_name box2_name box3_name" solution already given but I find gsh to be more convenient. You set up a /etc/ghosts file containing your servers in groups such as web, db, RHEL4, x86_64, or whatever (man ghosts) then you use that group when you call gsh.
[pdurbin#beamish ~]$ gsh web "cat /etc/redhat-release; uname -r"
www-2.foo.com: Red Hat Enterprise Linux AS release 4 (Nahant Update 7)
www-2.foo.com: 2.6.9-78.0.1.ELsmp
www-3.foo.com: Red Hat Enterprise Linux AS release 4 (Nahant Update 7)
www-3.foo.com: 2.6.9-78.0.1.ELsmp
www-4.foo.com: Red Hat Enterprise Linux Server release 5.2 (Tikanga)
www-4.foo.com: 2.6.18-92.1.13.el5
www-5.foo.com: Red Hat Enterprise Linux Server release 5.2 (Tikanga)
www-5.foo.com: 2.6.18-92.1.13.el5
[pdurbin#beamish ~]$
You can also combine or split ghost groups, using web+db or web-RHEL4, for example.
I'll also mention that while I have never used shmux, its website contains a list of software (including gsh) that lets you run commands on many servers at once. Capistrano has already been mentioned and (from what I understand) could be on that list as well.
Take a look at Expect (man expect)
I've accomplished similar tasks in the past using Expect.
You can pipe the local script to the remote server and execute it with one command:
ssh -t user#host 'sh' < path_to_script
This can be further automated by using public key authentication and wrapping with scripts to perform parallel execution.
You can try paramiko. It's a pure-python ssh client. You can program your ssh sessions. Nothing to install on remote machines.
See this great article on how to use it.
To give you the structure, without actual code.
Use scp to copy your install/setup script to the target box.
Use ssh to invoke your script on the remote box.
pssh may be interesting since, unlike most solutions mentioned here, the commands are run in parallel.
(For my own use, I wrote a simpler small script very similar to GavinCattell's one, it is documented here - in french).
Have you looked at things like Puppet or Cfengine. They can do what you want and probably much more.
For those that stumble across this question, I'll include an answer that uses Fabric, which solves exactly the problem described above: Running arbitrary commands on multiple hosts over ssh.
Once fabric is installed, you'd create a fabfile.py, and implement tasks that can be run on your remote hosts. For example, a task to Reload Apache might look like this:
from fabric.api import env, run
env.hosts = ['host1#example.com', 'host2#example.com']
def reload():
""" Reload Apache """
run("sudo /etc/init.d/apache2 reload")
Then, on your local machine, run fab reload and the sudo /etc/init.d/apache2 reload command would get run on all the hosts specified in env.hosts.
You can do it the same way you did before, just script it instead of doing it manually. The following code remotes to machine named 'loca' and runs two commands there. What you need to do is simply insert commands you want to run there.
che#ovecka ~ $ ssh loca 'uname -a; echo something_else'
Linux loca 2.6.25.9 #1 (blahblahblah)
something_else
Then, to iterate through all the machines, do something like:
for box in box1_name box2_name box3_name
do
ssh $box 'commmands_to_run_everywhere'
done
In order to make this ssh thing work without entering passwords all the time, you'll need to set up key authentication. You can read about it at IBM developerworks.
You can run the same command on several servers at once with a tool like cluster ssh. The link is to a discussion of cluster ssh on the Debian package of the day blog.
Well, for step 1 and 2 isn't there a tomcat manager web interface; you could script that with curl or zsh with the libwww plug in.
For SSH you're looking to:
1) not get prompted for a password (use keys)
2) pass the command(s) on SSH's commandline, this is similar to rsh in a trusted network.
Other posts have shown you what to do, and I'd probably use sh too but I'd be tempted to use perl like ssh tomcatuser#server perl -e 'do-everything-on-one-line;' or you could do this:
either scp the_package.tbz tomcatuser#server:the_place/.
ssh tomcatuser#server /bin/sh <<\EOF
define stuff like TOMCAT_WEBAPPS=/usr/local/share/tomcat/webapps
tar xj the_package.tbz or rsync rsync://repository/the_package_place
mv $TOMCAT_WEBAPPS/old_war $TOMCAT_WEBAPPS/old_war.old
mv $THE_PLACE/new_war $TOMCAT_WEBAPPS/new_war
touch $TOMCAT_WEBAPPS/new_war [you don't normally have to restart tomcat]
mv $THE_PLACE/vhost_file $APACHE_VHOST_DIR/vhost_file
$APACHECTL restart [might need to login as apache user to move that file and restart]
EOF
You want DSH or distributed shell, which is used in clusters a lot. Here is the link: dsh
You basically have node groups (a file with lists of nodes in them) and you specify which node group you wish to run commands on then you would use dsh, like you would ssh to run commands on them.
dsh -a /path/to/some/command/or/script
It will run the command on all the machines at the same time and return the output prefixed with the hostname. The command or script has to be present on the system, so a shared NFS directory can be useful for these sorts of things.
Creates hostname ssh command of all machines accessed.
by Quierati
http://pastebin.com/pddEQWq2
#Use in .bashrc
#Use "HashKnownHosts no" in ~/.ssh/config or /etc/ssh/ssh_config
# If known_hosts is encrypted and delete known_hosts
[ ! -d ~/bin ] && mkdir ~/bin
for host in `cut -d, -f1 ~/.ssh/known_hosts|cut -f1 -d " "`;
do
[ ! -s ~/bin/$host ] && echo ssh $host '$*' > ~/bin/$host
done
[ -d ~/bin ] && chmod -R 700 ~/bin
export PATH=$PATH:~/bin
Ex Execute:
$for i in hostname{1..10}; do $i who;done
There is a tool called FLATT (FLexible Automation and Troubleshooting Tool) that allows you to execute scripts on multiple Unix/Linux hosts with a click of a button. It is a desktop GUI app that runs on Mac and Windows but there is also a command line java client.
You can create batch jobs and reuse on multiple hosts.
Requires Java 1.6 or higher.
Although it's a complex topic, I can highly recommend Capistrano.
I'm not sure if this method will work for everything that you want, but you can try something like this:
$ cat your_script.sh | ssh your_host bash
Which will run the script (which resides locally) on the remote server.
Just read a new blog using setsid without any further installation/configuration besides the mainstream kernel. Tested/Verified under Ubuntu14.04.
While the author has a very clear explanation and sample code as well, here's the magic part for a quick glance:
#----------------------------------------------------------------------
# Create a temp script to echo the SSH password, used by SSH_ASKPASS
#----------------------------------------------------------------------
SSH_ASKPASS_SCRIPT=/tmp/ssh-askpass-script
cat > ${SSH_ASKPASS_SCRIPT} <<EOL
#!/bin/bash
echo "${PASS}"
EOL
chmod u+x ${SSH_ASKPASS_SCRIPT}
# Tell SSH to read in the output of the provided script as the password.
# We still have to use setsid to eliminate access to a terminal and thus avoid
# it ignoring this and asking for a password.
export SSH_ASKPASS=${SSH_ASKPASS_SCRIPT}
......
......
# Log in to the remote server and run the above command.
# The use of setsid is a part of the machinations to stop ssh
# prompting for a password.
setsid ssh ${SSH_OPTIONS} ${USER}#${SERVER} "ls -rlt"
Easiest way I found without installing or configuring much software is using plain old tmux. Say you have 9 linux servers. Pick a box as your main. Start a tmux session:
tmux
Then create 9 split tmux panes by doing this 8 times:
ctrl-b + %
Now SSH into each box in each pane. You'll need to know some tmux shortcuts. To navigate, press:
ctrl+b <arrow-keys>
Once your logged in to all your boxes on each pane. Now turn on pane synchronization where it lets you type the same thing into each box:
ctrl+b :setw synchronize-panes on
now when you press any keys, it will show up on every pane. to turn it off, just make on to off. to cycle resize panes, press ctrl+b < space-bar >.
This works alot better for me since I need to see each terminal output as sometimes servers crash or hang for whatever reason when downloading or upgrade software. Any issues, you can just isolate and resolve individually.

Resources