【问题标题】:Using sed to replace string in file with contents of variable of that name使用 sed 将文件中的字符串替换为该名称的变量内容
【发布时间】:2013-10-25 22:13:57
【问题描述】:

我正在尝试使用 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 的变量。

【问题讨论】:

  • 反引号在那里不起作用。变量由 shell 将参数发送到sed 之前扩展。
  • Perl 会更好。将$variable设置为%之间的字符串后,可以使用$ENV{$variable}获取环境变量。

标签: regex bash sed


【解决方案1】:

这里的问题是,在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 "Shell Parameter Expansion" of the Bash Reference Manual 所说的“间接扩展”。)

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

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

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

【讨论】:

  • 这是一个很好的解决方案并解决了问题,但我无法让它按原样工作。我只能通过将 grep 的输出重定向到一个临时文件然后将该文件重定向到 while 循环中来使其运行。运行上面的代码给了我 -bash: syntax error near unexpected token `
  • @reagleton:糟糕,抱歉,愚蠢的错误。对于我的process substitution,当我需要写&lt; &lt;(...) 时,我使用了&lt;(...)。 (我经常犯这个令人尴尬的错误。&lt;(...) 真的看起来 像它正在重定向标准输入,但它不是,它只是被替换为 FIFO 或诸如此类的名称。)
【解决方案2】:

使用这个:

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

例如:

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

打印:

abcdef $variable blah

【讨论】:

    【解决方案3】:

    使用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}

    【讨论】:

      【解决方案4】:

      尝试使用 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
         }
      "
      
      • 此处的限制是由于使用字符“²”未转义,因此如果 ² 出现在文件中,可能会很烦人(因此将此字符更改为标记或在文件中进行翻译)
      • #V0r: 和 :r0V# 也是标记,可以毫无问题地更改

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-10-11
        • 2011-01-16
        • 1970-01-01
        • 2015-04-18
        • 2020-01-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多