ChatGPT解决这个技术问题 Extra ChatGPT

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

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 但同样的问题仍然适用

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

与 Ruby 相同:stackoverflow.com/questions/2139080/…,一个可以做到这一点的 gem:github.com/bkeepers/dotenv
这是一个很好的问题,但措辞过于具体,带有特定的变量名称(“MINIENTREGA_FECHALIMITE”?这是什么意思?)和数字(3)。一般问题很简单,“如何将一组键/值对从文本文件导出到 shell 环境中”。
此外,这已经在 unix.SE 上得到了回答,并且可以说是更热门的话题。
一个提示可能对初学者有用:确保在其中获取环境变量时“执行”脚本。这样,您就不会让它们进入并污染您自己的环境,否则有时甚至会变得不安全,例如,当您将机密存储在其中一个环境变量中时。

N
Nate

这可能会有所帮助:

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

我使用它的原因是如果我想在我的 rails 控制台中测试 .env 东西。

gabrielf 提出了一种将变量保持在本地的好方法。这解决了从一个项目到另一个项目时的潜在问题。

env $(cat .env | xargs) rails

我用 bash 3.2.51(1)-release 对此进行了测试

更新:

要忽略以 # 开头的行,请使用此命令(感谢 Pete's comment):

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 系统上。

this answer 中,您可以通过以下方式自动检测操作系统:

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' ]; then

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

fi


谢谢,我喜欢这不需要在文件中添加任何内容 - 允许与 Foreman (Procfile) .env 格式兼容。
@BenjaminWheeler GNU linux 有 -d 用于设置分隔符,所以我正在尝试 env $(cat .env | xargs -d '\n') rails,但如果 .env 有空格,它仍然会出现找不到文件的错误。知道为什么这不起作用吗?
这是一个较短的变体eval $(cat .env) rails
作为一个简短的解释(帮助我理解解决方案实际在做什么),cat .env 将读取 .env 文件,然后我们将结果通过管道传递给 xargs,它是构造 cli 参数的辅助工具。最后,我们将参数和要运行的命令都传递给 env,它会加载环境变量并执行命令。
在将变量括在引号中时,我遇到了这个解决方案的问题,例如 REACT_APP_IFRAME_URI="http://localhost:3000"。引号被解析,这不是预期的。
f
fedorqui

-o allexport 允许导出所有以下变量定义。 +o allexport 禁用此功能。

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

奇迹般有效!即使 .env 文件中有评论。谢谢!
在一行set -o allexport; source conf-file; set +o allexport
当 Jenkins EnvInject 插件不起作用时,这是读取属性文件的好方法。谢谢!
@CMCDragonkai,对于 POSIX,它将是 set -a; . conf-file; set +a
如果环境变量中有空格,则此方法有效。许多其他人没有。虽然 eval() 方法可以,但使用 eval 我也有点奇怪
b
b4hand

您的方法的问题是 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

虽然逐行读取文件并将每一行传递给 export 并不理想,但也可以通过简单地在循环上使用输入重定向来解决该问题:while read line; do ... ; done < ./conf/$1
如果不是来自文件,请使用 < <(commands that generate output)
你有更多clean solution,我更喜欢set -o allexport
如果在系统之间使用这个 .env 文件,插入 export 会破坏它,例如 Java、SystemD 或其他工具
awk '{print "export " $0}' envfile 将导出添加到每行开头的便捷命令
c
cglacet
set -a
. ./env.txt
set +a

如果 env.txt 是这样的:

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

解释 -a 等价于 allexport。换句话说,shell 中的每个变量分配都被导出到环境中(供多个子进程使用)。更多信息可在 Set builtin documentation 中找到:

-a 创建或修改的每个变量或函数都被赋予导出属性并标记为导出到后续命令的环境。使用“+”而不是“-”会导致这些选项被关闭。这些选项也可以在调用 shell 时使用。当前的选项集可以在 $- 中找到。


