【问题标题】:List strings which are not found in a text file列出在文本文件中找不到的字符串
【发布时间】:2015-04-04 00:30:39
【问题描述】:

我有一个目录,里面有数百个文件。目录中所有文件的名称也列在 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)。但是在我的情况下,我想找到 匹配的文件列表。

【问题讨论】:

  • 您确定输入文件不是 JSON 吗?如果是,namefilename 应该用双引号引起来。
  • 如果是 JSON,那么肯定是通过 JSON 解析器运行的东西。如果不是,那么有人需要戳戳来制作看起来不像 JSON 的东西。 :)

标签: perl shell command-line awk sed


【解决方案1】:

使用 Perl 列出数据文件中但目录列表中缺少的文件的一种简单方法是在目录中使用文件测试 (或传递完整路径),该文件会打印文件名“如果文件不存在”或“除非文件存在”:

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

做相反的事情(你的例子) - ie 如果在目录列表中找不到文件名($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“对象”作为哈希,然后列出带有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

openopendir 的文件和目录句柄

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

【讨论】:

    【解决方案2】:

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

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

    注意:我本可以使用 use JSON; 来解析我的 JSON 文件。然而,在这个演示中,我只是在寻找filename 行以保持简单。如果这是一个实际的程序,请使用JSON 模块。

    #! /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 = $1;
        $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;
    

    【讨论】:

      【解决方案3】:

      如果你可以从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;
      

      【讨论】:

        【解决方案4】:

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

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

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

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

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


        附录

        跟进原始海报的评论,这里是 脚本的修改版本

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

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

        作为旁注,这个awk 脚​​本可以找到未提及的 png 文件 数据文件(根据 OP 请求),可能会很有趣 如果文件中提到的文件名不存在于 目录。但这可能是另一个问题的主题。

        【讨论】:

        • 图像文件位于其他目录中。 ls /var/www/html/img/* 当我这样做时,它会列出所有文件。
        【解决方案5】:

        这是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;
        }
        

        【讨论】:

        • 建议填充@list 的方法会很有用。
        【解决方案6】:
        $ cat tst.awk
        BEGIN {
            while (ARGC > 2) {
                sub(/.*\//,"",ARGV[--ARGC])
                targets[ARGV[ARGC]]
                delete ARGV[ARGC]
            }
        }
        sub(/.*filename:[[:space:]]*"/,"") && sub(/\"[[:space:]]*$/,"") {
            present[$0]
        }
        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 的输出,这总是一个坏主意。

        【讨论】:

        • 看起来所有文件都列在缺席部分。文本文件仅包含不包含路径名的文件名。例如:x.png and not /var/www/img/x.png
        • 好的,但你为什么要提到它?脚本做你想做的,对吧?
        • 哦,我的意思是这些文件实际上存在于数据文件中。它们需要在本节中显示。脚本不起作用。
        • 你想告诉我们什么?该脚本检查image 目录中的文件是否完全按照您的要求存在于给定文件中。脚本以什么方式“不起作用”?编辑您的问题以澄清是否对您的要求有误解。
        猜你喜欢
        • 2016-03-08
        • 1970-01-01
        • 1970-01-01
        • 2015-07-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多