Bash命令用于在文件中搜索字符串,并将值替换为另一个文件中的值

时间:2011-02-25 19:28:10

标签: bash replace

我有两个文件a。)xmlFile.xml b。)emails.txt

xmlFile.xml具有多次重复的以下结构

<gname>Office</gname>
<uname>person</uname>

emails.txt包含电子邮件地址列表

email1@company.com
email2@company.com
...

我想要完成的是将xmlFile.xml中的“person”替换为来自emails.txt的后续值

我试过了

# while read email ; do sed  "s/person/$email/g" xmlFile.xml > xmlFile.new; done < emails.txt

但是我最终使用的文件将所有“person”值替换为来自emails.txt的最后一封电子邮件

谢谢, 菲利普

3 个答案:

答案 0 :(得分:3)

awk 'NR==FNR{e[i++]=$0;next} /person/{sub("person",e[j++])}1' emails.txt xmlFile.xml

解释

  1. NR==FNR:仅当awk正在读取第一个文件时才会出现这种情况。它主要测试所看到的记录总数(NR)与当前文件(FNR)中的输入记录。
  2. e[i++]=$0:创建一个名为 e 的数组,其索引的增量为1(i++),其值等于当前记录$0。这个数组将保存我们的电子邮件
  3. next:如果达到此目的,请忽略脚本的其余部分,重新​​开始使用新的输入记录
  4. /person/:如果当前记录与正则表达式“person”匹配,则仅执行后续代码
  5. sub("person",e[j++]):将字面值“person”替换为我们之前创建的数组 e 中的值。为我们匹配的下一条记录增加此数组j++
  6. 1 :始终返回true,实际上是{print $0}的快捷方式,或输出我们当前的记录
  7. 概念证明

    $ cat emails.txt
    email1@company.com
    email2@company.com
    email3@company.com
    email4@company.com
    email5@company.com
    email6@company.com
    email7@company.com
    email8@company.com
    email9@company.com
    
    $ cat xmlFile.xml
    <gname>Office</gname>
    <uname>person</uname>
    <gname>Office</gname>
    <uname>person</uname>
    <gname>Office</gname>
    <uname>person</uname>
    <gname>Office</gname>
    <uname>person</uname>
    <gname>Office</gname>
    <uname>person</uname>
    <gname>Office</gname>
    <uname>person</uname>
    <gname>Office</gname>
    <uname>person</uname>
    <gname>Office</gname>
    <uname>person</uname>
    <gname>Office</gname>
    <uname>person</uname>
    
    $ awk 'NR==FNR{e[i++]=$0;next} /person/{sub("person",e[j++])}1' emails.txt xmlFile.xml
    <gname>Office</gname>
    <uname>email1@company.com</uname>
    <gname>Office</gname>
    <uname>email2@company.com</uname>
    <gname>Office</gname>
    <uname>email3@company.com</uname>
    <gname>Office</gname>
    <uname>email4@company.com</uname>
    <gname>Office</gname>
    <uname>email5@company.com</uname>
    <gname>Office</gname>
    <uname>email6@company.com</uname>
    <gname>Office</gname>
    <uname>email7@company.com</uname>
    <gname>Office</gname>
    <uname>email8@company.com</uname>
    <gname>Office</gname>
    <uname>email9@company.com</uname>
    

    上述脚本假定person是字面值。如果不是,那么..

    替换:/person/{sub("person",emails[j++])}
    使用:/<uname>/{sub(".*","<uname>"emails[j++]"</uname>")}

答案 1 :(得分:1)

实现这一目标的一种方法是使用就地编辑:

while read email ; do sed -i "s/person/$email/;q" xmlFile.xml; done < emails.txt

如果XML文件很少或没有比您显示的更多,只需重新构建它:

sed -e 'i <gname>Office</gname>' -e 's|.*|<uname>&</uname>|' emails.txt > newxmlFile.xml

甚至没有触及现有的xmlFile.xml

但是,您应该使用XML解析器,例如xmlstarlet

答案 2 :(得分:0)

以下是如何使用bash&amp; amp; xmlstarlet!

IFS=$'\n' read -r -d "" -a array < emails.txt                   # read file with email addresses into array
n=$(xmlstarlet sel -T -t -v "count(//uname)" -n xmlFile.xml)    # count "uname" nodes in XML file
xmlFileStr="$(< xmlFile.xml)"                                   # read XML file into variable


if [[ $n -eq ${#array[@]} ]]; then   # if the number of nodes & email addresses is equal ...
   for ((i=1; i <= ${n}; i+=1)); do
      xmlFileStr="$(printf '%s' "$xmlFileStr" | xmlstarlet ed -P -t -u "//uname[${i}]" -v "${array[$((i-1))]}")"
   done
fi

printf '%s\n' "$xmlFileStr" > xmlFile.xml
cat xmlFile.xml