加入并比较来自bash的大量输入文件

时间:2013-08-09 13:31:18

标签: bash shell if-statement while-loop

我想读取29个文件中的行并处理这些行并将它们放入if语句中。

在这个例子中,我创建了3个示例文件和一个shell脚本文件,它基本上使用while循环读取文件并从每个文件中读取这些行并使用带有if语句的sed处理,此if语句用于检查以查看第一个所有文件中的变量(例如abc.def)并将它们的值复制到文件中。

示例:

a.txt
    abc.def=123
    efg.hij=45666
    kml.nop=789
    qrs.tuv=901
    wxy.zabc=234
b.txt
    abc.def=123
    efg.hij=45666
    kml.nop=897
    klm.nop=123
    qrs.tuv=901
    wxy.zabc=234
c.txt
    abc.def=12344
    efg.hij=456
    kml.nop=123
    klm.nop=789
    wxy.zabc=234

sprict.sh

    #!/bash/bin

    count=1
    while IFS= read -r lineA && IFS= read -r lineB <&3 && IFS= read -r lineC <&4; do
    #splitting the line into two,example from line abc.def=123 slit varaibles as "abc.def" and "123"
    A1=`echo "$lineA" | sed -e 's/\=\(.*\)//' `
    A2=`echo "$lineA" | sed -e 's/^[^=]*=//' `
    B1=`echo "$lineB" | sed -e 's/\=\(.*\)//' `
    B2=`echo "$lineB" | sed -e 's/^[^=]*=//' `      
    C1=`echo "$lineC" | sed -e 's/\=\(.*\)//' `
    C2=`echo "$lineC" | sed -e 's/^[^=]*=//' `
    if [ [ "$A1" = "$B1" && "$A1" = "$C1"]];then
    echo -e "<variable id=\"$A1\">\t
    <a2>"$A2"</a2>\t
    <b2>"$B2"</b2>\t
    <c2>"$C2"</c2>\t
    </variable>\n" >> common.txt                    
    fi
    done <a.txt 3<b.txt 4<c.txt

预期输出为:

<variable id="abc.def">
   <a2>123</a2>
   <b2>123</b2>
   <c2>12344</c2>
</variable>
<variable id="efg.hij">
   <a2>456</a2>
   <b2>45666</b2>
   <c2>45666</c2>
</variable>
<variable id="kml.nop">
   <a2>789</a2>
   <b2>897</b2>
   <c2>123</c2>
</variable>

4 个答案:

答案 0 :(得分:3)

您可以使用read来简化将每一行拆分为名称和值,并使用预先构建的模板printf来简化输出。此外,您的if语句中存在一些语法错误:空格很重要。

shell保留大于10的文件描述符供自己使用,因此对于29个输入文件,您的方法无法很好地扩展。在bash 4.1及更高版本中,您可以让bash分配文件描述符。

#!/bash/bin

# Redirect from each input file, using bash-assigned file descriptors
exec {afile}<a.txt
exec {bfile}<b.txt
exec {cfile}<c.txt

template='<variable="%s">\n\t<a2>%s</a2>\n\t<b2>%s</b2>\n\t<c2>%s</c2>\n</variable>\n'
while IFS="=" read -r -u "$afile" A1 A2 &&
      IFS="=" read -r -u "$bfile" B1 B2 &&
      IFS="=" read -r -u "$cfile" C1 C2; do

  if [[ "$A1" = "$B1" && "$A1" = "$C1" ]]; then
    printf "$template" "$A1" "$A2" "$B2" "$C2"
  fi
done

答案 1 :(得分:3)

下面比较任意数量的文件,并且不要求它们的内容有序(正如chepner给出的解决方案所做的那样)。它还使用XMLStarlet生成XML格式的输出,保证良好的格式。

这确实意味着需要在您的系统上安装XMLStarlet;它通常可用于现代主要操作系统的打包。

#!/bin/bash

