要添加到@CharlesDuffy 的答案,对于那些仍然坚持使用旧的“不可升级”硬件/虚拟机的人,这里有一种 POSIX / 旧的 bash 方法可以安全地实现这一目标。使用 dash、ksh93、bash 2.05b、3 和 4 进行测试。无法检索我的旧 Bourne shell 92。
编辑:感谢有用的@CharlesDuffy cmets:
-
更新以处理 'value' 部分中的空白/空格/换行符/wathever。以一种基本的方式(多个空格减少到一个空格,新行被吞下)。正在努力寻找更好的方法来处理这个问题。
-
生成的变量名称现在以_ 为前缀,以防止任何
尝试覆盖PATH、LD_PRELOAD等。
EDIT2: 添加了处理值中的制表符/空格/换行符的 Bash 2/3/4 和 ksh 版本。见下文。
EDIT3:添加一个符合 POSIX 标准的 rev 2,可以处理 TAB、NEWLINE 和多个 SPACE。
POSIX 兼容 V1 :
这个不能很好地处理变量值部分的换行符和制表符。它不会崩溃,但相关变量将被“压缩”在一行中,使用空格而不是换行符,并且所有制表符/多个空格都减少为一个空格。
#!/bin/sh
# If you only have a bash 4.x, you can test with compat 3.1 bash
# shopt -s compat31
FOO="SYSCFG_lan_ifname='br1' SYSCFG_lan_ipaddr='10.0.0.1'
SYSCFG_lan_netmask='255.255.255.0' SYSCFG_space='my space' SYSCFG_newline='I have
many multi
lines input"
# An "env variable" definer that use the read command
# to parse and define the env variable
define() {
IFS=\= read -r key value <<EOF
$1
EOF
# Unquotting the value, adapt as it fit your needs
value="${value#\'}"
value="${value%\'}"
read -r "_${key}" << EOF
${value}
EOF
}
unset _SYSCFG_lan_ifname
unset _SYSCFG_lan_ipaddr
unset _SYSCFG_lan_netmask
unset _SYSCFG_space
unset _SYSCFG_newline
# Using the set command to "parse" the variables string
set ${FOO}
while [ "$1" ] ; do
key_value="$1"
while [ "$1" ] && [ "${key_value%\'}" = "${key_value}" ] ; do
shift
key_value="${key_value} $1"
done
define "${key_value}"
[ "$1" ] && shift
done
echo "${_SYSCFG_lan_ifname}"
echo "${_SYSCFG_lan_ipaddr}"
echo "${_SYSCFG_lan_netmask}"
echo "${_SYSCFG_space}"
echo "${_SYSCFG_newline}"
输出与ksh93、bash 2..4、破折号相同:
br1
10.0.0.1
255.255.255.0
我的空间
我有很多多行输入
POSIX 兼容 V2 :
这个版本可以处理特殊字符和,部分,换行符。它不使用set 命令来解析字符串,避免了任何潜在的全局效应。我们依赖于基本的 shell trimer # 和 %。这个还可以处理字符串中的不同引用和转义引号/双引号。 define 函数通过 here-doc 中的 \n 处理多行,因此它们的翻译将留给脚本用户。
#!/bin/sh
# If you only have a bash 4.x, you can test with compat 3.1 bash
# shopt -s compat31
# Test string. There is a TAB between "input" and "and".
FOO="SYSCFG_lan_ifname='br1' SYSCFG_lan_ipaddr='10.0.0.1 *'
SYSCFG_lan_netmask=\"255.255.255.0\" SYSCFG_space='mypath\\ so\'urce\\my space'
SYSCFG_newline='I have
many multi
lines input and /path/to/thi ngs'"
#
# Define here the prefix you want for the variables. A prefix is required
# to avoid LD_PRELOAD, PATH, etc. override from a variable.
_prefix="_"
#
# The POSIX way for a new line constant.
_NL="
"
# An "env variable" definer that use the read command to parse and define
define() {
_key="$1"
_value="$2"
_quote="$3"
_tmp=""
# The POSIX read command can only read one line at a time.
# For multiline, we loop to rebuild the full value.
while read -r _line ; do
[ "${_tmp}" ] && _tmp="${_tmp}\n${_line}" || _tmp="${_line}";
done <<EOF
${_value}
EOF
read -r "${_prefix}${_key}" << EOF
${_tmp}
EOF
}
unset _SYSCFG_lan_ifname
unset _SYSCFG_lan_ipaddr
unset _SYSCFG_lan_netmask
unset _SYSCFG_space
unset _SYSCFG_newline
# First, we trim blanks
_FOO="${FOO# * }"
_FOO="${_FOO% * }"
# We use shell basic trimer to "parse" the string
while [ "${_FOO}" ] ; do
# Get the first assignation from the beginning
_FOO_next="${_FOO#*=}"
if [ "${_FOO_next}" != "${_FOO}" ] ; then
# If there is backslash in the string we need to double escape them for
# using it as a pattern. We do that in a safe manner regarding FOO content.
_FOO_next_pattern="$( sed 's/\\/\\\\/g' <<EOF
${_FOO_next}
EOF
)"
# We have an assignation to parse
_key="${_FOO%=${_FOO_next_pattern}}"
# We must have a key, assignation without key part are ignored.
# If need, you can output error message in the else branch.
if [ "${_key}" ] ; then
# Triming space and newlines
_key="${_key## }"
_key="${_key##${_NL}}"
_key="${_key## }"
_quote="\'"
# Test if it is quote, if not quote then try double quote
[ "${_FOO_next}" = "${_FOO_next#${_quote}}" ] && _quote="\""
# If not double quote, consider unquoted...
[ "${_FOO_next}" = "${_FOO_next#${_quote}}" ] && _quote=""
# Extracting value part and trim quotes if any
if [ "${_quote}" ] ; then
_FOO_next="${_FOO_next#${_quote}}"
_FOO_next_pattern="${_FOO_next_pattern#${_quote}}"
fi
_value="${_FOO_next}"
if [ "${_quote}" ] ; then
_FOO_next="${_FOO_next#*[^\\]${_quote}}"
_FOO_next_pattern="${_FOO_next_pattern#*[^\\]${_quote}}"
else
# If the value part is not quoted, we look for the next unescaped space
# as the delimiter for the next key/value pair.
_FOO_next="${_FOO_next#*[^\\] }"
_FOO_next_pattern="${_FOO_next_pattern#*[^\\] }"
fi
_value="${_value%${_quote}${_FOO_next_pattern}}"
# We have parse everything need to set the variable
define "${_key}" "${_value}" "${_quote}"
_FOO="${_FOO_next}"
else
_FOO="${_FOO_next#*[^\\] }"
fi
else
# Nothing more to parse
_FOO=""
fi
done
printf "%s\n" "${_SYSCFG_lan_ifname}"
printf "%s\n" "${_SYSCFG_lan_ipaddr}"
printf "%s\n" "${_SYSCFG_lan_netmask}"
printf "%s\n" "${_SYSCFG_space}"
printf "%s\n" "${_SYSCFG_newline}"
输出与ksh93、bash 2..4、破折号相同:
br1
10.0.0.1 *
255.255.255.0
我的路径\所以\'urce\我的空间
我有许多多行输入和 /path/to/thi ngs
BASH V2+ 和 KSH93 兼容:
它不符合 POSIX,因为按模式 (/) 的变量替换不是 POSIX。文字 ASCII 推断 $'\x<hex ASCII code>' 确实不是 POSIX,以下脚本只能与基于 ASCII 的 UNIX shell 一起使用(忘记 EBCDIC...)。无论如何,这个可以处理变量值部分中的换行符/制表符/多个空格。
#!/bin/sh
# If you only have a bash 4.x, you can test with compat 3.1 bash
# shopt -s compat31
# Test string. There is a TAB between "input" and "and".
FOO="SYSCFG_lan_ifname='br1' SYSCFG_lan_ipaddr='10.0.0.1 *'
SYSCFG_lan_netmask='255.255.255.0' SYSCFG_space='mypath\\ source\\my space'
SYSCFG_newline='I have
many multi
lines input and /path/to/thi ngs"
#
# For bash 2.0, we can't make inline subsitution of ESC nor NL nor TAB because
# of the following bug :
# FOO="`echo -e \"multi\nline\"`";echo "${FOO//$'\x0a'/$'\x1b'}" ==> multi'line
# Bash 2.0 wrongly include one quote to the output in this case.
# To avoid that, we store ESC and NL in local variable, and it is better
# for readability.
_ESC=$'\x1b'
_NL=$'\x0a'
_TAB=$'\x09'
# Same kind of trouble with the backslash in bash 2.0, the substiution need
# 'double' escape for them in bash 2.0, so we store BKS, test it and double it
# if required.
# However, if used as a variable in pattern or subsitution part, we have then to
# deal with two forms of escaped bakcslash since shells don't "dedouble"/escape
# them for the substitute value, only for the pattern.
_BKS_PATTERN="\\\\"
_BKS="\\"
if [ "${_BKS_PATTERN//\\/X}" != "XX" ] ; then
# Hello bash 2.0
_BKS_PATTERN="\\\\\\\\"
_BKS="\\\\"
fi
# An "env variable" definer that use the read command to parse and define
define() {
IFS=\= read -r _key _value <<EOF
$1
EOF
# Unquotting the _value, adapt as it fit your needs
_value="${_value#\'}"
_value="${_value%\'}"
_value="${_value%\'${_BKS_PATTERN}}"
# Unescape the _key string to trim escaped nl
_key="${_key#${_ESC}}"
_key="${_key%${_ESC}}"
# Unescape the _value string
_value="${_value//${_BKS_PATTERN} / }"
_value="${_value//${_ESC}${_ESC}/${_TAB}}"
_value="${_value//${_ESC}/${_NL}}"
read -d\' -r "_${_key}" <<EOF
${_value}'
EOF
}
unset _SYSCFG_lan_ifname
unset _SYSCFG_lan_ipaddr
unset _SYSCFG_lan_netmask
unset _SYSCFG_space
unset _SYSCFG_newline
# First, we escape the new line with 0x1B
_FOO="${FOO//${_NL}/${_ESC}}"
# Second, escape each tab with double ESC. All tabs.
_FOO="${_FOO//${_TAB}/${_ESC}${_ESC}}"
# Third, escape each space. All space.
_FOO="${_FOO// /${_BKS} }"
# Using the set command to "parse" the variables string
set ${_FOO}
while [ "$1" ] ; do
_key_value="$1"
while [ "$1" ] && [ "${_key_value%\'${_BKS_PATTERN}}" = "${_key_value}" ] ; do
shift
_key_value="${_key_value} $1"
done
define "${_key_value}"
[ "$1" ] && shift
done
printf "%s\n" "${_SYSCFG_lan_ifname}"
printf "%s\n" "${_SYSCFG_lan_ipaddr}"
printf "%s\n" "${_SYSCFG_lan_netmask}"
printf "%s\n" "${_SYSCFG_space}"
printf "%s\n" "${_SYSCFG_newline}"
输出与 ksh93、bash 2 和 + 相同:
(请注意,我们使用printf 来呈现“输入”和“和”之间的TAb 字符。)
br1
10.0.0.1 *
255.255.255.0
我的路径\源\我的空间
我有
许多多
行输入和 /path/to/things