【问题标题】:Set environment variables from file of key/value pairs从键/值对文件中设置环境变量
【发布时间】:2013-10-20 07:44:03
【问题描述】:

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 上得到了回答,并且可以说是更多的话题。
  • 一个可能对初学者有用的提示:确保在其中获取环境变量时“执行”脚本。这样一来,您就不会让它们进入并污染您自己的环境,否则有时甚至会变得不安全,例如,当您将机密存储在其中一个环境变量中时。

标签: bash variables environment-variables


【解决方案1】:

改编自@Dan Kowalczyk

我把这个放在~/.bashrc

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

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

【讨论】:

    【解决方案2】:

    对于那些使用 ruby​​ 的人,我制作了一个名为 dotenv_export 的小型实用 gem。

    用法

    dotenv_export 是一个小型实用命令,它读取.env 文件并使用the ruby dotenv implementation 将其转换为export 语句。

    # first install `dotenv_export`
    gem install dotenv_export
    

    然后,在您的.bash_profile 或您要加载环境变量的任何 shell 环境中,执行以下命令:

    eval "$(dotenv-export /path/to/.env)"
    

    【讨论】:

    • 这太愚蠢了——为过去 40 多年开发的每一个 shell 中可以用两行代码完成的事情拉 250MB+ 的依赖项 :-) ...但你可以做得更糟,您可以将 Docker 带入其中 ;-)
    【解决方案3】:

    我用这个:

    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)
    

    首先删除 cmets:

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

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

    tr '\n' '\000'
    

    然后将equal替换为null:

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

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

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

    然后最终采购所有这些。

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

    【讨论】:

      【解决方案4】:

      我如何保存变量:

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

      我如何加载它们

      source myvariables.sh
      

      【讨论】:

        【解决方案5】:

        如果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"'
        

        【讨论】:

          【解决方案6】:

          source 的问题在于它要求文件具有正确的 bash 语法,一些特殊字符会破坏它:="'&lt;&gt;、和别的。所以在某些情况下你可以只做

          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 删除 cmets(以 # 开头的字符串)
          • /^\s*$/d 删除空字符串,包括空格
          • "s/'/'\\\''/g"'\'' 替换每个单引号,这是 bash 中产生引号的技巧序列:)
          • "s/=\(.*\)/='\1'/g" 将每个 a=b 转换为 a='b'

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

          要调试此代码,请将source 替换为cat,您将看到此命令产生的结果。

          【讨论】:

          • 在 bash 上为我工作 (TM),使用以下烦人的字符串:FOO=~`#$&*()\|[=]{}; '"/?!
          【解决方案7】:

          如果您打算将exec 作为脚本的最后一个命令,则可以使用execlineb 解释器来增加一个选项。这就是脚本最后一行的样子:

          #!/bin/sh
          ...
          exec envfile -I /etc/default/bla envfile /etc/default/bla-bla my_cmd
          

          envfile ... 是来自execline 套件的命令,它们依赖于“chain loading”。 顺便说一句,一旦你进入这个兔子洞,你可能会发现你不再需要 shell(......并重新考虑你的其他生活选择:-) 通过使用 execlineb 解释器而不是使用解释器以最小的开销启动服务非常有用完全壳,即:

          #!/bin/execlineb
          ...
          envfile -I /etc/default/bla
          envfile /etc/default/bla-bla
          my_cmd
          

          【讨论】:

            【解决方案8】:

            使用 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。该工具及其差异分析对我来说绝对有用
            【解决方案9】:

            我对此的贡献是从@扩展answer user4040650 允许在 git repo 中轻松使用。它将尝试从当前目录中获取 .env 文件,或者如果该文件不存在,则从您所在的 git repo 中获取 .env 。如果您已 cd 进入子目录然后不要'不必来源 ../../.env 之类的。

            我已经把它放在了我的 .bashrc 中,所以我只需要在需要的地方调用 setenv

            setenv() {
              local env_path
              if { [ -f .env ] && env_path='.env'; } || { env_path=$(git  rev-parse --show-toplevel 2>/dev/null)/.env && [ -f "$env_path" ]; }; then
                echo "sourcing $env_path"
                set -o allexport
                source "$env_path"
                set +o allexport
              else
                echo "No env file found"
              fi
            }
            

            【讨论】:

              【解决方案10】:

              符合 POSIX 的解决方案(不依赖 bash)

              正如其他人所指出的,在此处使用 for/while 循环的问题在于,变量不会在 shell 及其子 shell 之间共享。然而,我们可以做的是使用 args/stdin/stdout 在 shell 之间传递文本。

              当我们获取脚本时,在子 shell 中设置环境变量无济于事

              变量不会反向传播 - 但我们知道我们可以将文本发回。此文本也可以是代码,我们可以在当前 shell 中使用 eval 对其进行评估。

              如果我们改为生成设置所有环境变量的代码,然后评估结果会怎样?

              create_exports_script() {
                  echo "$1" | while read line; do
                      echo "export $line"
                  done
              }
              
              file_contents=$(cat "./conf/myconf.env")
              eval $(create_exports_script "$file_contents")
              

              这种在 bash 中的函数式元编程可以非常灵活。你也可以这样生成other languages than bash/sh

              【讨论】:

                【解决方案11】:

                我的版本:

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

                此解决方案的优点是文件可以在值中包含特殊字符,如管道、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
                
                

                【讨论】:

                  【解决方案12】:
                  sh -ac '. conf-file; yourcommand'
                  

                  -a 开关导出所有变量,以便程序可以使用它们。

                  不同于更长的版本set -a; . conf-file; set +a; yourcommand 使用sh 确保导出的值不会永久污染当前环境。它仅为在子 shell 中运行的程序提供和导出变量。

                  【讨论】:

                    【解决方案13】:

                    export答案。

                    互动练习

                    由于交互式,您可以尝试内联!

                    $ mkdir conf && printf 'MINIENTREGA_%s="%s"\n' FECHALIMITE 2011-03-31 FICHEROS \
                        "informe.txt programa.c" DESTINO ./destino/entrega-prac1 >conf/prac1 
                    
                    $ set -- prac1
                    $ while read -r line; do export $line; done <"conf/$1"
                    
                    bash: export: `programa.c"': not a valid identifier
                    
                    $ while read -r line; do LANG=C export "$line"; done <"conf/$1"
                    $ echo "$MINIENTREGA_FICHEROS"
                    "informe.txt programa.c"
                    

                    注意双引号!

                    source别名.

                    $ set -- prac1
                    $ . "conf/$1"
                    $ echo "$MINIENTREGA_FICHEROS"
                    informe.txt programa.c
                    

                    好的,那么现在export

                    export 命令告诉 shell export shell variablesenvironment... 所以你必须导出 script em> 变量在使用它们之前是任何子进程(如rubypythonperl 甚至是另一个shell 脚本。

                    清理以前的操作以进行进一步的演示

                    $ declare +x MINIENTREGA_FECHALIMITE MINIENTREGA_FICHEROS MINIENTREGA_DESTINO
                    $ unset MINIENTREGA_FECHALIMITE MINIENTREGA_FICHEROS MINIENTREGA_DESTINO
                    

                    因此,从交互式 shell 中,最简单的尝试方法是运行另一个

                    $ set -- prac1
                    $ . "conf/$1"
                    $ bash -c 'declare -p MINIENTREGA_FICHEROS'
                    bash: line 1: declare: MINIENTREGA_FICHEROS: not found
                    
                    $ export MINIENTREGA_FECHALIMITE MINIENTREGA_FICHEROS MINIENTREGA_DESTINO
                    $ bash -c 'declare -p MINIENTREGA_FICHEROS'
                    declare -x MINIENTREGA_FICHEROS="informe.txt programa.c"
                    

                    样本包装器用于导出变量

                    最小的包装器,没有安全问题(在寻找其他用户可编辑的脚本时要小心!!)。

                    #!/bin/sh
                    
                    while IFS== read -r varname _;do
                        case $varname in
                             *[!A-Za-z0-9_]* | '' ) ;;
                             * ) export $varname ;;
                        esac
                    done <conf/$1
                    . conf/$1
                    
                    busybox sh -c 'set | grep MINIENTREGA'
                    

                    prac1 作为参数运行,应该会产生:

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

                    很好

                    • 获取配置文件与声明变量相同。

                    • 导出你的变量是一个指示 shell 在全局环境中为任何子进程共享他的变量。

                    这两个操作可以按任何顺序进行,无所谓。唯一的要求是在您尝试运行任何子进程之前完成这两项操作。

                    您甚至可以通过在您的配置文件中导出 来同时执行这两项操作,例如:

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

                    【讨论】:

                      猜你喜欢
                      • 2019-07-28
                      • 2012-04-22
                      • 2014-12-17
                      • 2021-05-16
                      • 1970-01-01
                      • 2022-07-20
                      • 2019-04-02
                      • 1970-01-01
                      相关资源
                      最近更新 更多