Giter Club home page Giter Club logo

shdotenv's Introduction

shdotenv

dotenv for shells with support for POSIX-compliant and multiple .env file syntax

GitHub Workflow Status

Project Status: Almost complete. Major features have been implemented and v1.0.0 will be released in the near future.

Important Notes: Incompatible changes were made in Version 0.12.0. If a definition with the same name exists in the .env file when --overload is specified, the later definition takes precedence. Also, it has been changed to default to an error for undesirable usage. We believe this change will not affect many cases, but if you have a problem, please open an issue.

Quoting bkeepers/dotenv:

Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments–such as resource handles for databases or credentials for external services–should be extracted from the code into environment variables.

Why not use source or export?

It is not safe. There is no formal specification for the .env file syntax, and different languages, libraries, and tools use different syntaxes. If you load a .env file syntax that is incompatible with the POSIX shell syntax, you will get unexpected results and may even result in the execution of scripts.

shdotenv safely loads the syntax of .env files that are compatible with POSIX shell syntax. There is no possibility that the script will be executed. And also, for interoperability, .env files with other syntaxes are supported whenever possible.

The goals of this project

  1. Provide language-independent CLI utilities
  2. Provide a library that can safely load .env file from shell scripts
  3. Define POSIX shell compatible .env file syntax specification
  4. Support for .env file syntax dialects for interoperation

Requirements

shdotenv is a single file shell script with embedded awk script. It uses only the following commands which can be found anywhere.

  • POSIX shell (dash, bash, ksh, zsh, etc)
  • awk (gawk, nawk, mawk, busybox awk)

Install

Download shdotenv (shell script) from releases.

$ wget https://github.com/ko1nksm/shdotenv/releases/latest/download/shdotenv -O $HOME/bin/shdotenv
$ chmod +x $HOME/bin/shdotenv

Build your own

Build and install only

$ git clone https://github.com/ko1nksm/shdotenv.git
$ cd shdotenv
$ make build
$ make install PREFIX=$HOME

Full build

A full build requires requires shfmt, shellcheck and shellspec.

$ git clone https://github.com/ko1nksm/shdotenv.git
$ cd shdotenv
$ make MINIFY=true
$ make install PREFIX=$HOME

Note for developers: shdotenv can be run in source code without building. Please run src/shdotenv.

Usage

Usage: shdotenv [OPTION]... [--] [[COMMAND | export] [ARG]...]

  If the COMMAND is specified, it will load .env files and run the command.
  If the COMMAND is omitted, it will output the result of interpreting .env
  files. It can be safely loaded into the shell (For example, using eval).

Options:
  -d, --dialect DIALECT     Specify the .env dialect [default: posix]
                                posix, ruby, node, python,
                                php, go, rust, docker
  -f, --format FORMAT       Output in the specified format [default: sh]
                                sh, csh, fish, json, jsonl, yaml, name
  -e, --env ENV_PATH        Location of the .env file [default: .env]
                              Multiple -e options are allowed
                              If the ENV_PATH is "-", read from stdin
  -i, --ignore-environment  Ignore the current environment variables
      --overload            Overload predefined variables
      --no-allexport        Disable all variable export
                              Same as deprecated --noexport
      --no-nounset          Allow references to undefined variables
      --grep PATTERN        Output only names that match the regexp pattern
  -s, --sort                Sort variable names
  -q, --quiet               Suppress all output (useful for test .env files)
  -v, --version             Show the version and exit
  -h, --help                Show this message and exit

Usage: shdotenv export [-n | -p] [--] [NAME]...
  Exports environment variables in posix-compliant .env format.

  -n  List only environment variable names
  -p  Append "export" prefix to environment variable names

  This will be output after the .env files is loaded. If you do not want
  to load it, specify "-e /dev/null". This is similar to "export", "env"
  and "printenv" commands, but quoting correctly and exports only portable
  environment variable name that are valid as identifier for posix shell.

How to use

Use as a CLI utility