join_files() {
  local first
  if (( $# == 1 )); then
    sort <"$1"
  elif (( $# == 2 )); then
    join -t= <(sort <"$1") <(sort <"$2")
  elif (( $# > 2 )); then
    first=$1; shift
    join -t= <(sort <"$first") <(join_files "$@")
  fi
}

main() {
  declare -a items
  {
  printf '%s\n' '(root'
  while IFS='=' read -r -a items; do
    set -- "${items[@]}"
    name=$1; shift
    printf '%s\n' '(variable' "Aid $name"
    item_num=1
    for item; do
      printf '%s\n' "(a$item_num" "-$item" ")a$item_num"
      (( item_num++ ))
    done
    printf '%s\n' ')variable'
  done < <(join_files "$@")
  printf '%s\n' ')root'
  } | xmlstarlet depyx | xmlstarlet fo
}

main a.txt b.txt c.txt

答案 2 :(得分:2)

如果您不介意除 bash 之外的其他语言,我可以建议 awk (因为我熟悉它,也因为它很好用文本处理)。这是我的 awk 解决方案,我称之为 text2xml.awk

BEGIN {
    FS="=" # Use the equal sign as field separator
}

# When we encounter a new file, establish a new tag based on the file
# name
#   tag:      tag names,  (a2, b2, c2, ...)
#   tagfound: keep track of which tag has been found
#   tags:     array of tag names, where tags[0] = "a2", ...
#   tagcount: number of tags
FNR == 1 {
    tag = FILENAME
    sub(/\..*$/, "2", tag) # Replace extension with number 2, a.txt ==> a2
    if (!(tag in tagfound)) {
        tags[tagcount++] = tag
    }
    tagfound[tag] = 1
}

# For each line in each file, we parse the id, add that id to a list
# then store the value for later display.
#   id:      abc.def, efg.hij, ...
#   idfound: keep track of which id has been found
#   ids:     List of id, ids[0] = "abc.def", ...
#   values:  two-dimensional array, values[id,tag] stores the right-
#            hand-side of the equal sign
{
    sub(/^ */, "") # Remove leading spaces
    if (!($1 in idfound)) { ids[idcount++] = $1 }
    idfound[$1] = 1
    values[$1,tag] = $2
}

# Loop through the ids and tags, display the XML
END {
    for (i=0; i<idcount; i++) {
        id = ids[i]
        printf "<variable id=\"%s\">\n", id
        for (j=0; j<tagcount; j++) {
            tag = tags[j]
            printf "  <%s>%s</%s>\n", tag, values[id,tag], tag
        }
        printf "</variable>\n"
    }
}

使用它:

awk -f text2xml.awk a.txt b.txt c.txt  # Try out with 3 files
awk -f text2xml.awk *.txt              # For all .txt files

讨论

我希望我在代码中添加足够的注释以使您理解。如果需要,请随时提出更多问题。

  • ID的顺序取决于它们在文本文件中的显示方式
  • 标签的顺序(a2,b2,c2,...)取决于命令行中文件的顺序
  • 对于那些了解 awk 的人,我们可以遍历idfound数组并忘记idsidcount。但是,这种方法并不能保证ID的顺序;而且我觉得秩序很重要。 tagfoundtagcounttags也是如此。
  • 此解决方案适用于3个文件,它适用于29个文件及更高版本。
  • 我正在Mac OS X 10.8 Mountain Lion上测试此解决方案,但它应该可以与其他平台一起使用。

更新

根据phani的要求,我修复了代码以删除标签上的 .txt 。替换以下行:

    sub(/\..*$/, "2", tag) # Replace extension with number 2, a.txt ==> a2

使用:

    sub(/\.txt$/, "", tag) # Remove the .txt extension

答案 3 :(得分:0)

你快到了。你错放了if块中的空格。它应该是这样的:

if [[ "$A1" = "$B1" && "$A1" = "$C1" ]];then

请注意,开口括号之间没有空格,并且在结束之前有一个空格。

此外,您的测试文件中也存在一些拼写错误。在a.txt中你应该改变

klm.nop=789

kml.nop=789 

c.txt你应该改变

abca.def=12344

abc.def=12344