/bash-mosh

Bash: Run shell commands in multiple directories grouped by tags

Primary LanguageShellMIT LicenseMIT

Mosh: Run shell commands in multiple directories grouped by tags

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

screencast

  • Manage multiple Git repositors together
    • push and pull 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

Installation

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:

Usage

Run: Execute commands in multiple directories

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"

Filtering the directory list

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

Execute in the most recently used directories

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

Execute non-interactively

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

Check the exit code of the previous command

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

Tag: Assign tags to directories

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.

Find Git repositories

Tagging Git repositories under the current directory (./) with "git-repos" tag:

$ find "./" -name ".git" -printf "%h\n" | mosh tag "@git-repos"

Tag new repositories automatically

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

FAQ

I use MinTTY on Windows and XY don't work or work differently

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.

Similar projects