Set environment variables and execute the specified command.

shdotenv [OPTION]... <COMMAND> [ARGUMENTS]...

Test the .env file syntax

shdotenv --quiet --env .env

Use as a library

Load the .env file into the shell script. When run on the shell, it exports to the current shell.

sh, bash, ksh, zsh, etc. (POSIX-compliant shells)

eval "$(shdotenv [OPTION]...)"

You may want to abort the program when the .env file fails to parse. In that case, do the following

eval "$(shdotenv [OPTION]... || echo "exit $?")"

csh, tcsh

set newline='\
'
eval "`shdotenv -f csh [OPTION]...`"

fish

eval (shdotenv -f fish [OPTION]...)

Export environment variables safely

This is similar to export, env and printenv commands, but quoting correctly and exports only portable environment variable name that are valid as identifier for POSIX shell.

shdotenv export [-n | -p] [NAME]...

Additional CLI utility

contrib/dockerenv

The docker command has the --env-file option, but it only supports setting simple values.

This tool makes the files read by --env-file compatible with the .env format, and supports variable expansion and newlines.

Example: (Use dockerenv instead of docker)

dockerenv run --env-file .env -it debian

.env file syntax

# dotenv posix
# This line is a comment, The above line is a directive
COMMENT=This-#-is-a-character # This is a comment

UNQUOTED=value1 # Spaces and some special characters cannot be used
SINGLE_QUOTED='value 2' # Cannot use single quote
DOUBLE_QUOTED="value 3" # Some special characters need to be escaped

MULTILINE="line1
line2: \n is not a newline
line3"
LONGLINE="https://github.com/ko1nksm\
/shdotenv/blob/main/README.md"

ENDPOINT="http://${HOST}/api" # Variable expansion requires braces

