Execute command in a different directory

TL;DR

How do you execute a command/script in a different directory and stay where you were earlier?

(cd path/to/some/dir && ./some-script)

Problem: Dance of cd

With Monorepo setup in many tech companies, we often run into situations where the command/script we want to run is not located (or not runnable) in the same directory where the terminal is (most likely the root directory of the repo).

Let’s say we have a monorepo project some-awesome-project with both iOS and Android child directories:

├── android
│   ├── build
│   ├── gradle
│   ├── gradlew
│   ├── lib
│   └── settings.gradle.kts
├── ios
│   ├── Package.swift
│   ├── README.md
│   ├── Sources
│   └── Tests
└── scripts
└── some-script.sh

To build the Android project, we have to run ./gradlew build in the android directory because that’s where settings.gradle.kts is located. If we run ./android/gradlew build directly at the root directory of the repo, we will end up with an error complaining the root directory does not contain a Gradle build.

To avoid the build error, we have to do a dance of directory changing like this:

  1. Change directory: cd android
  2. Run the command ./gradlew build
  3. cd .. back to where we were earlier because many other commands of our day-to-day workflow assume the terminal stays at the root directory of a repo

Solution: Subshell

To avoid the cd dance back and forth, we can run the command in a subshell by enclosing the command inside parentheses:

(cd path/to/some/dir && ./some-script)

Because the directory change made in a subshell does not carry over to the parent shell, it will stay in the same directory after the command in the subshell finishes.

Going back to our example, we can simply run (cd android && ./gradlew build) directly at root directory:

$ pwd
/Developer/some-awesome-project

$ (cd android && ./gradlew build)

BUILD SUCCESSFUL in 977ms
5 actionable tasks: 5 executed

$ pwd
/Developer/some-awesome-project

References