For developers who running the same command in different directories repeatedly, Mosh is a productivity tool that saves time by executing the command without having to change the directory. Unlike other similar tools, Mosh does not bound to a certain software (like Git for example), it can execute any shell command. It works on any Bash-compitable shell (Bash, Zsh, Git Bash on Windows, etc.).
- Manage multiple Git repositors together
push
andpull
all of your repos at once- Checkout the same branch for project and its submodules
- Commit with the same message: walk through the repos without interruption and copy / paste the same commit message
- Prevent early push: you do not have to remember which repositories have modified, just look at them at the end of the day to see where you need to push
- Control multiple vagrant machines at the same time
The essence of the logic in a nutshell:
for dir in $selected_directories; do
cd $dir
$shell_command
done
Download the bin/mosh
script and place it somewhere on your PATH or use
Basher.
Get the source code, report bugs, open pull requests, or just star because you didn't know that you need it:
- https://github.com/bimlas/bash-mosh (please star if you like it)
Let's say that you have a bunch of directories that start with the same prefix
(g
) in /usr/share
. You want to issue the same commands to all of them.
You can use mosh run
with a wild card for the directory names you want to
interact with:
$ mosh run /usr/share/g*
This will open up an interactive terminal where you can run one or more commands. For example list the content of them:
mosh > ls
______________________________________________________________________________
@1 gdb (/usr/share)
auto-load
______________________________________________________________________________
@2 git (/usr/share)
msys2-32.ico ReleaseNotes.css
______________________________________________________________________________
@3 glib-2.0 (/usr/share)
gdb schemas
...
Exiting from the program is done with Control+D.
Of course, this could have been done with the ls /usr/share/g*
command.
The real targets of the program are the commands that can only be executed
in the current directory, so we should enter the directory before issuing the
command. As a useful example, we can check the status for a bunch of Git
repos.
$ mosh run ~/src/* "../wip-project"
mosh > git status --short --branch
______________________________________________________________________________
@1 awesome-project (/home/me/src/)
## master
AM README.md
M package.json
______________________________________________________________________________
@2 git-test (/home/me/src/)
## master...origin/master
M README.adoc
M encoding/cp1250-encoding-dos-eol.txt
M encoding/dos-eol.txt
______________________________________________________________________________
@3 wip-project (/home/me/helping-tom/)
## master...origin/master
M example-code.js
==============================================================================
mosh > another command and so on ...
Arguments can be tags and paths (see below for the description of tags).
$ mosh run "@git-repos" "../wip-project"
Since you will mostly use the run
command, you may want to assign an
alias to execute the command with less typing.
# Before
$ mosh run ~/src/* "../wip-project"
alias run="mosh run"
# After
$ run ~/src/* "../wip-project"
If you want to execute a command only in certain directories, you can select them by their index.
mosh > @1,3 git status --short --branch
______________________________________________________________________________
@1 awesome-project (/home/me/src/)
## master
AM README.md
M package.json
______________________________________________________________________________
@3 wip-project (/home/me/helping-tom/)
## master...origin/master
M example-code.js
This is useful if the output is long and you want to execute additional commands on certain directories. In this case, open a new terminal window (so you can look back at results in the current terminal) and run the program without arguments: the directory list is always stored when tag or directory arguments are given, but if you run it without arguments, it executes the commands on the last specified directories.
$ mosh run "@git-repos" "../wip-project"
mosh > git status --short --branch
______________________________________________________________________________
@1 awesome-project (/home/me/src/)
## master
AM README.md
...
# Another terminal
$ mosh run
mosh ! WARNING: Using most recently used directory list
mosh > @3 git diff
______________________________________________________________________________
@3 wip-project (/home/me/helping-tom/)
example-code.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/example-code.js b/example-code.js
index 12b5e40..733220f 100644
--- a/example-code.js
+++ b/example-code.js
...
If you want to use it within a script or in scheduled tasks, you can also send commands to stdin.
$ echo "cp -r ./ ~/image_backups" | mosh run "@pictures" > /dev/null
If a command requires the success of the same command in the previous directory, you can check the exit code.
mosh > if [[ $? == 0 ]]; then echo "Running"; non_existent_command; else echo "Not running"; fi
______________________________________________________________________________
@1 awesome-project (/home/me/src/)
Running
/bin/bash: non_existent_command: command not found
______________________________________________________________________________
@2 git-test (/home/me/src/)
Not running
______________________________________________________________________________
@3 wip-project (/home/me/helping-tom/)
Not running
To avoid having to always type the path of directories, you can assign them to tags (think of them as bookmarks).
The tags can be specified as command-line parameters prefixed with @
,
directories should be listed in the prompt, or piped to stdin.
$ mosh tag "@pictures" "@personal"
/home/myself/photos
/home/mom/my_little_family
../granny
$ echo "./" | mosh tag "@pictures" "@personal"
Tag files are stored in ~/.mosh
directory, you can edit them with any text
editor.
Tagging Git repositories under the current directory (./
) with
"git-repos" tag:
$ find "./" -name ".git" -printf "%h\n" | mosh tag "@git-repos"
In order not to miss any newly created or cloned repositories, one of the Git hooks (post-commit or pre-push for example) can be used to automatically assign the repository path to a default tag.
#!/bin/sh
if ( which mosh &> /dev/null ); then
echo "." | mosh tag "@git" &> /dev/null &
fi
Under MinTTY (default terminal emulator of Git
for Windows), it is not possible to identify exactly that stdin
is a
terminal or pipe (see https://duckduckgo.com/?q=MinTTY+is+not+a+TTY), so
some things may work differently than in other terminals. Try using another
terminal like system default, your IDE's builtin terminal,
Conemu,
Alacritty.