export EXPORT1="value"
export EXPORT2 # Equivalent to: export EXPORT2="${EXPORT2:-}"
  • The syntax is a subset of the POSIX shell.
  • The first line is an optional directive that specifies the dialect of the .env syntax
  • No spaces are allowed before or after the = separating the name and value
  • ANSI-C style escapes are not available (i.e., \n is not a newline)
  • Unquoted value
    • The special characters that can be used are # % + , - . / : = @ ^ _
  • Single-quoted value
    • The disallowed character is: '
    • It can contain newline characters.
  • Double-quoted value
    • Variable expansion is available (only ${VAR} style is supported)
    • The following values should be escaped with a backslash (\): $ ` " \
    • The \ at the end of a line value means line continuation
    • It can contain newline characters.
  • An optional export prefix can be added to the name
  • Comments at the end of a line need to be preceded by spaces before the #

Detailed POSIX-compliant .env syntax specification

Directive

Specifies the dotenv syntanx dialect that this .env file.

# dotenv <DIALECT>

Example:

# dotenv ruby

Supported dialects

The formal .env syntax for this project is posix only. The posix is a subset of the POSIX shell and is compatible with shell scripts. Support for other .env syntax dialects is for interoperability purposes. Compatibility will be improved gradually, but is not fully compatible. Reports of problems are welcome.

Comparing Dialects

.shdotenv

Specifies options for shdotenv. Currently, only dialect is supported. It is recommended that the dotenv dialect be specified with the dotenv directive. The .shdotenv setting is for personal use in projects where it is not allowed.

dialect: <DIALECT>

Example:

dialect: ruby

Environment Variables

name description default
SHDOTENV_FORMAT Output format (sh, fish, etc.) sh
SHDOTENV_AWK Path of the awk command awk

FAQ

Note and reference: The FAQs present on motdotla's dotenv node project page and cdimascio's dotenv-java project page are so well done that I've included those that are relevant in the FAQs above.

Q: Should I deploy a .env to e.g. production?

A: Tenant III of the 12 factor app methodology states "The twelve-factor app stores config in environment variables". Thus, it is not recommended to provide the .env file to such environments. dotenv, however, is super useful in e.g a local development environment as it enables a developer to manage the environment via a file which is more convenient.

Using dotenv in production would be cheating. This type of usage, however is an anti-pattern.

Q: Should I commit my .env file?

No. We strongly recommend against committing your .env file to version control. It should only include environment-specific values such as database passwords or API keys. Your production database should have a different password than your development database.

Q: What happens to environment variables that were already set?

By default, we will never modify any environment variables that have already been set. In particular, if there is a variable in your .env file which collides with one that already exists in your environment, then that variable will be skipped.

If instead, you want to override environment variables use the --overload option.

shdotenv --overload

Q: Why can't I define an environment variable with the same name in the .env file?

We allows multiple .env files for convenience and interoperability with other dotenv tools. However, we believe that being able to use the same name in different .env files will lead to environment variables that are not "fully orthogonal" as The Twelve-Factor App outlines.

In a twelve-factor app, env vars are granular controls, each fully orthogonal to other env vars. They are never grouped together as “environments”, but instead are independently managed for each deploy. This is a model that scales up smoothly as the app naturally expands into more deploys over its lifetime.

– The Twelve-Factor App

If you want to override a previous definition, use the --overload option.

shdotenv's People

Contributors

asheroto avatar ko1nksm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

shdotenv's Issues

Expand $HOME directory shorthand ~ (tilde character) to absolute path

Is your feature request related to a problem? Please describe.

I'm using shdotenv in a special Laradock project structure, where I merge Laradock's and my environment variables into one:

eval "$($DIR/shdotenv --dialect docker --overload --env laradock.env.example || echo "exit $?")"
set -a && . ./.env && set +a

However, shdotenv cannot expand the tilde character, if I put the value in quote or not, doesn't matter:

eval "$($DIR/shdotenv --dialect posix --overload --env .env || echo "exit $?")"

Output I'm getting:

APACHE_SSL_PATH=~/.laradock/ssl/apache2

Expected output:

APACHE_SSL_PATH=/home/user/.laradock/ssl/apache2

Describe the solution you'd like

It would be nice to have a flag, where it can expand $HOME shorthand (~) to absolute paths.

Describe alternatives you've considered

The only working solution is to use the good old bash env variable parsing trick:

set -a && . ./.env && set +a

But with this, I loose every control over the file and I have to format it very carefully. It works with my files, but not with the ones from various repositories.

Additional context

None.

Parse files without using shell environment variables

Is your feature request related to a problem? Please describe.

# test.env
KEY1=value1
KEY2=value2
$ ./shdotenv --noexport --env test.env
KEY1='value1'
KEY2='value2'

When KEY1 is already defined in my shell, it's removed from the output:

$ KEY1='blaaat' ./shdotenv --noexport --env test.env
KEY2='value2'

I don't want that. I want the result always to have both keys.

Describe the solution you'd like
I want to cascade multiple dotenv files into one file but without using the env vars in my shell.

Describe alternatives you've considered

I can use env -i shdotenv ... but not sure if that is expected.

Additional context

.shdotenv for specify dialect

I recommend using # dotenv directive to specify dialect, but this may not be acceptable for your projects. Although it is possible to specify by the --dialect option, it is tedious to specify it every time, so I will add the ability to specify it by .shdotenv file.

shdotenv seems to ignore -e /dev/null

Unless I understood the docs wrong, shdotenv export ignores -e /dev/null
Ex:

$ echo "VAR1=foo" > .env
$ shdotenv -e /dev/null -e .env export
HOME='/root'
PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
PWD='/tmp'
SHLVL='2'
TERM='xterm'
VAR1='foo'

Expected behavior

$ echo "VAR1=foo" > .env
$ shdotenv -e /dev/null -e .env export
VAR1='foo'

Environment:

  • OS: linux (alpine:3.17 container)
  • awk version: GNU awk 5.1.1

Additional context
I would like to use shdotenv to manipulate an env file ensuring that file is valid and merging files, for example:

echo "VAR1=foo" > .existing.env
MYOUTPUT=$(echo 'value that I have no idea on how to escape')
shdotenv export MYOUTPUT | shdotenv -e /dev/null -e existing.env -e - export

Of course I can do shdotenv export MYOUTPUT >> existing.env and rely that values would be overwritten due their order, but I would prefer to use shdotenv to keep the env file cleaner.

Make ~/.local/bin as the default download target in README

Currently README instructs

$ wget https://github.com/ko1nksm/shdotenv/releases/latest/download/shdotenv -O $HOME/bin/shdotenv
$ chmod +x $HOME/bin/shdotenv

However, $HOME/bin is not a standard location - not available on Ubuntu Linux or macOS based on my test.

~/.local/bin enjoys at least some standard support.

I would propose changing README to recommend this best practice. I can update it with a PR unless there are some concerns around this.

I will remove some of the (short) options in v0.10.0

We plan to remove options that are not used interactively in v1.0.0.

To be removed

  • -s, --shell (Deprecated)
  • -o, -n, -g.
  • -k, --keyonly (Rename to --name-only)

Currently

Usage: shdotenv [OPTION]... [--] [[COMMAND | export] [ARG]...]

  If the COMMAND is specified, it will load .env files and run the command.
  If the COMMAND is omitted, it will output the result of interpreting .env
  files. It can be safely loaded into the shell (For example, using eval).

Options:
  -d, --dialect DIALECT     Specify the .env dialect [default: posix]
                                posix, ruby, node, python,
                                php, go, rust, docker
  -f, --format FORMAT       Output in the specified format [default: sh]
                                sh, fish
  -e, --env ENV_PATH        Location of the .env file [default: .env]
                              Multiple -e options are allowed
                              If the ENV_PATH is "-", read from stdin
  -i, --ignore-environment  Ignore the current environment variables
      --overload            Overload predefined environment variables
      --noexport            Do not append "export" prefix
      --grep PATTERN        Output only names that match the regexp pattern
      --name-only           Output only environment variable names
  -q, --quiet               Suppress all output (useful for test .env files)
  -v, --version             Show the version and exit
  -h, --help                Show this message and exit

  Deprecated: (to be removed in the next version)
  -s, --shell SHELL         Use the -f (--format) option instead
  -k, --keyonly             Use the --name-only option instead
  -o, -n, -g                Use long options instead

Usage: shdotenv export [-n | -p] [--] [NAME]...
  Exports environment variables in posix-compliant .env format.

  -n  List only environment variable names
  -p  Append "export" prefix to environment variable names

  This will be output after the .env files is loaded. If you do not want
  to load it, specify "-e /dev/null". This is similar to "export", "env"
  and "printenv" commands, but quoting correctly and exports only portable
  environment variable name that are valid as identifier for posix shell.

generate dotenv compatible file based on current env

Is your feature request related to a problem? Please describe.

Generating dotenv compatible file based on current environment variables.

Describe the solution you'd like

 # saves sorted list of env variable names/values to dotenv compatible format
shdotenv > .env
# accepts space separated key list as arguments
shdotenv KEY_1 KEY_2 > .env 

Describe alternatives you've considered

env | sort > .env

These alternative solutions are generally:

  • not posix compliant
  • not dotenv compliant, like missing quotes around special characters
  • not simply abstracted
  • have bugs with special characters

Additional context

I am initializing my environment based on a shell script that I source, that takes various inputs. After I initialize my environment I would like to export it in a way that other programs like IDEs can references those same values.

https://stackoverflow.com/questions/60756020/print-environment-variables-sorted-by-name-including-variables-with-newlines

Wrong choice when multiple values in file

Describe the bug

By default shdotenv skip current defined variable names.
It's a right logic to prefer inherited environment.
But in group of values taken from file it prefer first value. Should be the last.
When a developer overrides values ​​in an .env file, he believes that the lower ones override the higher ones.

To Reproduce

Steps to reproduce the behavior:

# make file
$ cat <<EOF > example.env 
SSH_HOST=oldest
SSH_HOST=old
SSH_HOST=actual
EOF

case 1: use it like settings reader

# good
$ SSH_HOST=overrided ./shdotenv --noexport -e example.env

# bad (undefined behavior)
$ SSH_HOST=overrided ./shdotenv --noexport -e example.env --overload
SSH_HOST='oldest'
SSH_HOST='old'
SSH_HOST='actual'

# bad (unexpected value)
$ ./shdotenv --noexport -e example.env
SSH_HOST='oldest'

case 2: use it like runner

# good
$ SSH_HOST=overrided ./shdotenv -e example.env -- sh -c 'echo $SSH_HOST'
overrided

# bad
$ ./shdotenv -e example.env -- sh -c 'echo $SSH_HOST'
oldest

ok, may be --overload is solution? No it only revert results

# bad (--overload works like --ignore-environment, but it not set here)
$ SSH_HOST=overrided ./shdotenv -e example.env --overload -- sh -c 'echo $SSH_HOST'
actual

# good
$ ./shdotenv -e example.env --overload -- sh -c 'echo $SSH_HOST'
actual

No way to make universal usage for select 'overrided' when override and 'actual' otherwise.

Expected behavior

#  --overload enabled by default, no ignore environment
$ SSH_HOST=overrided ./shdotenv --noexport -e example.env

#  --overload no ignore environment
$ SSH_HOST=overrided ./shdotenv --noexport -e example.env --overload

#  --overload enabled by default, print last value
$ SSH_HOST=overrided ./shdotenv --noexport -e example.env --ignore-environment
SSH_HOST='actual'

#  --overload enabled by default, print last value
$ ./shdotenv --noexport -e example.env
SSH_HOST='actual'

$ ./shdotenv -e example.env -- sh -c 'echo $SSH_HOST'
actual

$ SSH_HOST=overrided ./shdotenv -e example.env  --overload -- sh -c 'echo $SSH_HOST'
overrided

$ SSH_HOST=overrided ./shdotenv -e example.env --ignore-environment -- sh -c 'echo $SSH_HOST'
actual

# new mode for print all
$ ./shdotenv --noexport -e example.env --no-overload
SSH_HOST='oldest'
SSH_HOST='old'
SSH_HOST='actual'

Environment (please complete the following information):

  • OS: Mac OS X 10.13.6
  • GNU Awk 5.1.1, API: 3.1 (GNU MPFR 4.1.0, GNU MP 6.2.1)

Additional context

  • fix --overload to overload only env file values, not parent env. The --ignore-environment is works fine
  • enable --overload by default. because it confusing developers.
  • add option to disable overload feature. like --no-overload or --overload no or --all to print all

Output without quotes

Is your feature request related to a problem? Please describe.
I like the portability and functions of this project, but would like an option to export without quotes, without escaping anything. My current usecase is converting dotenv files to json.

With values in the format "var=value" i can easily do this:

echo -e "var1=val1\nvar2=val2" | jq -Rn '{Parameters:[(inputs | split("=")) | {(.[0]): (.[1:] | join("="))}] | add}'
{
  "Parameters": {
    "var1": "val1",
    "var2": "val2"
  }
}

Describe the solution you'd like

I would like a flag "--no-quotes" which outputs without quotes. Everything after the equals should be provided as is without any quoting.

Describe alternatives you've considered

I could use sed to replace the single quotes or do it in jq, but it gets ugly. I think it would be a good feature to output in a more standard dotenv format.

The python project outputs by default without quotes

$ ▶ dotenv list
myvar=myvalue
myconcatvar=concat/myvalue/woo

I am happy to work on a PR for this if you are agreeable.

Special character in unqouted variable

Hello! Great library!

I have a question, is there a support for variables that has special values like secret keys that don't have single qoutes?

Example:

GENERATED_SECRET_KEY=dAz31q82*E0d

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.