如何使用 Shell 脚本读取包含具有句点字符的键的 .properties 文件
How to read a .properties file which contains keys that have a period character using Shell script
我正在尝试从 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
中的某个位置
由于 BASH shell 中的变量名不能包含点或 space 最好在 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" )'
因为 (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
注意以上只是翻译。到 _,如果您有更复杂的格式,您可能需要使用其他翻译。我最近不得不解析一个包含大量讨厌字符的完整 Ant 属性文件,我不得不使用:
key=$(echo $key | tr .-/ _ | tr -cd 'A-Za-z0-9_')
@fork2x
我已经尝试过这样的方法。请检查并更新我是否正确的方法。
#/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
我在 bash 脚本中使用简单的 grep
内部函数从 .properties
文件接收属性。
我在两个地方使用这个属性文件 - 设置开发环境和应用程序参数。
我相信 grep
在大循环中可能会运行缓慢,但是当我想准备 dev
环境时它解决了我的需求。
希望有人会觉得这有用。
示例:
文件:setup.sh
#!/bin/bash
ENV=${1:-dev}
function prop {
grep "" 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
文件:env/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
我发现使用 while IFS='=' read -r
有点慢(我不知道为什么,也许有人可以在评论中简要解释或指出 SO 答案?)。我还发现@Nicolai 的回答非常简洁,但效率很低,因为它会针对 prop
.
的每次调用一遍又一遍地扫描整个属性文件
我找到了一个可以回答问题的解决方案,性能很好,而且它是一行代码(虽然有点冗长)。
该解决方案进行寻源,但在寻源之前对内容进行处理:
#!/usr/bin/env bash
source <(grep -v '^ *#' ./app.properties | grep '[^ ] *=' | awk '{split([=10=],a,"="); print gensub(/\./, "_", "g", a[1]) "=" a[2]}')
echo $db_uat_user
解释:
grep -v '^ *#'
:丢弃注释行
grep '[^ ] *='
:丢弃没有 =
的行
split([=16=],a,"=")
:在=
处拆分行,存入数组a
,即a[1]为键,a[2]为值
gensub(/\./, "_", "g", a[1])
:将 .
替换为 _
print gensub... "=" a[2]}
将上面 gensub
的结果与 =
和值连接起来。
编辑:正如其他人所指出的,存在一些不兼容问题 (awk),而且它不会验证内容以查看 属性 文件的每一行是否实际上是一个 kv 对。但这里的目标是展示一个既快速又干净的解决方案的总体思路。采购似乎是一种方式,因为它一次加载可以多次使用的属性。
对于非常高性能,并且 BASH 3.0 兼容的解决方案:
文件:loadProps.sh
function loadProperties() {
local fileName=
local prefixKey=
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 的工作,处理 ~1k 条目的属性文件,读取 ~200 个属性,整个脚本被调用了很多次。
这个函数还处理
- 空行
- 注释代码
- 重复条目(最后一个获胜)
- 标准化 属性 名称
- 最后一行没有合适的新行
测试
文件:my.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 < ""
}
function prop {
grep '^\s*'""'=' "" | cut -d'=' -f2-
}
function Nicolai() {
for i in $(seq 1 ); do
prop0000=$(prop "property_0000")
done
}
function perfCase() {
echo "perfCase , , "
time for i in $(seq 1 1); do
eval
done
}
function perf() {
perfCase 0001.properties
perfCase 0010.properties
perfCase 0100.properties
perfCase 1000.properties
}
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
我正在尝试从 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
中的某个位置由于 BASH shell 中的变量名不能包含点或 space 最好在 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" )'
因为 (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
注意以上只是翻译。到 _,如果您有更复杂的格式,您可能需要使用其他翻译。我最近不得不解析一个包含大量讨厌字符的完整 Ant 属性文件,我不得不使用:
key=$(echo $key | tr .-/ _ | tr -cd 'A-Za-z0-9_')
@fork2x
我已经尝试过这样的方法。请检查并更新我是否正确的方法。
#/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
我在 bash 脚本中使用简单的 grep
内部函数从 .properties
文件接收属性。
我在两个地方使用这个属性文件 - 设置开发环境和应用程序参数。
我相信 grep
在大循环中可能会运行缓慢,但是当我想准备 dev
环境时它解决了我的需求。
希望有人会觉得这有用。
示例:
文件:setup.sh
#!/bin/bash
ENV=${1:-dev}
function prop {
grep "" 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
文件:env/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
我发现使用 while IFS='=' read -r
有点慢(我不知道为什么,也许有人可以在评论中简要解释或指出 SO 答案?)。我还发现@Nicolai 的回答非常简洁,但效率很低,因为它会针对 prop
.
我找到了一个可以回答问题的解决方案,性能很好,而且它是一行代码(虽然有点冗长)。
该解决方案进行寻源,但在寻源之前对内容进行处理:
#!/usr/bin/env bash
source <(grep -v '^ *#' ./app.properties | grep '[^ ] *=' | awk '{split([=10=],a,"="); print gensub(/\./, "_", "g", a[1]) "=" a[2]}')
echo $db_uat_user
解释:
grep -v '^ *#'
:丢弃注释行
grep '[^ ] *='
:丢弃没有 =
的行
split([=16=],a,"=")
:在=
处拆分行,存入数组a
,即a[1]为键,a[2]为值
gensub(/\./, "_", "g", a[1])
:将 .
替换为 _
print gensub... "=" a[2]}
将上面 gensub
的结果与 =
和值连接起来。
编辑:正如其他人所指出的,存在一些不兼容问题 (awk),而且它不会验证内容以查看 属性 文件的每一行是否实际上是一个 kv 对。但这里的目标是展示一个既快速又干净的解决方案的总体思路。采购似乎是一种方式,因为它一次加载可以多次使用的属性。
对于非常高性能,并且 BASH 3.0 兼容的解决方案:
文件:loadProps.sh
function loadProperties() {
local fileName=
local prefixKey=
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 的工作,处理 ~1k 条目的属性文件,读取 ~200 个属性,整个脚本被调用了很多次。
这个函数还处理
- 空行
- 注释代码
- 重复条目(最后一个获胜)
- 标准化 属性 名称
- 最后一行没有合适的新行
测试
文件:my.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 < ""
}
function prop {
grep '^\s*'""'=' "" | cut -d'=' -f2-
}
function Nicolai() {
for i in $(seq 1 ); do
prop0000=$(prop "property_0000")
done
}
function perfCase() {
echo "perfCase , , "
time for i in $(seq 1 1); do
eval
done
}
function perf() {
perfCase 0001.properties
perfCase 0010.properties
perfCase 0100.properties
perfCase 1000.properties
}
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