Awk-比较两个文件中的数字并在新文件中写入差异

Awk-比较两个文件中的数字并在新文件中写入差异

我有两个带有项目编号的列表,并且希望通过在新文件中写入两个文件中都不存在的数字来标记这些列表之间的差异。

两个文件在第 2 列中都有项目编号,在第 3 列中包含零件 ID。首先,我想检查文件 1 中的项目编号是否存在,如果存在,则必须检查零件 ID,如果为真,则转到下一个项目编号。否则,如果其中一个条件不成立,则应将差异写入新创建的文件中。如果项目编号仅在两个文件之一中,则程序应写入“Artikel[x] 无法找到”。

例子

文件1

Artikel[ 456]= 1,2
Artikel[ 877]= 3
Artikel[ 278]= 4
Artikel[ 453]= 13

文件2

Artikel[ 456]= 2, 1 
Artikel[ 877]= 3, 5 
Artikel[ 387]= 4, 9, 4 
Artikel[ 947]= 10

输出

Artikel[ 877]= 3 != Artikel[ 877]= C3, C5
Artikel[ 278]= 4 != Artikel[ 278 ]= 4, 9, 4
Artikel[ 453]= 13 cannot be found in File 2!
Artikel[ 947]= 10 cannot be found in File 1!

我以为我可以通过将文件 1 中的项目编号写入数组中并仅检查文件 2 中的每一行来做到这一点,但不知何故我很难管理它。我将不胜感激任何帮助。

谢谢

答案1

使用任何 POSIX awk:

$ cat tst.awk
BEGIN {
    FS = "[]=[]+"
    f1 = ARGV[1]
    f2 = ARGV[2]
}
{
    gsub(/[[:space:]]+/,"")
    gsub(/,/,"& ")
    key = $1 "[ " $2 " ]="
    keys[key]
    vals = substr($0,index($0,"=")+1)
}
FILENAME == f1 {
    f1KeyVals[key] = vals
}
FILENAME == f2 {
    f2KeyVals[key] = vals
}
END {
    for ( key in keys ) {
        if ( (key in f1KeyVals) && (key in f2KeyVals) ) {
            if ( f1KeyVals[key] != f2KeyVals[key] ) {
                areDifferent = 0

                delete f1vals
                split(f1KeyVals[key],tmp,/, */)
                for ( i in tmp ) { f1vals[tmp[i]] }

                delete f2vals
                split(f2KeyVals[key],tmp,/, */)
                for ( i in tmp ) { f2vals[tmp[i]] }

                for ( val in f1vals ) {
                    if ( val in f2vals ) {
                        delete f2vals[val]
                    }
                    else {
                        areDifferent = 1
                        break
                    }
                }

                for ( val in f2vals ) {
                    areDifferent = 1
                    break
                }

                if ( areDifferent ) {
                    printf "%s %s != %s %s\n", key, f1KeyVals[key], key, f2KeyVals[key]
                }
            }
        }
        else if ( key in f2KeyVals ) {
            printf "%s cannot be found in %s!\n", key, f1
        }
        else {
            printf "%s cannot be found in %s!\n", key, f2
        }
    }
}
$ awk -f tst.awk file1 file2
Artikel[ 5129720100 ]= cannot be found in file2!
Artikel[ 5089100000 ]= C3 != Artikel[ 5089100000 ]= C3, C5
Artikel[ 4005530901 ]= cannot be found in file1!
Artikel[ 5091270000 ]= C4 != Artikel[ 5091270000 ]= C4, C19, C34

上面假设如果值曾经重复,例如,C1, C1, C2它们应该以与不重复相同的方式处理,即C1, C2

答案2

TXR口齿不清:

(defun read-file (path)
  (let ((h (hash)))
    (with-stream (s (open-file path))
      (whilet ((line (get-line s)))
        (if-match `Artikel[ @item ]= @list` line
          (let ((idh (flow list
                       (tok #/[^ ,]+/)
                       hash-list)))
            (set [h item] idh))))
      h)))

(defun out-one (h file)
  (dohash (item ids h)
    (put-line `Artikel[ @item ] = @{(hash-values ids) ", "} cannot be found in @file!`)))

(defun out-both (h)
  (dohash (item id-pair h)
    (tree-bind (left-ids . right-ids) id-pair
      (unless (equal left-ids right-ids)
         (put-line `Artikel[ @item ] = @{(hash-values left-ids) ", "} !=\ \
                    Artikel[ @item ] = @{(hash-values right-ids) ", "}`)))))

(let* ((h0 (read-file "file1"))
       (h1 (read-file "file2")))
  (out-one (hash-diff h0 h1) "File 2")
  (out-one (hash-diff h1 h0) "File 1")
  (out-both [hash-isec h0 h1 cons]))

输出:

$ txr diff.tl
Artikel[ 5129720100 ] = C13 cannot be found in File 2!
Artikel[ 4005530901 ] = C10 cannot be found in File 1!
Artikel[ 5091270000 ] = C4 != Artikel[ 5091270000 ] = C34, C19, C4
Artikel[ 5089100000 ] = C3 != Artikel[ 5089100000 ] = C3, C5

答案3

以下不是完整的解决方案,但了解 Unix 命令很有用join

▷  join -v 1 -t= <(sort -k 1b,1 FILE1) <(sort -k 1b,1 FILE2) | tr '=' '\t'
Artikel[ 5129720100 ]    C13
▷  join -v 2 -t= <(sort -k 1b,1 FILE1) <(sort -k 1b,1 FILE2) | tr '=' '\t'
Artikel[ 4005530901 ]    C10
▷  join -t= <(sort -k 1b,1 FILE1) <(sort -k 1b,1 FILE2) | tr '=' '\t'
Artikel[ 4003526101 ]    C1,C2    C2,C1
Artikel[ 5089100000 ]    C3    C3,C5
Artikel[ 5091270000 ]    C4    C4,C19,C34

join需要排序输入并忽略前导空格,因此排序选项如图所示。输出-v <i>文件中不可配对的行。<i>有了这些输出,计算您需要的东西就变得容易多了。

答案4

使用(以前称为 Perl_6)

~$ raku -e 'my %hash1; for "path/to/file1.txt".IO.lines() {  
               .split("= ") andthen %hash1.append: .[0] => .[1].split(",") };  
            my %hash2; for "path/to/file2.txt".IO.lines() {  
               .split("= ") andthen %hash2.append: .[0] => .[1].split(",") };  
            for (%hash1.keys ∩ %hash2.keys).map(*.key) -> $i {  
                unless %hash1{$i} == %hash2{$i} {  
                    put ($i ~ "= " ~ %hash1{$i}.join(",") ~ " != " ~ $i ~ "= " ~ %hash2{$i}.join(","))  // next} };  
            my ($k2,$v2) = %hash2{(%hash2.keys (-) %hash1.keys)}:kv;   
            my ($k1,$v1) = %hash1{(%hash1.keys (-) %hash2.keys)}:kv;  
            put $k2 ~ "= " ~ $v2.join(",") ~ " cannot be found in File 1!" // next;  
            put $k1 ~ "= " ~ $v1.join(",") ~ " cannot be found in File 2!" // next;'

示例输出:

Artikel[ 5091270000 ]= C4 != Artikel[ 5091270000 ]= C4,C19,C34
Artikel[ 5089100000 ]= C3 != Artikel[ 5089100000 ]= C3,C5
Artikel[ 4005530901 ]= C10 cannot be found in File 1!
Artikel[ 5129720100 ]= C13 cannot be found in File 2!

上面是用 Raku(Perl 编程语言家族的最新成员)编写的答案。 Raku 具有对内置 Unicode 的高级支持以及高级正则表达式引擎。这个答案利用了 Raku 的%签名哈希(键值)数据结构(Perl 系列语言的一个功能)。

  • 简而言之,文件按行读入 a %hash:该行split给出=两个部分,.[0]第一部分成为键,而逗号split第二部分 ( .[1]) 成为值。

  • Raku 内置了 Set 函数,因此您只需通过书写%hash1.keys ∩ %hash2.keys(使用 Unicode 中缀字符或 3 字符 ASCII 中缀(&))即可获得哈希键的交集。

  • 从交集结果来看,代码%hash{$k}是一个基本的键查找以返回关联的值。有了这些知识,您就可以构建一个输出字符串(~波浪线用于将字符串连接在一起)。由于unless %hash1{$i} == %hash2{$i}(unless与) 子句的关系,不会输出值相等的哈希键if not

  • Raku 还具有 Set Difference 函数,此处由 3 个字符的 ASCII 中缀表示(-)。使用3 字符 ASCII 中缀(-)是因为实际的 Unicode“SET-MINUS”符号(U+2216) 很容易与另一个混淆。计算两个散列键差异,为每个和每个 out 构造一个输出字符串put


注1:上面的代码没有对每个键值的唯一性做出任何假设。因此,如果一个文件中有重复的值(但另一个文件中没有),它将在输出中显示为差异。要使每个哈希值唯一,请添加unique到每个哈希构造函数,例如%hash.push: .[0] => .[1].split(",").unique

笔记2:上面的代码并没有尝试简化“Artikel”键,但您可能最好使用.[0]正则表达式将每个键简化为仅数字,如下所示:.[0].match(/ \d+ /).Str

注3:在此示例中,输入路径是硬编码的,但您可以硬编码一个(证明文件)并采取测试$*ARGFILES.IO.lines() {...};使用, 甚至从命令行关闭文件$*IN.IO.lines() {...};(确保<适当地重定向 STDIN)。请参阅下面的第二个链接了解更多 CLI 选项(例如使用 Raku 的@*ARGS命令行数组等)。


https://docs.raku.org/language/setbagmix#Sets,_bags,_and_mixes
https://docs.raku.org/language/create-cli
https://raku.org

相关内容