如果有评论怎么办?和VAR2=$VAR1
嗨@Alexis。此处使用的 . 命令本质上类似于在终端上键入。您可以在终端上自己尝试一下,看看结果如何。注释将被忽略,并且对其他变量的引用将起作用,只要它们之前已定义。
是的,我试过它是这样工作的。感谢您的跟进!
H
Huan

我发现最有效的方法是:

export $(xargs < .env)

解释

当我们有这样的 .env 文件时:

key=val
foo=bar

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

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

局限性

它不处理值中有空格的情况。诸如 env 之类的命令会产生这种格式。 – @Shardj


不适用于 MacOS :(
谢谢!它适用于我的 Monterey 12.4 macOS
g
gsf

此处的其他几个答案中提到了 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
}

这看起来不错,但是您是否在离开目录时卸载环境变量?
我不会取消设置变量,这从来都不是问题。我的应用程序倾向于使用不同的变量名,如果有重叠,我会在 .env 中使用 VAR= 将它们设置为空白。
A
Amit Joki
eval $(cat .env | sed 's/^/export /')

使用 eval $(cat .env | sed 's/^[^$]/export /') 可以让您有空行以提高可读性。
我发现 cat .env | sed 's/^[^$]/export /' 去掉了初始字符。即对于文件A=foo\nB=bar\n,我得到export =foo\nexport =bar\n。这对于跳过空白行效果更好:cat .env | sed '/^$/! s/^/export /'
(为了 UNIX 代码高尔夫球手,我还指出,在任何一种情况下都不需要 cateval $(sed 's/^/export /' .env) 也可以。)
不支持以 # 开头的注释行
评估 sed 's/^/export /' .env
k
kolypto

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,您将看到此命令产生的结果。


