linux 如何使用Shell脚本读取.properties文件,该文件包含带有句点字符的键

6za6bjd0  于 2022-12-18  发布在  Linux
关注(0)|答案(7)|浏览(747)

我正在尝试从包含句点(.)字符的shell脚本读取属性文件,如下所示:

# app.properties
db.uat.user=saple user
db.uat.passwd=secret

#/bin/sh
function pause(){
   read -p "$*"
}

file="./app.properties"

if [ -f "$file" ]
then
    echo "$file found."
 . $file

echo "User Id " $db.uat.user
echo "user password =" $db.uat.passwd
else
    echo "$file not found."
fi

我已经尝试解析文件后,源文件,但它是不工作的,因为键包含“.”字符,并有空格在该值也。
我的属性文件始终驻留在脚本所在的目录中或/usr/share/doc中的某个位置

vcudknz3

vcudknz31#

我在bash脚本中使用简单的**grep内部函数从.properties文件接收属性。
这个属性文件我用在两个地方-设置开发环境和作为应用程序参数。
我认为
grep**在大循环中可能运行缓慢,但在我准备dev环境时,它满足了我的需求。
希望有人会觉得这个有用。

示例:

文件:设置.sh

#!/bin/bash

ENV=${1:-dev}

function prop {
    grep "${1}" env/${ENV}.properties|cut -d'=' -f2
}

docker create \
    --name=myapp-storage \
    -p $(prop 'app.storage.address'):$(prop 'app.storage.port'):9000 \
    -h $(prop 'app.storage.host') \
    -e STORAGE_ACCESS_KEY="$(prop 'app.storage.access-key')" \
    -e STORAGE_SECRET_KEY="$(prop 'app.storage.secret-key')" \
    -e STORAGE_BUCKET="$(prop 'app.storage.bucket')" \
    -v "$(prop 'app.data-path')/storage":/app/storage \
    myapp-storage:latest

docker create \
    --name=myapp-database \
    -p "$(prop 'app.database.address')":"$(prop 'app.database.port')":5432 \
    -h "$(prop 'app.database.host')" \
    -e POSTGRES_USER="$(prop 'app.database.user')" \
    -e POSTGRES_PASSWORD="$(prop 'app.database.pass')" \
    -e POSTGRES_DB="$(prop 'app.database.main')" \
    -e PGDATA="/app/database" \
    -v "$(prop 'app.data-path')/database":/app/database \
    postgres:9.5

文件:环境文件/dev.properties

app.data-path=/apps/myapp/

#==========================================================
# Server properties
#==========================================================
app.server.address=127.0.0.70
app.server.host=dev.myapp.com
app.server.port=8080

#==========================================================
# Backend properties
#==========================================================
app.backend.address=127.0.0.70
app.backend.host=dev.myapp.com
app.backend.port=8081
app.backend.maximum.threads=5

#==========================================================
# Database properties
#==========================================================
app.database.address=127.0.0.70
app.database.host=database.myapp.com
app.database.port=5432
app.database.user=dev-user-name
app.database.pass=dev-password
app.database.main=dev-database

#==========================================================
# Storage properties
#==========================================================
app.storage.address=127.0.0.70
app.storage.host=storage.myapp.com
app.storage.port=4569
app.storage.endpoint=http://storage.myapp.com:4569
app.storage.access-key=dev-access-key
app.storage.secret-key=dev-secret-key
app.storage.region=us-east-1
app.storage.bucket=dev-bucket

用法:

./setup.sh dev
j2cgzkjk

j2cgzkjk2#

由于(Bourne)shell变量不能包含点,你可以用下划线替换它们。读每一行,把关键字中的.翻译成_并求值。

#/bin/sh

file="./app.properties"

if [ -f "$file" ]
then
  echo "$file found."

  while IFS='=' read -r key value
  do
    key=$(echo $key | tr '.' '_')
    eval ${key}=\${value}
  done < "$file"

  echo "User Id       = " ${db_uat_user}
  echo "user password = " ${db_uat_passwd}
else
  echo "$file not found."
fi

注意上面的代码只翻译了. to _,如果你有一个更复杂的格式,你可能需要使用额外的翻译。我最近不得不解析一个完整的Ant属性文件,里面有很多讨厌的字符,我不得不用途:

