5

我正在尝试使用 sed 将 %XXX% 形式的文件中的模板字符串替换为我的 shell 脚本中名为 XXX 的变量的值。

例如以下工作完美

sed "s/%user_home%/$user_home/gi"

因此,如果user_home=fred出现以下情况,

NameVirtualHost *:80

<VirtualHost *:80>
  ServerName %server_name%

  ErrorLog /var/log/apache2/%user_home%_webapp_error.log
  CustomLog /var/log/apache2/%user_home%_webapp.log common

  DocumentRoot /home/%user_home%/web_app/public
</VirtualHost>

变成,

NameVirtualHost *:80

<VirtualHost *:80>
  ServerName %server_name%

  ErrorLog /var/log/apache2/fred_webapp_error.log
  CustomLog /var/log/apache2/fred_webapp.log common

  DocumentRoot /home/fred/web_app/public
</VirtualHost>

问题是我想在不明确知道模板字符串及其变量的情况下运行 sed 命令。也就是说,它会查找 %XXX%,然后将其替换为 $XXX 的内容,而不关心变量的实际名称是什么。

我知道它与反向引用有关,但我不知道如何使用反向引用的内容作为变量名。

我试过了,

sed "s/%\([a-z_]\)%/$(\1)/gi"

但这不起作用,因为它似乎是在寻找一个名为 $\1 的变量。

4

4 回答 4

3

这里的问题是,在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消除重复项,因为您只需要不同变量名的列表。)

有了它,您可以组装一个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}to 的意思是“将 的值$varname作为变量名,并返回该变量的值。”这就是Bash 参考手册第 3.5.3 节“Shell 参数扩展”所称的“间接扩展”。 )

您可以将其包装在一个函数中:

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

你也可以调整上面做逐行处理,这样就不需要两次读取文件了。(这为您提供了更大的灵活性,因为两次读取文件意味着您必须传入实际文件,并且不能(例如)将其应用于管道的输出。)

于 2013-10-25T05:32:33.473 回答
0

使用awk你可以做到这一点

awk '{gsub(/%user_home%/,"${user_home}")}1' file
NameVirtualHost *:80

<VirtualHost *:80>
  ServerName %server_name%

  ErrorLog /var/log/apache2/${user_home}_webapp_error.log
  CustomLog /var/log/apache2/${user_home}_webapp.log common

  DocumentRoot /home/${user_home}/web_app/public
</VirtualHost>

这将替换%user_home%为变量${user_home}

于 2013-10-25T05:24:31.137 回答
0

用这个:

sed -E "s/%(\w+)%/\$\1/g"

例如这个:

echo "abcdef %variable% blah" | sed -E "s/%(\w+)%/\$\1/g"

印刷:

abcdef $variable blah
于 2013-10-25T05:53:53.663 回答
0

尝试使用 1 sed,但之前仍需要捕获“设置”内容以了解变量名称和值

#!/bin/ksh
# YourFilename contain the file name of your file to treat (here passed as 1st parameter to a script)
YourFileName=$1

(set | sed 's/.*/#V0r:&:r0V#/'; cat ${YourFileName}) | sed -n "
s/$/²/
H

$  {
   x
   s/^\(\n *\)*//
# also reset t flag
   t varxs

:varxs
   s/^#V0r:\([a-zA-Z0-9_]\{1,\}\)=\([^²]*\):r0V#²\(\n.*\)%\1%/#V0r:\1=\2:r0V#²\3\2/
   t varxs
: tmpb

# clean the line when no more occurance in text
#   s/^#V0r:\([a-zA-Z0-9_]\{1,\}\)=\([^²]*\):r0V#²\n//
   s/^[^²]*:r0V#²\n//

# and next
   t varxs


# clean the  marker
   s/²\(\n\)/\1/g
   s/²$//

# display the result
   p
   }
"
  • 由于使用 char "²" 没有转义,因此这里的限制,所以如果 ² 出现在文件中,可能会很烦人(所以将此字符更改为标记或在文件中翻译它)
  • #V0r: 和 :r0V# 也是标记,可以毫无问题地更改
于 2013-10-25T14:13:57.510 回答