从键/值对文件中设置环境变量

1106

TL;DR: 我该如何从文本文件中导出一组键/值对到shell环境中?


以下是原始问题的版本和示例。

我正在编写一个在bash中解析特定文件夹中具有3个变量的文件的脚本,这是其中之一:

MINIENTREGA_FECHALIMITE="2011-03-31"
MINIENTREGA_FICHEROS="informe.txt programa.c"
MINIENTREGA_DESTINO="./destino/entrega-prac1"

这个文件存储在 ./conf/prac1 中。

我的脚本 minientrega.sh 使用以下代码解析该文件:

cat ./conf/$1 | while read line; do
    export $line
done

但是当我在命令行中执行minientrega.sh prac1时,它没有设置环境变量。

我也尝试使用source ./conf/$1,但是问题仍然存在。

也许有其他方法可以做到这点,我只需要使用我脚本所传递的文件的环境变量。


在Unix上相同的操作:http://unix.stackexchange.com/questions/31797/set-variable-environment-variables-in-bash-or-other - Ciro Santilli OurBigBook.com
5
这是一个很棒的问题,但是表述过于具体,使用了特定的变量名(“MINIENTREGA_FECHALIMITE”是什么意思?)和数字(3)。通用的问题可以简单地表述为:“如何将文本文件中的一组键值对导出到shell环境中”。 - Dan Dascalescu
2
此外,这个问题已经在 unix.SE 上得到了回答,并且在那里更加相关。 - Dan Dascalescu
4
我对将这个问题标记为4年后出现的问题的重复有所保留。 - anubhava
1
@anubhava,这很公平,但是这里的答案质量相当糟糕。首要目标是为人们提供良好的信息,难道不是吗?(虽然这个问题有一些好的答案,但与顶部存在缺陷的答案相比,它们的评分很低;这是一个Stack Overflow的格式和规则没有为我们的社区/读者服务的地方)。 - Charles Duffy
显示剩余2条评论
51个回答

1573

这可能会有所帮助:

export $(cat .env | xargs) && rails c

我使用这个的原因是因为如果我想在我的rails控制台中测试.env的内容。

Gabrielf提出了一个不错的方法来保持变量的局部性。这解决了从一个项目到另一个项目时可能出现的潜在问题。

env $(cat .env | xargs) rails

我已经在 bash 3.2.51(1)-release 上测试过了。


更新:

要忽略以 # 开头的行,请使用以下内容(感谢Pete 的评论):

export $(grep -v '^#' .env | xargs)

如果你想要取消文件中定义的所有变量,可以使用以下命令:unset

unset $(grep -v '^#' .env | sed -E 's/(.*)=.*/\1/' | xargs)

更新:

为了处理带有空格的值,请使用:

export $(grep -v '^#' .env | xargs -d '\n')

在GNU系统上——或:

export $(grep -v '^#' .env | xargs -0)

在BSD系统上。


这个答案中,您可以使用以下命令自动检测操作系统:

export-env.sh

#!/bin/sh

## Usage:
##   . ./export-env.sh ; $COMMAND
##   . ./export-env.sh ; echo ${MINIENTREGA_FECHALIMITE}

unamestr=$(uname)
if [ "$unamestr" = 'Linux' ]; then

  export $(grep -v '^#' .env | xargs -d '\n')

elif [ "$unamestr" = 'FreeBSD' ] || [ "$unamestr" = 'Darwin' ]; then

  export $(grep -v '^#' .env | xargs -0)

fi


49
这是一个更短的变体:eval $(cat .env) rails - manalang
1
在我的端上,这似乎不能与.env文件中的多个条目一起使用,因为它不会保留换行符,所以你会得到一个SOME_ENV_VAR=sdfajkasldfjlasdkfj ANOTHERVAR=sdafjasdjasdfkl作为单个变量(因为它们都在单行导出之后)。 - Kzqai
1
这太复杂了 - 请考虑使用 set -o allexport 的更简单方法 下面 - Nam G VU
在我的情况下,添加了一个 \r 导致数值错误。我添加了 tr -d '\r' 但是 tr '\r' '\0' 也可以工作。最终命令是 export $(grep -v '^#' .env | tr '\r' '\0' | xargs -d '\n') - Player Schark
2
不,export $(grep -v '^#' .env | xargs -0) 在GNU系统上是不正确的。未引用的扩展仍会在空格上进行单词拆分;xargs发出NUL并不改变这一事实。(而且某些版本的bash只会静默删除命令替换结果中的NUL,因此该代码的确切行为取决于版本,并且不能可靠地进行测试)。而且,xargs -d '\n' 在任何地方都是不必要的,因为换行符存在于IFS中,因此在命令替换结果中,它被解析的方式与空格完全相同。 - Charles Duffy
@manalang:eval函数无法将变量本地化。 - undefined

955

-o allexport 启用所有随后的变量定义导出。 +o allexport 禁用此功能。

set -o allexport
source conf-file
set +o allexport

16
或者使用一行命令set -o allexport && source conf-file && set +o allexport。感谢@user4040650的精彩分享。 - 2upmedia
5
这适用于脚本。 - Pål Thingbø
2
在我看来,这是一个简单且应该被接受的答案! - Nam G VU
这个有效。可以确认,即使有$特殊字符。 - undefined

366
你的做法有问题,while循环中的export是在子shell中执行的,因此这些变量在当前shell(while循环的父Shell)中不可用。需要在文件本身中添加export命令。
export MINIENTREGA_FECHALIMITE="2011-03-31"
export MINIENTREGA_FICHEROS="informe.txt programa.c"
export MINIENTREGA_DESTINO="./destino/entrega-prac1"

接着你需要在当前 shell 中使用以下命令导入该文件:

. ./conf/prac1

或者

source ./conf/prac1

6
如果数据不来自文件,则使用 < <(生成输出的命令) - o11c
15
你有一个更加干净的解决方案,我更喜欢使用set -o allexport - heralight
7
如果在各个系统之间使用此 .env 文件,插入“export”将使 Java、SystemD 或其他工具出现问题。 - FilBot3
3
如果您使用. ./conf/prac1,则无需前缀为export - Stokedout
2
虽然这种方法可以用,但我发现在每个环境变量前加上export会干扰很多试图解析它们的程序(例如,如果所有东西都以export为前缀,我就无法使用VS Code调试器)。 - Mason Jones
显示剩余3条评论

316
set -a
. ./env.txt
set +a

如果 env.txt 的内容类似于:

VAR1=1
VAR2=2
VAR3=3
...

解释 -a 相当于 allexport。换句话说,shell 中的每个变量赋值都会导出到环境中(以供多个子进程使用)。更多信息可以在“Set builtin”文档中找到:

-a    每个创建或修改的变量或函数都被赋予导出属性,并标记为导出到后续命令的环境中。

使用‘+’而非‘-’会关闭这些选项。这些选项也可以在调用 shell 时使用。当前的选项集可在 $- 中找到。


假如有一个注释,以及 VAR2=$VAR1 - Alexis
2
嗨@Alexis。这里使用的“.”命令本质上就像在终端上输入一样。你可以在终端上自己尝试并查看结果。注释将被忽略,对其他变量的引用也将起作用,只要它们之前已经定义过。 - Dan Kowalczyk
是的,我已经尝试过,它是这样工作的。感谢跟进! - Alexis

107

我发现最有效率的方法是:

export $(xargs < .env)

解释

当我们有一个像这样的.env文件:

key=val
foo=bar

运行 xargs < .env 将会得到 key=val foo=bar

因此我们会得到一个 export key=val foo=bar,这正是我们需要的!

限制

  1. 它无法处理值中包含空格的情况。像 env 这样的命令会产生这种格式。- @Shardj

1
我完美的解决方案。 - alexwenzel

52

在其他答案中提到了allexport选项,其中set -a是它的快捷方式。与循环遍历行并导出环境变量相比,使用源文件.env更好,因为它允许包含注释、空行和甚至命令生成的环境变量。我的.bashrc包括以下内容:

# .env loading in the shell
dotenv () {
  set -a
  [ -f .env ] && . .env
  set +a
}

# Run dotenv on login
dotenv

# Run dotenv on every new directory
cd () {
  builtin cd $@
  dotenv
}

5
这看起来不错,但你在离开目录时如何卸载环境变量? - Bastian Venthur
1
我不取消变量,这从来都不是个问题。我的应用程序往往使用不同的变量名,如果有重叠,我会在 .env 中将它们设置为空白 VAR= - gsf