key=$(echo $key | tr .-/ _ | tr -cd 'A-Za-z0-9_')
fhity93d

fhity93d3#

由于BASH shell中的变量名不能包含点或空格,因此最好在BASH中使用如下关联数组:

#!/bin/bash

# declare an associative array
declare -A arr

# read file line by line and populate the array. Field separator is "="
while IFS='=' read -r k v; do
   arr["$k"]="$v"
done < app.properties

测试:

使用declare -p显示结果:

> declare -p arr  

        declare -A arr='([db.uat.passwd]="secret" [db.uat.user]="saple user" )'
azpvetkf

azpvetkf4#

对于性能极高且与BASH 3.0兼容的解决方案:
文件:加载属性.sh

function loadProperties() {
  local fileName=$1
  local prefixKey=$2

  if [ ! -f "${fileName}" ]; then
    echo "${fileName} not found!"
    return 1
  fi

  while IFS='=' read -r origKey value; do
    local key=${origKey}
    key=${key//[!a-zA-Z0-9_]/_} 
    if [[ "${origKey}" == "#"*   ]]; then
      local ignoreComments
    elif [ -z "${key}" ]; then
      local emptyLine
    else
      if [[ "${prefixKey}${key}" =~ ^[0-9].* ]]; then
        key=_${key}
      fi
      eval ${prefixKey}${key}=\${value}
    fi
  done < <(grep "" ${fileName})
}

这里提供的其他解决方案都很棒,也很优雅,但是

  • @fork2execve:处理大型属性文件时速度较慢
  • @Nicolai:阅读大量属性时速度慢
  • @anubhava:需要BASH 4.0(用于阵列)

我需要一些东西在bash 3上工作,处理~ 1 k条目的属性文件,阅读~200个属性,整个脚本调用了很多次。
此函数还处理

  • 空行
  • 注解代码
  • 重复条目(最后一个获胜)
  • 规范化属性名称
  • 没有适当新行的最后一行

测试

文件:我的.properties

a=value
a=override value
b=what about `!@#$%^&*()_+[]\?
c=${a} no expansion
d=another = (equal sign)
e=     5 spaces front and back     
f=
#g=commented out
#ignore new line below

.@a%^=who named this???
a1=A-ONE
1a=ONE-A
X=lastLine with no new line!

测试脚本

. loadProps.sh

loadProperties my.properties PROP_
echo "a='${PROP_a}'"
echo "b='${PROP_b}'"
echo "c='${PROP_c}'"
echo "d='${PROP_d}'"
echo "e='${PROP_e}'"
echo "f='${PROP_f}'"
echo "g='${PROP_g}'"
echo ".@a%^='${PROP___a__}'"
echo "a1='${PROP_a1}'"
echo "1a='${PROP_1a}'"
echo "X='${PROP_X}'"

loadProperties my.properties
echo "a='${a}'"
echo "1a='${_1a}'"

输出

a='override value'
b='what about `!@#$%^&*()_+[]\?'
c='${a} no expansion'
d='another = (equal sign)'
e='     5 spaces front and back     '
f=''
g=''
.@a%^='who named this???'
a1='A-ONE'
1a='ONE-A'
X='lastLine with no new line!'
a='override value'
1a='ONE-A'

性能测试

. loadProps.sh

function fork2execve() {
  while IFS='=' read -r key value; do
    key=$(echo $key | tr .-/ _ | tr -cd 'A-Za-z0-9_')
    eval ${key}=\${value}
  done < "$1"
}

function prop {
  grep '^\s*'"$2"'=' "$1" | cut -d'=' -f2-
}

function Nicolai() {
  for i in $(seq 1 $2); do 
    prop0000=$(prop $1 "property_0000")
  done
}

function perfCase() {
  echo "perfCase $1, $2, $3"
  time for i in $(seq 1 1); do 
    eval $1 $2 $3
  done
}

function perf() {
  perfCase $1 0001.properties $2
  perfCase $1 0010.properties $2
  perfCase $1 0100.properties $2
  perfCase $1 1000.properties $2
}

perf "loadProperties"
perf "fork2execve"
perf "Nicolai" 1
perf "Nicolai" 10
perf "Nicolai" 100

包含4个NNNN.properties文件,其中包含以下条目

property_0000=value_0000
property_0001=value_0001
...
property_NNNN=value_NNNN

结果是

function   , file,   #,     real,    user,     sys
loadPropert, 0001,    ,    0.058,   0.002,   0.005
loadPropert, 0010,    ,    0.032,   0.003,   0.005
loadPropert, 0100,    ,    0.041,   0.013,   0.006
loadPropert, 1000,    ,    0.140,   0.106,   0.013

fork2execve, 0001,    ,    0.053,   0.003,   0.007
fork2execve, 0010,    ,    0.211,   0.021,   0.051
fork2execve, 0100,    ,    2.146,   0.214,   0.531
fork2execve, 1000,    ,   21.375,   2.151,   5.312

Nicolai    , 0001,   1,    0.048,   0.003,   0.009
Nicolai    , 0010,   1,    0.047,   0.003,   0.009
Nicolai    , 0100,   1,    0.044,   0.003,   0.010
Nicolai    , 1000,   1,    0.044,   0.004,   0.009

Nicolai    , 0001,  10,    0.240,   0.020,   0.056
Nicolai    , 0010,  10,    0.263,   0.021,   0.059
Nicolai    , 0100,  10,    0.272,   0.023,   0.062
Nicolai    , 1000,  10,    0.295,   0.027,   0.059

Nicolai    , 0001, 100,    2.218,   0.189,   0.528
Nicolai    , 0010, 100,    2.213,   0.193,   0.537
Nicolai    , 0100, 100,    2.247,   0.196,   0.543
Nicolai    , 1000, 100,    2.323,   0.253,   0.534
llew8vvj

llew8vvj5#

@fork 2次
我已经这样试过了。请审查并更新我是否是正确的方法或没有。

#/bin/sh
function pause(){
   read -p "$*"
}

file="./apptest.properties"

if [ -f "$file" ]
then
    echo "$file found."

dbUser=`sed '/^\#/d' $file | grep 'db.uat.user'  | tail -n 1 | cut -d "=" -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'`
dbPass=`sed '/^\#/d' $file | grep 'db.uat.passwd'  | tail -n 1 | cut -d "=" -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'`

echo database user = $dbUser
echo database pass = $dbPass

else
    echo "$file not found."
fi
gwbalxhn

gwbalxhn6#

我发现使用while IFS='=' read -r有点慢(我不知道为什么,也许有人可以在评论中简要解释一下,或者指出一个SO答案?)我还发现@Nicolai答案作为一个一行程序非常简洁,但效率非常低,因为它将一遍又一遍地扫描整个属性文件,以查找prop的每一个调用。
我找到了一个解决方案,它回答了这个问题,性能良好,而且是一行代码(虽然有点冗长)。
该解决方案可以进行寻源,但在寻源之前会对内容进行处理:

#!/usr/bin/env bash

source <(grep -v '^ *#' ./app.properties | grep '[^ ] *=' | awk '{split($0,a,"="); print gensub(/\./, "_", "g", a[1]) "=" a[2]}')

echo $db_uat_user

说明:
grep -v '^ *#':丢弃注解行grep '[^ ] *=':丢弃不包含=split($0,a,"=")的行:在=处拆分行并存入数组a,即a[1]为键,a[2]为值gensub(/\./, "_", "g", a[1]):将.替换为_print gensub... "=" a[2]}将上面gensub的结果与=和值连接。
编辑:正如其他人所指出的,有一些不兼容的问题(awk),而且它也没有验证内容,看看属性文件的每一行是否实际上是一个kv对。但这里的目标是展示一个快速和干净的解决方案的总体思路。来源似乎是要走的路,因为它加载属性一次,可以多次使用。

2j4z5cfb

2j4z5cfb7#

我认为尼古拉的回答是好的。然而,有时候人们写道

app.server.address = 127.0.0.70

代替

app.server.address=127.0.0.70

在这种情况下。如果我们直接使用

function prop {
    grep "${1}" env/${ENV}.properties|cut -d'=' -f2
}

它将生成“127.0.0.70“而不是“127.0.0.70”,字符串组合中存在一些错误。要解决此问题,我们可以添加“|xargs”。它将是

grep "${1}" ${ENV}.properties|cut -d'=' -f2 | xargs

我们会得到我们想要的。

相关问题