【问题标题】:Reading java .properties file from bash从 bash 读取 java .properties 文件
【发布时间】:2009-11-05 18:04:23
【问题描述】:

我正在考虑使用 sed 来读取 .properties 文件,但想知道是否有更聪明的方法可以从 bash 脚本中做到这一点?

【问题讨论】:

  • 这是一个很好的问题 :) 在这个具体的问题中,我想看看是否有一个我想念的简单方法(不知道\太愚蠢等)
  • 尝试使用 Gradle 来构建和部署,而不是 BASH

标签: bash scripting properties


【解决方案1】:

这可能是最简单的方法:grep + cut

# Usage: get_property FILE KEY
function get_property
{
    grep "^$2=" "$1" | cut -d'=' -f2
}

【讨论】:

  • 这是我最喜欢的简单案例。但我会修改 grep "^$PROP_KEY="(包括 ^ 和 = 分隔符),因为属性键经常是其他键的前缀或后缀。
  • 这将在任何包含等号的属性上失败。更好(更完整、更短,甚至更快)的解决方案是sed "/^$2=/!d; s///" "$1",它与上面的grep 具有相同的逻辑,但也会删除匹配的部分,只留下要打印的参数值。
  • 属性允许属性名称和等号之间有空格。在 grep 或 sed 中使用正则表达式 ^$2 *= 允许使用空格。
  • 我不确定这是否能处理多行属性。
  • 另外,如果值中有=(相等)字符,这会减少值。使用cut -d'=' -f2-。请参阅末尾的减号。
【解决方案2】:

上述解决方案适用于基础。我不认为它们涵盖多行值。这是一个 awk 程序,它将从标准输入解析 Java 属性并将 shell 环境变量生成到标准输出:

BEGIN {
    FS="=";
    print "# BEGIN";
    n="";
    v="";
    c=0; # Not a line continuation.
}
/^\#/ { # The line is a comment.  Breaks line continuation.
    c=0;
    next;
}
/\\$/ && (c==0) && (NF>=2) { # Name value pair with a line continuation...
    e=index($0,"=");
    n=substr($0,1,e-1);
    v=substr($0,e+1,length($0) - e - 1);    # Trim off the backslash.
    c=1;                                    # Line continuation mode.
    next;
}
/^[^\\]+\\$/ && (c==1) { # Line continuation.  Accumulate the value.
    v= "" v substr($0,1,length($0)-1);
    next;
}
((c==1) || (NF>=2)) && !/^[^\\]+\\$/ { # End of line continuation, or a single line name/value pair
    if (c==0) {  # Single line name/value pair
        e=index($0,"=");
        n=substr($0,1,e-1);
        v=substr($0,e+1,length($0) - e);
    } else { # Line continuation mode - last line of the value.
        c=0; # Turn off line continuation mode.
        v= "" v $0;
    }
    # Make sure the name is a legal shell variable name
    gsub(/[^A-Za-z0-9_]/,"_",n);
    # Remove newlines from the value.
    gsub(/[\n\r]/,"",v);
    print n "=\"" v "\"";
    n = "";
    v = "";
}
END {
    print "# END";
}

如您所见,多行值会使事情变得更复杂。要查看 shell 中属性的值,只需在输出中输入:

cat myproperties.properties | awk -f readproperties.awk > temp.sh
source temp.sh

变量将用“_”代替“.”,因此属性 some.property 将是 shell 中的 some_property。

如果您有具有属性插值的 ANT 属性文件(例如 '${foo.bar}'),那么我建议将 Groovy 与 AntBuilder 一起使用。

这里是my wiki page on this very topic

【讨论】:

  • 当多行值是最后定义并以空行结束时,我已经更正了脚本的一个错误
  • 非常好。源
【解决方案3】:

我写了一个脚本来解决这个问题,放在我的github上。

properties-parser

【讨论】:

  • 太棒了!将脚本复制到我的项目中并很好地读取属性。
【解决方案4】:

一种选择是编写一个简单的 Java 程序来为您完成 - 然后在您的脚本中运行 Java 程序。如果您只是从单个属性文件中读取属性,那可能看起来很愚蠢。但是,当您尝试从由属性文件支持的 Commons Configuration CompositeConfiguration 之类的东西中获取配置值时,它变得非常有用。有一段时间,我们采取了在我们的 shell 脚本中实现我们需要的方式来获得与 CompositeConfiguration 相同的行为。然后我们明智地意识到我们应该让CompositeConfiguration 为我们做这项工作!我不希望这是一个受欢迎的答案,但希望你会发现它有用。

【讨论】:

    【解决方案5】:

    如果你想使用 sed 解析 -any- .properties 文件,你可能会得到一个相当复杂的解决方案,因为格式允许换行、不带引号的字符串、unicode 等:http://en.wikipedia.org/wiki/.properties

    一种可能的解决方法是使用 java 本身将 .properties 文件预处理为对 bash 友好的文件,然后获取它。例如:

    .properties 文件:

    line_a : "ABC"
    line_b = Line\
             With\ 
             Breaks!
    line_c = I'm unquoted :(
    

    会变成:

    line_a="ABC"
    line_b=`echo -e "Line\nWith\nBreaks!"`
    line_c="I'm unquoted :("
    

    当然,这会产生更差的性能,但实现会更简单/更清晰。

    【讨论】:

    • line_b=$'Line\nWith\nBreaks!'
    • 丹尼斯,不知怎的,它不起作用:pastebin.com/m503047e8。我错过了什么吗?
    【解决方案6】:

    在 Perl 中:

    while(<STDIN>) {
       ($prop,$val)=split(/[=: ]/, $_, 2);
       # and do stuff for each prop/val
    }
    

    未经测试,应该更能容忍前导/尾随空格、cmets 等,但你明白了。您是否使用 Perl(或其他语言)而不是 sed,实际上取决于您将属性从文件中解析出来后要如何处理。

    请注意(如 cmets 中突出显示的那样)Java 属性文件可以有 multiple forms of delimiters(尽管我在实践中没有看到除了冒号之外的任何东西)。因此,拆分使用选择的字符进行拆分。

    最终,您最好使用 Perl 中的 Config::Properties 模块,该模块旨在解决这个特定问题。

    【讨论】:

    • 那不一定行得通。属性文件的键和值可以由空格、= 字符或 : 字符分隔(也可能有其他方式)。我相信您还可以混合和匹配您在单个属性文件中使用的分隔符。
    • @Thomas - 注意。虽然在实践中我不记得看到除了冒号以外的任何东西。
    【解决方案7】:

    我有一些 shell scripts 需要查找一些 .properties 并将它们用作我没有编写的程序的参数。脚本的核心是这样的一行:

    dbUrlFile=$(grep database.url.file etc/zocalo.conf | sed -e "s/.*: //" -e "s/#.*//")
    

    实际上,这就是键的 grep 并过滤掉冒号之前和任何散列之后的内容。

    【讨论】:

      【解决方案8】:

      如果您想使用“shell”,解析文件并进行适当编程控制的最佳工具是 (g)awk。仅使用 sed 简单替换。

      【讨论】:

        【解决方案9】:

        我有时只是将属性文件导入 bash 脚本。这将导致在脚本中使用文件中的名称和内容设置环境变量。也许这对你来说也足够了。如果你必须做一些“真正的”解析,当然,这不是要走的路。

        【讨论】:

          【解决方案10】:

          嗯,我今天也遇到了同样的问题。这是穷人的解决方案,诚然比聪明更直接;)

          decl=`ruby -ne 'puts chomp.sub(/=(.*)/,%q{="\1";}).gsub(".","_")' my.properties`
          eval $decl 
          

          然后,可以将属性“my.java.prop”作为 $my_java_prop 访问。

          这可以通过 sed 或其他方式来完成,但我最终还是选择了 ruby​​ 的 'irb',这对于实验来说很方便。 它非常有限(点只能在 '=' 之前替换,不处理注释),但可以作为起点。

          @Daniel,我尝试获取它,但 Bash 不喜欢变量名中的点。

          【讨论】:

            【解决方案11】:

            我已经取得了一些成功

                PROPERTIES_FILE=project.properties
            function source_property {
              local name=$1
              eval "$name=\"$(sed -n '/^'"$name"'=/,/^[A-Z]\+_*[A-Z]*=/p' $PROPERTIES_FILE|sed -e 's/^'"$name"'=//g' -e 's/"/\\"/g'|head -n -1)\""
            }
            
                source_property 'SOME_PROPERTY'
            

            【讨论】:

              【解决方案12】:

              这是一种解决方案,可以正确解析引号并在未给出引号时以空格终止。这是安全的:没有使用eval

              我在我的 .bashrc 和 .zshrc 中使用此代码从 shell 脚本中导入变量:

              # Usage: _getvar VARIABLE_NAME [sourcefile...]
              # Echos the value that would be assigned to VARIABLE_NAME
              _getvar() {
                local VAR="$1"
                shift
                awk -v Q="'" -v QQ='"' -v VAR="$VAR" '
                  function loc(text) { return index($0, text) }
                  function unquote(d) { $0 = substr($0, eq+2) d; print substr($0, 1, loc(d)-1) }
                  { sub(/^[ \t]+/, ""); eq = loc("=") }
                  substr($0, 1, eq-1) != VAR { next }  # assignment is not for VAR: skip
                  loc("=" QQ) == eq { unquote(QQ); exit }
                  loc("="  Q) == eq { unquote( Q); exit }
                  { print substr($1, eq + 1); exit }
                ' "$@"
              }
              

              这会保存所需的变量名,然后移动参数数组,以便将其余的作为文件传递给awk

              因为在awk 中调用shell 变量和引用引号字符非常困难,所以我在命令行中将它们定义为awk 变量。 Q 是单引号(撇号)字符,QQ 是双引号,VAR 是我们之前保存的第一个参数。

              为了更方便,有两个辅助函数。第一个返回当前行中给定文本的位置,第二个使用引号字符d(用于“分隔符”)打印行中前两个引号之间的内容。有一个杂散的d 连接到第一个substr 以防止多行字符串(请参阅下面的“注意事项”)。

              虽然我编写了用于 POSIX shell 语法解析的代码,但它似乎与您的格式仅不同之处在于分配周围是否有空格。您可以通过在 awk 的第 4 行的 sub(…) 之前添加 sub(/[ \t]*=[ \t]*/, "="); 来将该功能添加到上述代码中(注意:第 1 行是空白的)。

              第四行去掉前导空白并保存第一个等号的位置。请确认您的awk 支持\t 作为选项卡,这在古代 UNIX 系统上无法保证。

              substr 行将等号之前的文本与VAR 进行比较。如果不匹配,则该行正在分配一个不同的变量,因此我们跳过它并移至下一行。

              现在我们知道我们已经得到了请求的变量赋值,所以只需解开引号即可。我们通过搜索="(第6行)或='(第7行)或不带引号(第8行)的第一个位置来做到这一点。这些行中的每一行都打印分配的值。

              警告:如果有转义的引号字符,我们将返回一个截断的值。检测到这一点有点不重要,我决定不实施它。还有一个多行引号的问题,它在第一个换行符处被截断(这是上面提到的“流浪d”的目的)。此页面上的大多数解决方案都存在这些问题。

              【讨论】:

                【解决方案13】:

                为了让 Java 进行棘手的解析,这里有一个解决方案,使用 jrunscript 以 bash read-friendy(键、制表符、值、空字符)方式打印键和值:

                #!/usr/bin/env bash
                jrunscript -e '
                        p = new java.util.Properties();
                        p.load(java.lang.System.in);
                        p.forEach(function(k,v) { out.format("%s\t%s\000", k, v); });
                    ' < /tmp/test.properties \
                | while IFS=$'\t' read -d $'\0' -r key value; do
                    key=${key//./_}
                    printf -v "$key" %s "$value"
                    printf '=> %s = "%s"\n' "$key" "$value"
                done
                

                我在@david-foerster 的this answer 中找到了printf -v

                引用jrunscript:警告:Nashorn 引擎计划从未来的 JDK 版本中删除

                【讨论】: