<!--
  canonical-url: https://www.avonture.be/blog/compare-env-files-cli
  published:     2024-01-26
  This is a static plain-Markdown mirror generated at build time.
  Visit the canonical URL above for the fully rendered page, with images and interactive components.
-->

# Compare environment files in the Linux console

> Struggling to sync your .env and .env.example files? Learn a powerful Linux CLI command using diff, grep, and sort to accurately compare environment variables and skip comments.

---
![Compare environment files in the Linux console](/img/v2/bash.webp)

**TL;DR**

This article shows a Linux one-liner to reliably compare two `.env` files (e.g. `.env` vs `.env.example`) using `diff -y --suppress-common-lines` combined with `grep` and `sort`, so comments, blank lines, and line order are ignored and specific variables (like `APP_KEY`) can be excluded from the comparison.

This is a very common source of problems using .env files: you have two or more different `.env` files like `.env` and `.env.example`.

You're a programmer and coding a new amazing feature. You're adding one or more new environment variables to your local `.env` file and everything is working fine **on your computer**.

> **Boum! Your feature is buggy.:**
>
> A colleague copies the source code from a version-control system like GitHub/GitLab, or, second scenario, someone deploys the feature to a server — and your feature breaks.

Why? Because the variable(s) you've added have been added in **your local `.env`** file, on **your computer only**.

As you know, you have to create the variables in the `.env.example` file too but let's be honest, nobody thinks about it.

## The one-liner and its output

Copy this in your console, in the folder holding the two files:

```bash
(
  clear
  ENV1=.env
  ENV2=.env.example
  printf "\e[33;1m%-63s %s\e[0;1m\n" "Left side: ${ENV1}" "Right side: ${ENV2}"
  diff --suppress-common-lines -y \
    <(grep -v -E '^#|^$' ${ENV1}| sort) \
    <(grep -v -E '^#|^$' ${ENV2} | sort) \
   | grep -v 'APP_KEY'
)
```

On two `.env` files that have drifted apart, here is what comes back:

```diff
Left side: .env                   Right side: .env.example
ALLOW_FEATURE_DO_THIS = true    <
DATABASE_TYPE = pgsql           | CACHE_DRIVER = redis
                                > DATABASE_TYPE = mysql
```

How to read:

* `ALLOW_FEATURE_DO_THIS` is only present in `.env`,
* `DATABASE_TYPE` is initialized to `pgsql` in `.env` and to `mysql` in `.env.example`,
* `CACHE_DRIVER` is only present in `.env.example` and
* all other lines are strictly identical (remember we've ignored commented and empty lines)

Three lines of output, and the answer to "what did I forget to report in `.env.example`?" is right there.

## Why plain `diff` isn't enough

`diff` alone (see [Linux - Comparing two folders/files in the console](/blog/linux-diff-file-folder) for the general case) would drown you in noise, because:

1. We don't care about comments and empty lines. If a variable has been commented, we just need to ignore it.
2. We don't care about the position in the file where the variable is declared. If `APP_ENV = local` is on the first line, in the middle of the file or just before the last line, we don't care about it.
3. We can also ignore some variables that we know should be different, like `APP_KEY` f.i.

That's exactly what the three helpers around `diff` are doing: `grep -v -E '^#|^$'` drops comments and blank lines, `sort` neutralizes the order, and the final `grep -v 'APP_KEY'` removes the variables you already know will differ.

The flag `--suppress-common-lines -y` does the rest: display the result in two columns (`-y`) and only the differences (`--suppress-common-lines`).

## Reproduce it

Let's try... Below we'll create the file `.env.example` with two lines then copy it to `.env` and just add a new line in `.env.example`. Finally, we'll sort `.env.example` so the order will differ with `.env`.

```bash
$ mkdir -p /tmp/playing_env && cd $_
$ echo 'APP_NAME = My application' > .env.example
$ echo 'APP_ENV = local' >> .env.example
$ cp .env.example .env
$ echo 'APP_KEY = 5a0678afd37f4b8d/8d9451a7381266e3' >> .env
$ echo 'APP_KEY = 3445118442a942d1/afd37466fadd5223' >> .env.example
$ echo 'CACHE_DRIVER = redis' >> .env.example
$ sort -o .env.example .env.example
```

Now that we have our two files with one single difference, running the command above gives:

```diff
Left side: .env                   Right side: .env.example
                                > CACHE_DRIVER = redis
```

The column on the left represents the first file (in our example `.env`) while the column on the right is for the second file (`.env.example`). So: `CACHE_DRIVER` exists only in `.env.example`.

For the illustration, we can now add a new key but just in `.env` (real world situation: I'm coding a new feature and I add a variable like a switch on/off)

```bash
$ echo 'ALLOW_FEATURE_DO_THIS = true' >> .env
```

Now the output will be:

```diff
Left side: .env                   Right side: .env.example
ALLOW_FEATURE_DO_THIS = true    <
                                > CACHE_DRIVER = redis
```

Last sample, the same key with two different values on each side:

```bash
$ echo 'DATABASE_TYPE = pgsql' >> .env
$ echo 'DATABASE_TYPE = mysql' >> .env.example
```

And you're back to the three-line output shown at the top of this article, where the `|` marker appears: same key, different values.

## Conclusion

Nobody thinks about updating `.env.example` — that's the whole problem, and no amount of good intentions fixes it. A one-liner you can paste before every commit does, because it takes two seconds and answers with three lines or nothing at all.

Once you know which keys are missing, the next step is to add them everywhere without editing each file by hand: [Batch edit of environment file](/blog/update-env-files-cli). And to consume those variables from your scripts, see [Bash - Loading environment variables from a file](/blog/bash-load-env).
