列出在文本文件中找不到的字符串

List strings which are not found in a text file

我有一个包含数百个文件的目录。目录中所有文件的名称也列在 Javascript 文件中(见下文)。我想在目录中找到文本文件中 不存在 的文件名。示例:

% ls ./images/ 
a.png
c.png
x.png

文件:

{
   name: "A",
   filename: "a.png"

},
{
   name: "X",
   filename: "x.png"

}

在这种情况下,输出应该是“c.png”。

我找到了一些能够找到字符串的 awk 脚本(参见:awk script: check if all words(fields) from one file are contained in another file)。但是在我的例子中,我想找到 匹配的文件列表。

这是 perl 中的解决方案:

@list 是包含文件名的数组。

open(my $fh, "<", "input.txt");
my $contents = do { local $/ = <$fh> };
my $string = <$fh>;
close($fh);

foreach my $entry (@list) {
    print "$entry is not in file\n" if index($contents, $entry) == -1;
}

你想要的可以通过以下命令完成

$ mawk '/filename:/{gsub("\"","",);names[]}
        END{while(("ls ?.png"|getline fnm)>0){
               if(!(fnm in names)) print fnm
        }}' file.dat

在第一行中,我们扫描数据文件,查找字符串 "filename",从引号中去除文件名并最终将文件名保存在数组中。

END 处,我们对相关 ls 命令的输出进行循环,如果当前文件名未保存在数组中,我们将其打印到标准输出。

困难的部分是为最终的 for 循环获取正确的语法...


附录

跟进楼主的评论,这里是 脚本的修改版本

$ mawk '/filename:/{gsub("\"","",);names[]}
        END{while(("ls /var/www/html/img/*.png"|getline path)>0){
                n = split(path, parts, "/")
                fnm = parts[n]
                if(!(fnm in names)) print fnm
        }}' file.dat

适用于固定目录名称。如果目录名必须是 在运行时给定,尝试以下

 $ extra_png () {
 mawk '/filename:/{gsub("\"","",);names[]}
        END{while(("ls '""'/*.png"|getline path)>0){
                n = split(path, parts, "/")
                fnm = parts[n]
                if(!(fnm in names)) print fnm
        }}' ""
 }
 $ extra_png data.txt /var/www/html/img
 c.png
 $

其中第一个命令定义了一个 shell 接受作为 参数一个数据文件和一个要扫描的目录。

附带说明一下,此 awk 脚本会查找中未提及的 png 文件 数据文件(根据 OP 请求),了解它可能很有趣 如果文件中提到的文件名不存在于 目录。但这可能是另一个问题的主题。

$ cat tst.awk
BEGIN {
    while (ARGC > 2) {
        sub(/.*\//,"",ARGV[--ARGC])
        targets[ARGV[ARGC]]
        delete ARGV[ARGC]
    }
}
sub(/.*filename:[[:space:]]*"/,"") && sub(/\"[[:space:]]*$/,"") {
    present[[=10=]]
}
END {
    print "Present:"
    for (file in present) {
        if (file in targets) {
            print "\t" file
        }
    }

    print "\nAbsent:"
    for (file in targets) {
        if (! (file in present) ) {
            print "\t" file
        }
    }
}

$ awk -f tst.awk file image/*
Present:
        x.png
        a.png

Absent:
        c.png

请注意,无论您的文件名包含什么字符(包括空格和双引号),这都会起作用,并且不会尝试解析 ls 的输出,这总是一个坏主意。

使用 Perl 列出数据文件中但目录列表中缺少的文件的一种简单方法是使用目录中的文件测试(或通过完整路径)打印文件名 "if file does not exist" 或 "unless file does exist":

perl -nE 'map { say if !-e $_ } m/\"(.*)\"/ if /filename/' data.js

做相反的事情(你的例子) - 打印文件名($fname)如果无法在从文件列表数据 (data.js) 创建的名称数组 (@m) 中找到目录列表:

perl -nE 'push @m, m/\"(.*)\"/ if /filename/ }{ 
         for $fname (glob "*"){ say $fname if !grep { $_ eq $fname } @m}' data.js

这是@neuhaus 发布内容的完整脚本变体。不同之处在于以下方法使用 IO::All 从目录 './images/' 创建一个 IO "object" 作为散列,然后使用 keys 列出文件的名称。我修改了您的文本文件中的数据以说明 grep unless 语句:

# files.pl
use IO::All;
@files =  keys %{ io('./images/') }  ;

while(<DATA>) {
  push @flist, m/\"(.*)\"/ if /filename/  ; 
}

for $fname ( @flist) {print $fname unless grep { $_ eq $name } @files}  ;

__DATA__

{
   name: "A",
   filename: "a.png"
},
   {
   name: "X",
   filename: "x.png"
},
  {
   name: "Z",
   filename: "z.png"
}

输出(如果perl files.pl在包含./images/目录的目录中是运行):

  % ls ./images/ 
  a.png x.png y.png z.png
  % perl files.pl
  y.png

__DATA__ 部分(代表 data.js 文件)文件名被提取到 @files。目录列表中的文件打印 unless 它们可以在 @files.

中用 grep 找到

这是一个与您的数据在 data.js:

中的单行版本
perl -MIO::All -lne 'push @flist, m/\"(.*)\"/ if /filename/ ; 
   }{ for $name (keys %{ io "./images/" }){ print $name 
   unless grep { $_ eq $name } @flist }' data.js

更像 Unix 的方法可能会使用 /images/ 目录中的 glob(注意:在某些平台上有时会出现有关文件名带空格的问题):

 perl -MIO::All -lne  'push @flist, m/\"(.*)\"/ if /filename/ ; 
    }{ for $name ( glob("*.png") ){ print $name 
    unless grep { $_ eq $name } @flist }' data.js

或文件和目录句柄 open and opendir

... 
opendir(my $dir, ".") || die; 
@files = readdir $dir ;
...

如果您可以从 CPAN 安装一些很酷的模块,我建议您使用更简洁的(恕我直言)脚本来完成您的任务:

#!/usr/bin/perl

use strict; use warnings; use 5.010; 
use JSON;
use Path::Tiny;

my $json_data = path('images.json')->slurp;
my $data = decode_json( $json_data );

my %files_to_check = map { $_->basename => 0 } path('images')->children; 
my @files_in_json = map { $_->{filename} } @$data; 
delete @files_to_check{ @files_in_json }; # delete all files we have in JSON

say "$_" for sort keys %files_to_check;

每当您有必须在列表中找到或找不到某物的想法时,请考虑 哈希。散列是索引列表的一种快速方法,因为您只需查看键即可确定列表中是否包含某些内容。

在本程序的前半部分,我将浏览您的 JSON 文件以查找文件名并将它们存储在名为 %files 的散列中。在下半部分,我遍历了我的 png 文件所在的目录,并检查每个文件是否都在那个 %files 哈希中。如果特定条目不存在,我知道它不在我的 JSON 文件中。

NOTE: I could have used use JSON; to parse my JSON file. However, in this demonstration, I am merely looking for filename lines to keep things simple. If this was an actual program, use the JSON module.

#! /usr/bin/env perl
use strict;
use warnings;
use autodie;
use feature qw(say);

use constant {
    FILE_NAME       => 'file.txt',
    DIR_NAME        => 'temp',
};

#
# Build the %files hash
#
open my $fh, "<", FILE_NAME;
my %files;
while ( my $line = <$fh> ) { 
    chomp $line;
    next unless $line =~ /\s+filename:\s+"(.+)"/;
    my $file = ;
    $files{$file} = 1;
}
close $fh;

#
# Go through directory looking for entries not in %files
#
opendir my $dh, DIR_NAME;
while ( my $file = readdir $dh ) {
    next if $file eq "." or $file eq "..";
    if ( not exists $files{$file} ) {
        say qq(File "$file" not in list);
    }
}
closedir $dh;