这里的问题是,到了sed
命令实际运行(因此当它检索变量名时),sed
命令必须已完全组装(包括将 Bash 变量的值替换为替换字符串);所以一切都以错误的顺序发生。
或者,从更高层次来看,问题在于sed
不了解 Bash 变量,因此您需要 Bash 提供变量的详细信息,但 Bash 不了解sed
替换,因此它无法知道您需要哪些变量的详细信息。
只要您想使用 Bash 变量,解决方法就是使用更多 Bash:您需要在第一次调用之前识别相关的变量名称sed
。下面展示了如何做到这一点。
要获取文件中所有变量名称的列表,您可以编写如下内容:
grep -o '%[a-z_][a-z_]*%' FILE | grep -o '[a-z_][a-z_]*' | sort -u
(首先grep
获取该形式的所有表达式%...%
。第二grep
过滤掉百分号;或者你可以使用sed
为此,如果您愿意的话。这sort -u
消除重复项,因为您只需要列表distinct变量名。)
有了它,你就可以组装一个sed
执行所有必要的替换的命令:
sed_args=()
while read varname ; do
sed_args+=(-e "s/%$varname%/${!varname}/g")
done < <(grep -o '%[a-z_][a-z_]*%' FILE | grep -o '[a-z_][a-z_]*' | sort -u)
sed "${sed_args[@]}" FILE
(注意使用${!varname}
意思是“取值$varname
作为变量名,并返回该变量的值。”这就是§3.5.3“外壳参数扩展”Bash 参考手册 http://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion称为“间接扩展”。)
您可以将其包装在一个函数中:
function replace_bash_variables () {
local file="$1"
local sed_args=()
local varname
while read varname ; do
sed_args+=(-e "s/%$varname%/${!varname}/g")
done < <(grep -o '%[a-z_][a-z_]*%' "$file" | grep -o '[a-z_][a-z_]*' | sort -u)
if [[ "${#sed_args[@]}" = 0 ]] ; then
# if no variables to replace, just cat the file:
cat -- "$file"
else
sed "${sed_args[@]}" -- "$file"
fi
}
replace_bash_variables OLD_FILE > NEW_FILE
您还可以调整上面的内容来进行逐行处理,这样就不需要读取文件两次。 (这为您提供了更大的灵活性,因为读取文件两次意味着您必须传入实际文件,并且不能(比如说)将其应用于管道的输出。)