41

source的问题在于它要求文件具有适当的bash语法,而一些特殊字符会破坏它:="'<>和其他字符。因此在某些情况下,您可以只需

source development.env

而且它会起作用。

然而,这个版本可以承受值中的每个特殊字符

set -a
source <(cat development.env | \
    sed -e '/^#/d;/^\s*$/d' -e "s/'/'\\\''/g" -e "s/=\(.*\)/='\1'/g")
set +a

解释:
  • -a 表示每个 bash 变量都会变成环境变量
  • /^#/d 删除注释(以 # 开头的字符串)
  • /^\s*$/d 删除空字符串,包括空格
  • "s/'/'\\\''/g" 将每个单引号替换为 '\'',这是 bash 中产生引号的技巧序列 :)
  • "s/=\(.*\)/='\1'/g" 将每个 a=b 转换为 a='b'

因此,您可以使用特殊字符 :)

要调试此代码,请将 source 替换为 cat,然后您将看到此命令生成的内容。


direnv 用户的注意事项:它有一个辅助函数 dotenv,请在你的 .envrc 文件中使用它代替:

[ -f ".env" ] && dotenv ".env"

1
在bash上,使用以下烦人的字符串:FOO =〜`#$&*()\ | [ =] {};'“<> /?!对我有效(TM)。 - Klaas van Schelven
1
这个对我来说几乎有效,但是必须将\s替换为[[:space:]],以使其在FreeBSD/Mac上的bash上也能正常工作:source <(cat .env | sed -e '/^#/d;/^[[:space:]]*$/d' -e "s/'/'\\\''/g" -e "s/=\(.*\)/='\1'/g") - takilara
2
对我来说有效,尽管我不得不用以下命令替换最后一个sed: sed -e '/^#/d;/^\s*$/d' -e "s/'/'\\\''/g" -e "s/\ *=\ */=/g")以转义等号周围的任何空格。 - Lars Ejaas
1
添加到你的dotfiles中的完美小代码片段 - 当我的.env文件中有分号时,它非常好用! - cody.codes

34
eval $(cat .env | sed 's/^/export /')

1
使用 eval $(cat .env | sed 's/^[^$]/export /') 可以让你在代码中使用空行,提高可读性。 - Mario Uher
2
我发现 cat .env | sed 's/^[^$]/export /' 去掉了初始字符。例如,对于文件 A=foo\nB=bar\n,我得到了 export =foo\nexport =bar\n。这种方法更适合跳过空行:cat .env | sed '/^$/! s/^/export /' - Owen S.
(我还要提醒UNIX代码高手们,在这两种情况下都不需要使用cateval $(sed 's/^/export /' .env)同样适用。) - Owen S.
不支持以 # 开头的注释行。 - stefcud
eval sed 's/^/export /' .env - Amanuel Nega

27

这里是另外一种 sed 解决方案,不需要运行 eval 或者需要 ruby:

source <(sed -E -n 's/[^#]+/export &/ p' ~/.env)

这将添加导出功能,保留以注释开头的行上的注释。

.env内容

A=1
#B=2

样例运行

$ sed -E -n 's/[^#]+/export &/ p' ~/.env
export A=1
#export B=2

当我构建一个用于在systemd unit文件中加载的文件时,我发现这特别有用,可以使用EnvironmentFile


22
我不确定为什么,或者我错过了什么,但在浏览大多数答案并失败后,我意识到使用这个 .env 文件:
MY_VAR="hello there!"
MY_OTHER_VAR=123

我可以简单地这样做:

source .env
echo $MY_VAR

输出:Hello there!

在Ubuntu Linux中似乎运行良好。


如果您在Docker中使用这样的env文件,MY_VAR将包含引号作为值的一部分 :) https://docs.docker.com/compose/env-file/ - kolypto
1
@kolypto 在高票答案中的任何其他命令也会发生同样的情况。这只是选择的示例。这只是为了显示您也可以只使用源文件 - 这是核心思想。其余的技巧是为了覆盖特殊符号。 - questionto42
也适用于Mac。 - Edgar Froes
1
你所忽略的是这并没有定义任何环境变量,而是定义了参数。它们是完全不同的:因为参数不会被继承,所以你调用的任何命令都看不到这些值。 - Konrad Rudolph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接