Skip to content

Instantly share code, notes, and snippets.

@judy2k
Created March 22, 2017 13:34
Star You must be signed in to star a gist
Save judy2k/7656bfe3b322d669ef75364a46327836 to your computer and use it in GitHub Desktop.
Parse a .env (dotenv) file directly using BASH
# Pass the env-vars to MYCOMMAND
eval $(egrep -v '^#' .env | xargs) MYCOMMAND
# … or ...
# Export the vars in .env into your shell:
export $(egrep -v '^#' .env | xargs)
@mopcweb
Copy link

mopcweb commented Apr 19, 2021

TL;DR;

local result=$(grep ^VAR_NAME=.* path/to/.env | cut -d "=" -f 2);

More protected function to get necessary var and throw error if VAR not found, or invalid path provided.

Usage:

local port=`getEnvVar --var PORT --path ./some-project/.env`;
echo "port = $port";

Code

# Throws error - if it is.
#
# @example: exitIfError $? "Your error text".
# @example: exitIfError $1 "Your error text".
function exitIfError() {
  local exit_code=$1
  shift
  [[ $exit_code ]] &&
    ((exit_code != 0)) && {
      echo "ERROR. $@" >&2;
      exit "$exit_code";
    }
}


# Gets ENV property from provided .env file
#
# @param var - Variable name
# @param path - Path to env file.
# @param [file] - Optional fileName param. @default .env.
function getEnvVar() {
  # This line is necessary to parse function named args
  # @see https://gist.github.com/mopcweb/38f5d09525f8defa5aa807d95efa8307
  while [[ $# -gt 0 ]]; do if [[ $1 == *"--"* ]]; then if [[ $2 != *"--"* ]]; then local "${1/--/}"="${2:-true}"; else local "${1/--/}"=true; fi; fi; shift; done;
  [[ -n $file ]] && local fileName=$file || local fileName=".env";

  [[ -z $var || -z $path ]] && exitIfError 1 "getEnvVar: --var & --path are required.";
  [[ ! -d $path ]] && exitIfError 1 "getEnvVar: --path should be a valid dir.";

  local result=$(grep ^$var=.* $path/$fileName | cut -d "=" -f 2);

  [[ -z $result ]] && exitIfError 1 "getEnvVar: there is no such $var var in $path/$fileName file.";

  echo $result;
}

@abij
Copy link

abij commented Apr 19, 2021

@mopcweb Can you update with input and output, what is supported in the .env file?
And how much fun did you have, creating your own solution ;)?

# INPUT                     Expected:
'FOO=value'                 FOO='value'
"FOO=#value # comment"      FOO='#value # comment'
"FOO=value   "              FOO='value   '
'FOO='                      FOO=''
'export FOO=value'          export FOO='value'
"FOO=foo bar"               FOO='foo bar'
"FOO=   foo"                FOO='   foo'

Test cases from @ko1nksm: https://github.com/ko1nksm/shdotenv/blob/main/spec/docker_spec.sh.
Note: check his awesome script: https://github.com/ko1nksm/shdotenv !

@lzkill
Copy link

lzkill commented May 5, 2021

Why not dotenv-cli?

$ dotenv <command with arguments> 
# or
$ dotenv -e .env.custom <command with arguments>

@loopmode
Copy link

loopmode commented May 5, 2021

Because not every system has nodejs on it. And it's good that way.

@geoffjukes
Copy link

geoffjukes commented May 11, 2021

One-liner that allows unquoted variables that contain spaces:

OLD_IFS=$IFS; IFS=$'\n'; for x in `grep -v '^#.*' .env`; do export $x; done; IFS=$OLD_IFS

@alex-hladun
Copy link

ead_var() {
VAR=$(grep $1 $2 | xargs)
IFS="=" read -ra VAR <<< "$VAR"
echo ${VAR[1]}
}

MY_VAR=$(read_var MY_VAR .env)

Perfect, thanks

@ChrisGibb
Copy link

What about this

# save the existing environment variables
prevEnv=$(env)

# if the .env file exists, source it
[ -f .env ] && . .env

# re-export all vars from the env so they override what ever was set in .env
for e in $prevEnv
do
    export $e
done

@wieczorek1990
Copy link

wieczorek1990 commented Jan 21, 2022

I wrote my own because was using forbidden symbols in envs.

This basically adds apostrophes so that all variables will be treated as strings. This way you can use your Docker env files and source them with source envs.sh

import sys


def main(input_path, postfix='.sh'):
    with open(input_path, 'r') as file_handle:
        lines = file_handle.readlines()
        envs = {}
        for line in lines:
            try:
                parts = line.split('=')
                name = parts[0]
                value = ''.join(parts[1:]).rstrip('\n')
            except ValueError:
                pass
            else:
                envs[name] = value

    output_path = f'{input_path}{postfix}'
    with open(output_path, 'w') as file_handle:
        lines = []
        for name, value in envs.items():
            line = f'{name}=\'{value}\'\n'
            lines.append(line)
        file_handle.writelines(lines)


if __name__ == '__main__':
    # Passes first argument as input path.
    main(sys.argv[1])

@andylamp
Copy link

andylamp commented Mar 12, 2023

The solution proposed by @arizonaherbaltea will not work correctly when the file is not terminated with a newline. For example using this .env example,

# test.env
MY_VAR=a

Will not work properly, whereas the following will,

# test.env
MY_VAR=a

The only change is adding a newline at the end of the file. This is because read requires a newline to parse the line correctly. Thus, in order to ensure everything works properly we can add a check to see if the file ends with newline and if not append it before parsing it. Doing this will ensure all lines are parsed correctly.

#!/bin/bash

function export_envs() {
  local env_file=${1:-.env}
  local is_comment='^[[:space:]]*#'
  local is_blank='^[[:space:]]*$'
  echo "trying env file: ${env_file}"

  # ensure it has a newline that the end, if it does not already
  tail_line=`tail -n 1 "${env_file}"`
  if [[ "${tail_line}" != "" ]]; then
      echo "No Newline at end of ${env_file}, appending!"
      echo "" >> "${env_file}"
  fi

  while IFS= read -r line; do
    echo "${line}"
    [[ $line =~ $is_comment ]] && continue
    [[ $line =~ $is_blank ]] && continue
    key=$(echo "$line" | cut -d '=' -f 1)
    # shellcheck disable=SC2034
    value=$(echo "$line" | cut -d '=' -f 2-)
    # shellcheck disable=SC2116,SC1083
    echo "The key: ${key} and value: ${value}"
    eval "export ${key}=\"$(echo \${value})\""
  done < <(cat "${env_file}")
}

export_envs ${1}

Then it results,

✗ ./test.sh test.env
trying env file: test.env
The key: MY_VAR and value: a
The key: MY_VAR and value: b

Hope this helps someone out there :)

@gmeligio
Copy link

I've been using shdotenv from @ko1nksm and it's been great.

You can do shdotenv my-command to parse and run a command.
If you want to export the variables to the shell session, use eval $(shdotenv).

@tgrushka
Copy link

tgrushka commented Feb 6, 2024

What's wrong with:

if [ -f .env ]; then
    set -o allexport
    source .env
fi

Works on macOS with my .env that works with docker compose and does not have quotes around every string.

Test with:

envsubst < "$secret_file" | cat

later in the same script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment