Docker shell shortcuts - because writing full commands is hard!
Tags: docker, devops, beginners, shell
Published: 2020-07-28 22:30:31

## Containers for fun and profit!
So you're now officially excited about Docker and containers.

Good! They're an awesome tool to create portable configurations for your software and make sure it works seamlessly across multiple devices.

If you've been around them long enough, though, you're probably using [Docker engine's CLI](https://docs.docker.com/engine/reference/commandline/cli/) (command line interface) quite often.

Commands like:

```shell
docker exec -ti 1x81hs72k2s9 /bin/bash
```

or

```shell
docker start container-name
```

Are probably all over your shell history.

I had the same problem, and so I made a small hack and uploaded it to my slowly-expanding Useful Snippets repository (which you can totally star if you feel like it :) ):

{% github https://github.com/tomgs/useful-snippets %} 

## Money time
Specifically, if you go [here](https://github.com/tomgs/useful-snippets/blob/master/docker-bashrc/docker-bashrc.sh) you'll see the following snippets:

```shell
#!/bin/bash

# Get container id of existing container
dgrep(){
    docker ps -a | grep "$1" | cut -c1-12
}

# bash into an existing container
dbash() {
    docker exec -ti $(dgrep "$1") /bin/bash
}

# sh into an existing container
dsh() {
    docker exec -ti $(dgrep "$1") /bin/sh
}

# Execute something in an existing container
dex() {
    docker exec -ti $(dgrep "$1") $2
}

# Start an existing container
dstart(){
    docker start $(dgrep "$1")
}

# Stop an existing container
dstop(){
    docker stop $(dgrep "$1")
}

# Remove an existing container
drm(){
    docker rm $(dgrep "$1")
}

# Stop and remove an existing container
dsrm(){
    dstop $(dgrep "$1") && drm $(dgrep "$1")
}

# Remove an existing image
dirm(){
    docker image remove $(dgrep "$1")
}

# Get logs of an existing container
dlog(){
    docker logs $(dgrep "$1")
}
```

These go inside your shell's configuration file, probably located at `~/.bashrc` if you're on one of the Linux distros and older macs, or `~/.zshrc` if you're on newer macs.

After you put those commands inside your shell configuration file, whenever your shell starts up they will become available for use inside your session - just like regular shell commands.

## Shell functions as command aliases

Note that what I did up there was define shell **functions** that perform specific commands, and take **arguments** just like regular shell commands.

This goes in contrast to defining shell _aliases_, like Nick Taylor does [here](https://dev.to/nickytonline/my-shell-aliases-1obk) (for the most part), and allows us to pass arguments to our "aliases".

This was a bit of a mouthful. Let's consider, as an example, the `dgrep` function I defined earlier:

```shell
# Get container id of existing container
dgrep() {
    docker ps -a | grep "$1" | cut -c1-12
}
```

`dgrep()` is a _shell function_ that accepts an unlimited amount of arguments (as shell functions do). In order to access the arguments provided to the function inside the function's body, we use the `$X` notation, where `X` is an integer number.

`$0` always refers to the name of the executing command, so if you're running:

```shell
my-command first-tom second-tom
```

And `my-command` is defined as

```shell
my-command() {
    echo $0;
    echo $1;
    echo $2;
}
```

Then the output will be:

```shell
my-command
1
2
```

Coming back to our `dgrep` command:

```shell
# Get container id of existing container
dgrep() {
    docker ps -a | grep "$1" | cut -c1-12
}
```

Note that what it's doing is getting the contents of [<u>`docker ps`</u>](https://docs.docker.com/engine/reference/commandline/ps/) (that is a list of all running containers), then [piping](https://dev.to/yashsugandh/piping-in-linux-system-4456) the input into [<u>`grep`</u>](https://dev.to/antjanus/demystifying-grep-a1o), which returns any match to the searched text with the provided argument.

In our case, it's "grepping" for `$1`, which is the first argument provided to `dgrep`. It finally pipes _that_ output into `cut -c1-12` that gets the first 12 characters of the piped text (which is, coincidentally, exactly the length of Docker container IDs).

For example, if you're running:

```shell
dgrep tom-container
```

Then if `tom-container` is a running container, `docker ps` will return a string containing all the information the Docker engine has about that container.

`dgrep` will then pipe it into the `cut` command, and print out only the container ID of that container.

## But why do I need that?

Excellent question. Remember our example from the beginning of the article, where we used the full container ID to refer to the container?

```shell
docker exec -ti 1x81hs72k2s9 /bin/bash
```

Nobody expects you to remember the full names or the IDs of your containers. We can now re-write the command as:

```shell
docker exec -ti $(dgrep substring-in-container-name) /bin/bash
```

Where `$()` is a [subshell](https://bash.cyberciti.biz/guide/What_is_a_Subshell%3F) - a special command that executes whatever is provided to it, and sends out the output as text before running the rest of the command.

In our case, `$(dgrep substring-in-container-name)` will find us a container ID based on some substring in the name (like `my` in `my-very-long-and-hard-to-remember-container-name`), and pass it to the `docker exec -ti CONTAINER-ID /bin/bash` command as `CONTAINER-ID`.

## Wait, but that's still kinda long...

You're right! "Bashing" into a container (i.e. running `bash` inside that container interactively, which is basically "opening" a terminal to that container) is something we do quite often to debug long-running containers, and to do other fun filesystem shenanigans ([ask me about that if you'd like to know more!](mailto:tom@kidonpoint.com)). 

In comes `dbash()`:

```shell
# bash into an existing container
dbash() {
    docker exec -ti $(dgrep "$1") /bin/bash
}
```

As you can see, `dbash` is calling the previously mentioned `dgrep` in a subshell, and passes that subshell the argument provided to `dbash` - namely a substring of the relevant container.

This eventually lets us do something like:

```shell
dbash tom
```

And get a terminal to the first container on the list of running containers that has `tom` in its human-readable name.

Pretty neat, right?

## Conclusion

Containers are fun. And so is working with the shell!

I literally _just_ made a really nice hack using Docker for an [open-source project](https://github.com/FedericoPonzi/Horust/pull/41) I'm working on with my good friend, Federico. Feel free to take a look if you're into container wizardry!

### Side note

An observant reader would note that I can save one pipe by using [Docker command formatting](https://docs.docker.com/config/formatting/). That reader would be correct, but I think my syntax is simpler and easier to explain to beginners, so I stuck with it for this tutorial (and, honestly, for my own `~/.zshrc` as well).