在 bash 上为我 (TM) 工作,使用以下烦人的字符串: FOO=~`#$&*()\|[=]{}; '"<>/?!
太好了,谢谢!
这几乎对我有用,但必须用 [[:space:]] 换掉 \s 以使其也适用于 FreeBSD/Mac 上的 bash:``` source <(cat .env | sed -e '/^# /d;/^[[:space:]]*$/d' -e "s/'/'\\\''/g" -e "s/=(.*)/='\1'/ g") ```
t
tutuDajuju

这是另一个 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 file, with EnvironmentFile 中加载时特别有用。


在 OSX 中不支持多行
N
Nagev

我赞成 user4040650 的答案,因为它既简单又允许文件中的注释(即以 # 开头的行),这对我来说是非常可取的,因为可以添加解释变量的注释。只是在原始问题的上下文中重写。

如果脚本按照指示调用:minientrega.sh prac1,则 minientrega.sh 可能具有:

set -a # export all variables created next
source $1
set +a # stop exporting

# test that it works
echo "Ficheros: $MINIENTREGA_FICHEROS"

以下是从 set documentation 中提取的:

这个内置函数非常复杂,值得拥有自己的部分。 set 允许您更改 shell 选项的值并设置位置参数,或显示 shell 变量的名称和值。 set [--abefhkmnptuvxBCEHPT] [-o option-name] [argument ...] set [+abefhkmnptuvxBCEHPT] [+o option-name] [argument ...] 如果没有提供选项或参数,set 显示所有 shell 的名称和值变量和函数,根据当前语言环境排序,格式可重复用作设置或重置当前设置变量的输入。只读变量不能被重置。在 POSIX 模式下,仅列出 shell 变量。提供选项时,它们会设置或取消设置 shell 属性。选项(如果指定)具有以下含义: -a 创建或修改的每个变量或函数都被赋予导出属性并标记为导出到后续命令的环境。

这也是:

使用“+”而不是“-”会导致这些选项被关闭。这些选项也可以在调用 shell 时使用。当前的选项集可以在 $- 中找到。


J
Jules Colle

不完全确定为什么,或者我错过了什么,但是在运行了大多数答案并失败之后。我意识到这个 .env 文件:

MY_VAR="hello there!"
MY_OTHER_VAR=123

我可以简单地这样做:

source .env
echo $MY_VAR

输出:Hello there!

似乎在 Ubuntu linux 中工作得很好。


如果您在 Docker 中使用此类和 env 文件,您的 MY_VAR 将包含引号作为值的一部分 :) docs.docker.com/compose/env-file
@kolypto 投票率较高的任何其他命令也会发生同样的情况。这只是示例的选择。这只是为了表明您也可以直接获取它 - 这是核心思想。其余的技巧例如也包括特殊标志。
J
Jaydeep Solanki

改进 Silas Paul 的回答

将变量导出到子shell 会使它们成为命令的本地变量。

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


然后您可以使用此 (set -a; source dev.env; set +a; rails c) 来获得采购的好处(例如脚本可以执行)。
j
jwfearn

SAVE=$(set +o | grep allexport) && set -o allexport && . .env; eval "$SAVE"

这将保存/恢复您的原始选项,无论它们是什么。

使用 set -o allexport 的优点是可以在没有正则表达式的情况下正确跳过评论。

set +o 本身会以 bash 以后可以执行的格式输出您当前的所有选项。也很方便:set -o 本身,以人性化的格式输出您当前的所有选项。


如果您需要取消设置仅在 .env 中设置的变量,我可能会 exec env -i bash 在调用 eval 之前清除现有环境。
F
Flavien Volken

我发现的最短方法:

您的 .env 文件:

VARIABLE_NAME="A_VALUE"

然后就

. ./.env && echo ${VARIABLE_NAME}

奖励:因为它是一个简短的单行,它在 package.json 文件中非常有用

  "scripts": {
    "echo:variable": ". ./.env && echo ${VARIABLE_NAME}"
  }

如果你有很多变量怎么办?
@Madeo,您可以添加任意数量的行,就像行 VARIABLE_NAME="A_VALUE"
警告:这会将这些变量导出到您的活动外壳中...
是的,.source 的同义词,因此也是如此
g
glen

这是我的变体:

  with_env() {
    (set -a && . ./.env && "$@")
  }

与之前的解决方案相比:

它不会泄漏范围外的变量(来自 .env 的值不会暴露给调用者)

不破坏设置选项

返回执行命令的退出代码

使用 posix 兼容的 set -a

使用 .而不是来源以避免bashism

如果 .env 加载失败,则不会调用命令

with_env rails console

您也可以内联运行(变量公开给您当前的终端会话):set -a && . ./.env && "$@" && echo "your comand here"
g
gogo

如果 env 支持 -S 选项,则可以使用换行符或转义字符,例如 \n\t(请参阅 env):

env -S "$(cat .env)" command

.env 文件示例:

KEY="value with space\nnewline\ttab\tand
multiple
lines"

测试:

env -S "$(cat .env)" sh -c 'echo "$KEY"'

J
Javier Buzzi

更简单:

抓取文件的内容 删除所有空行(以防你分隔了一些东西)删除所有注释(以防你添加了一些......)添加导出到所有行评估整个事情

eval $(cat .env | sed -e /^$/d -e /^#/d -e 's/^/export /')

另一种选择(您不必运行 eval (感谢@Jaydeep)):

export $(cat .env | sed -e /^$/d -e /^#/d | xargs)

最后,如果您想让您的生活真正轻松,请将其添加到您的 ~/.bash_profile

function source_envfile() { export $(cat $1 | sed -e /^$/d -e /^#/d | xargs); }

(确保您重新加载您的 BASH 设置!!!source ~/.bash_profile 或.. 只需创建一个新选项卡/窗口即可解决问题)您可以这样称呼它:source_envfile .env


我必须从管道的 gitlab 秘密变量中读取 .env 文本:根据您的解决方案,此命令对我有用:source <( echo $(sed -E -n 's/[^#]+/ &/ p' <(echo "$tr -d '\r'" | tr -d '\r')) );。不知何故,gitlab 用 windows 回车保存了秘密变量,所以我不得不用 tr -d '\r' 修剪它。
C
Community

我在 Mac 上使用 docker-compose 和 .env 文件,并想将 .env 导入我的 bash shell(用于测试),这里的“最佳”答案是在以下变量上绊倒:

.env

NODE_ARGS=--expose-gc --max_old_space_size=2048

解决方案

所以我最终使用了 eval,并将我的 env var defs 用单引号括起来。

eval $(grep -v -e '^#' .env | xargs -I {} echo export \'{}\')

重击版本

$ /bin/bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
Copyright (C) 2007 Free Software Foundation, Inc.

K
Koichi Nakashima

使用 shdotenv

dotenv 支持 shell 和 POSIX 兼容的 .env 语法规范
https://github.com/ko1nksm/shdotenv

eval "$(shdotenv)"

用法

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

  -d, --dialect DIALECT  Specify the .env dialect [default: posix]
                           (posix, ruby, node, python, php, go, rust, docker)
  -s, --shell SHELL      Output in the specified shell format [default: posix]
                           (posix, fish)
  -e, --env ENV_PATH     Location of the .env file [default: .env]
                           Multiple -e options are allowed
  -o, --overload         Overload predefined environment variables
  -n, --noexport         Do not export keys without export prefix
  -g, --grep PATTERN     Output only those that match the regexp pattern
  -k, --keyonly          Output only variable names
  -q, --quiet            Suppress all output
  -v, --version          Show the version and exit
  -h, --help             Show this message and exit

要求

shdotenv 是一个带有嵌入式 awk 脚本的单文件 shell 脚本。

POSIX shell(dash、bash、ksh、zsh 等)

awk(gawk、nawk、mawk、busybox awk)


很棒的工具和对细节的高度关注。谢谢!!
@PierreGramme 使用至少运行两个分叉的专用工具来解决理解 one command 中减少的概念的问题似乎有点矫枉过正!
@FHauri 也许有点矫枉过正,但这个问题有 43 个不同的答案:真的是这么简单的问题吗?在我的用例中,我有一个用 Python dialect 编写的 .env 文件并将其应用于 Bash。由于管理空间等的不同约定,不能简单地使用 source。该工具及其对差异的分析对我来说绝对有用
b
b4hand

您可以使用原始脚本来设置变量,但您需要通过以下方式调用它(使用独立点):

. ./minientrega.sh

cat | while read 方法也可能存在问题。我建议使用方法 while read line; do .... done < $FILE

这是一个工作示例:

> cat test.conf
VARIABLE_TMP1=some_value

> cat run_test.sh
#/bin/bash
while read line; do export "$line";
done < test.conf
echo "done"

> . ./run_test.sh
done

> echo $VARIABLE_TMP1
some_value

与大多数其他答案不同,此解决方案不会将 test.conf 作为脚本文件进行评估。这使它变得更好。除非您确实需要,否则不允许编写脚本会更安全,特别是如果有人没有意识到正在发生的事情(或忘记了)。
A
Alex Skrypnyk
t=$(mktemp) && export -p > "$t" && set -a && . ./.env && set +a && . "$t" && rm "$t" && unset t

这个怎么运作

创建临时文件。将所有当前环境变量值写入临时文件。启用将源脚本中所有声明的变量导出到环境。读取 .env 文件。所有变量都将导出到当前环境中。禁止将源脚本中所有声明的变量导出到环境。读取临时文件的内容。每一行都会声明 -x VAR="val" 将每个变量导出到环境中。删除临时文件。取消设置保存临时文件名的变量。

特征

保留环境中已设置的变量值

.env 可以有评论

.env 可以有空行

.env 不需要像其他答案中那样的特殊页眉或页脚(set -a 和 set +a)

.env 不需要为每个值都导出

单线


我非常感谢您的解决方案。在大多数项目中,您都有带有注释、空格、没有导出语句等的 .env 文件。非常好!
V
Victor Roetman

在其他答案的基础上,这是一种仅导出文件中行子集的方法,包括带有空格的值,例如 PREFIX_ONE="a word"

set -a
. <(grep '^[ ]*PREFIX_' conf-file)
set +a

T
Tudor Ilisoi

我的.env:

#!/bin/bash
set -a # export all variables

#comments as usual, this is a bash script
USER=foo
PASS=bar

set +a #stop exporting variables

调用:

source .env; echo $USER; echo $PASS

参考https://unix.stackexchange.com/questions/79068/how-to-export-variables-that-are-set-all-at-once


A
Alex

我的要求是:

没有导出前缀的简单 .env 文件(为了与 dotenv 兼容)

引号中的支持值:TEXT="alpha bravo charlie"

支持以 # 和空行为前缀的注释

mac/BSD 和 linux/GNU 通用

从上面的答案编译的完整工作版本:

  set -o allexport
  eval $(grep -v '^#' .env | sed 's/^/export /')
  set +o allexport

如果您在它们前面加上“export”,那么“-o allexport”有什么意义?
l
lapsus63

我的版本:

我打印文件,删除注释行,空行,并从“=”符号中拆分键/值。然后我只应用导出命令。

此解决方案的优点是文件可以在值中包含特殊字符,如管道、html 标记等,并且值不必像真正的属性文件那样用引号括起来。

# Single line version
cat myenvfile.properties | grep -v '^#' | grep '=' | while read line; do IFS=\= read k v <<< $line; export $k="$v"; done

# Mutliline version:
cat myenvfile.properties | grep -v '^#' | grep '=' | while read line; do 
  IFS=\= read k v <<< $line
  export $k="$v"
done


G
Guss

我对早期建议的解决方案有疑问:

@anubhava 的解决方案使编写 bash 友好的配置文件非常烦人,而且 - 您可能不想总是导出您的配置。

@Silas Paul 解决方案在您的变量中有空格或其他字符在引用值中运行良好时会中断,但是 $() 会弄得一团糟。

这是我的解决方案,它仍然是非常糟糕的 IMO - 并没有解决 Silas 解决的“仅导出给一个孩子”问题(尽管您可能可以在子 shell 中运行它以限制范围):

source .conf-file
export $(cut -d= -f1 < .conf-file)

P
Polv

修改自@Dan Kowalczyk

我把它放在 ~/.bashrc 中。

set -a
. ./.env >/dev/null 2>&1
set +a

与 Oh-my-Zsh 的 dotenv 插件很好地交叉兼容。 (有 Oh-my-bash,但没有 dotenv 插件。)


I
Iwan Aucamp

我用这个:

source <(cat .env \
  | sed -E '/^\s*#.*/d' \
  | tr '\n' '\000' \
  | sed -z -E 's/^([^=]+)=(.*)/\1\x0\2/g' \
  | xargs -0 -n2 bash -c 'printf "export %s=%q;\n" "${@}"' /dev/null)

首先删除评论:

sed -E '/^\s*#.*/d'

然后转换为空分隔符而不是换行符:

tr '\n' '\000'

然后用null替换equal:

sed -z -E 's/^([^=]+)=(.*)/\1\x0\2/g'

然后将对打印到有效的引用 bash 导出(使用 bash printf for %q):

xargs -0 -n2 bash -c 'printf "export %s=%q;\n" "${@}"' /dev/null

然后最终采购所有这些。

它应该适用于几乎所有具有所有特殊字符的情况。


A
Anand Tripathi

首先,创建一个环境文件,该文件将包含如下环境的所有键值对,并在我的例子中将其命名为 env_var.env

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

然后创建一个脚本,该脚本将导出 Python 环境的所有环境变量,如下所示,并将其命名为 export_env.sh

#!/usr/bin/env bash

ENV_FILE="$1"
CMD=${@:2}

set -o allexport
source $ENV_FILE
set +o allexport

$CMD

该脚本将第一个参数作为环境文件,然后导出该文件中的所有环境变量,然后运行该命令。

用法:

./export_env.sh env_var.env python app.py

L
Lucas B

我如何保存变量:

printenv | sed 's/\([a-zA-Z0-9_]*\)=\(.*\)/export \1="\2"/' > myvariables.sh

我如何加载它们

source myvariables.sh