使用awk完成.txt文档中的10000个路径并检查文件是否存在...?

使用awk完成.txt文档中的10000个路径并检查文件是否存在...?

我想读取我的照片库中的所有文件并检查它们是否确实存在。到目前为止,我的 AppleScript 知识还不错,足以实现这一点。但这涉及大量文件,而 AppleScript 绝对不适合于此。对于 10,000 个文件,需要 20 分钟。所以我决定用 shell 脚本来完成脚本中最重要的部分......但我对 Unix 世界缺乏经验,必须完成为期两天的互联网搜索速成课程。 然而,我现在已经到了需要你帮助的地步!

这是我的实验:

我会将其全部嵌入 AppleScript 中。由于必须编辑很多文件,我认为最好将它们保存在步骤之间的临时文本文件中。第一步,读取数据库。只需一秒钟:

路径 |名称 |身份证 |参考|外部硬盘名称

2018/03/27/20180327-122110/TVTower.JPG|TVTower|hA3CRRfPSS6FXqk7IDobLw|0|
Projekte/BCT 2017/BCT Fotos GPS/BCT_GPS_001.JPG|BCT_A_GPS_001|hyvsQgiaR4e3ou7XIZ%Gjg|1|Media
Leo/Carmina Burana/Leo UdK/IMG_0626.JPG|IMG_0626|j7342DtGSmag7YVLN1Nzhg|1|Logic
Users/spazek/Desktop/WeTransfer/Bild 2.png|Bild 2|Sa7rckZiSd2bIiRVO0JidA|1|macOS

在下一步中,添加缺少的路径部分

/Users/spazek/Pictures/Fotos Library.photoslibrary/Masters/2018/03/27/20180327-122110/TVTower.JPG|TVTower|hA3CRRfPSS6FXqk7IDobLw|0|
/Volumes/Logic/Projekte/BCT 2017/BCT Fotos GPS/BCT_GPS_001.JPG|BCT_A_GPS_001|hyvsQgiaR4e3ou7XIZ%Gjg|1|Media
/Volumes/Logic/Leo/Carmina Burana/Leo UdK/IMG_0626.JPG|IMG_0626|j7342DtGSmag7YVLN1Nzhg|1|Logic
/Users/spazek/Desktop/WeTransfer/Bild 2.png|Bild 2|Sa7rckZiSd2bIiRVO0JidA|1|macOS

我的解决方案在 Mac 上处理 10,000 个文件需要 2 分 30 分钟。正在运行的 AppleScript 似乎已经达到了过载的极限!在 Terminal.app 中运行,我在窗口标题中看到 awk 和 bash 之间总是有跳转...我猜有问题。

在下一步中,我想检查路径以查看它们是否存在。由于与之前的脚本类似,因此也需要更长的时间。最后一步将丢失的文件写入文本文件。

sqlite3  -separator $'|' /Users/spazek/Desktop/xsystx/systphotos.db 'select RKMaster.imagePath, RKMaster.name, RKMaster.uuid, RKMaster.fileIsReference, ( select RKVolume.name from RKVolume where RKVolume.modelId  = RKMaster.volumeId) from RKMaster' > /Users/spazek/Desktop/filelist1.txt

while read f; do
    var1=`echo "$f" | awk -F[=\|] '{print $1}'`;
    var2=`echo "$f" | awk -F[=\|] '{print $2}'` ;
    var3=`echo "$f" | awk -F[=\|] '{print $3}'` ;
    var4=`echo "$f" | awk -F[=\|] '{print $4}'` ;
    var5=`echo "$f" | awk -F[=\|] '{print $5}'` ;
    if  [ "$var4" == 0 ] ; then
        echo /Users/spazek/Pictures/Fotos Library.photoslibrary/Masters/"${f}" ;
    else
        if [ "$var5" == "macOS" ]; then
            echo /"${f}" ;
        else
            echo /Volumes/"$var5"/"${f}";
        fi;
    fi >> /Users/spazek/Desktop/filelist2.txt;
done < /Users/spazek/Desktop/filelist1.txt

while read f; do
    var1=`echo "$f" | awk -F[=\|] '{print $1}'`;
    var3=`echo "$f" | awk -F[=\|] '{print $3}'` ;
    test -f "$var1" || echo "$var1|$var3" >> /Users/spazek/Desktop/filelist3.txt;
done < /Users/spazek/Desktop/filelist2.txt

while read f; do
    var1=`echo "$f" | awk -F[=\|] '{print $1}'`;
    var2=`echo "$f" | awk -F[=\|] '{print $2}'` ;
    test -f "$var1" || echo "Name = $var2 \n Path = $var1 \n";
done > ~/Desktop/Photos_MissingItems.txt < /Users/spazek/Desktop/filelist3.txt

我很高兴获得改进脚本的帮助或建议

答案1

如果您awk安装了 GNU 版本 4 或更高版本,它能够加载提供标准 awk 甚至 GNU 增强版中不存在的功能的外部模块awk。它附带了一组模块,其中一个称为filefuncs.该filefuncs模块包括一个awk系统stat函数的包装器,可用于获取有关文件的信息(包括它们是否存在)。

以下awk脚本加载filefuncs模块,读取每个输入行,检查第五列以确定每个输入文件名前面的路径,并检查文件是否存在。如果是,它将完整路径和文件名打印到标准输出。如果没有,它会向 stderr 打印一条警告消息。

关联数组paths(又名“散列”或“散列数组”)和默认的预置路径是我对您的意图的最佳猜测。根据需要进行调整。它与您提供的示例中的数据相匹配(即使是媒体 -> /Volumes/Logic 的明显错误),而不是您在评论之一中所说的内容。如果您的评论是准确的,那么代码可以简化。

#!/usr/bin/awk -f

# this will only work with GNU awk >= version 4.0
@load "filefuncs"

BEGIN {
  FS=OFS="|";
  paths["default"] = "/Users/spazek/Pictures/Fotos Library.photoslibrary/Masters/";
  paths["Logic"] = "/Volumes/Logic/";
  paths["Media"] = "/Volumes/Logic/";
  paths["macOS"] = "/";
}

{ if ($5 in paths) {
    filename = paths[$5] $1;
  } else { # $5 not known in paths array, use a default
    filename = paths["default"] $1;
  }

  # try to stat the file. get the return code in variable 'rc' and error
  # string (if any) in 'error'.
  rc=stat(filename,fstat);
  error=ERRNO;   # oddly, ERRNO is a string, not a number.

  if (rc == -1) {  # return code of -1 is "No such file or directory"
    # print warning to stdout and skip to next input line
    print filename ": " error > "/dev/stderr"
    next;
  };

  # filename exists, do something with filename.
  print filename, $2, $3, $4, $5;
}

将其另存为,例如./exists.awk,使其可执行chmod +x(与使用 shell 脚本相同)并像这样运行它:

./exists.awk /Users/spazek/Desktop/filelist1.txt

或者直接通过管道将 sqlite3 输入到其中:

sqlite3  -separator $'|' /Users/spazek/Desktop/xsystx/systphotos.db \
'select RKMaster.imagePath, RKMaster.name, RKMaster.uuid, RKMaster.fileIsReference, ( select RKVolume.name from RKVolume where RKVolume.modelId  = RKMaster.volumeId) from RKMaster' \
  | ./exists.awk

我不知道awk现在 Mac OS 附带什么版本。我怀疑它可能是 BSD或自由软件基金会转而使用 GPLv3 许可证之前的awk某个古老版本的 GNU (这就是为什么 Mac 停留在古老的v3 而不是当前版本 4 - 这不是因为 Appleawkbashbash不能升级bash,因为他们惯于。使用酿造如果您需要更高版本的 GNUbashawk)。

无论如何,如果您没有安装 GNU awk >= v4.0,您可以使用任何版本的perl.

以下perl脚本不使用任何非标准的 perl 模块或功能,甚至不需要使用 的perl内置函数,因为 perl 具有与测试文件是否存在stat()类似的运算符。sh我们将-e在此处使用运算符来测试文件是否存在,与以下内容相同sh

#!/usr/bin/perl

use strict;

# declare %paths hash
my %paths = (
  "default" => "/Users/spazek/Pictures/Fotos Library.photoslibrary/Masters/",
  "Media"   => "/Volumes/Logic/",
  "Logic"   => "/Volumes/Logic/",
  "macOS"   => "/",
);

# main loop, read in each line of input and process it.
while(<>) {
  chomp; # strip trailing linefeed from end-of-line
  my $filename='';  # declare $filename to belong to this scope

  # split input on "|" characters
  my ($path,$name,$id,$reference,$diskname) = split /\|/;

  if (defined($paths{$diskname})) {
    $filename = $paths{$diskname} . $path;
  } else {  # diskname not known in %paths hash, use a default
    $filename = paths{"default"} . $path;
  }

  if (! -e $filename) {
    # print warning to stderr and skip to next input line
    warn "$filename: No such file or directory\n";
    next;
  };

  # filename exists, do something with filename.
  print join('|', $filename, $id, $reference, $diskname), "\n";
}

再次将其另存为exists.pl并使其可执行chmod +x。运行为:

./exists.pl /Users/spazek/Desktop/filelist1.txt

while read这两个脚本中的任何一个都会比使用或类似循环的shell 脚本快数百或数千倍。

答案2

我同意 gawk4 或 perl(或 python)是解决这个问题的更好方法。但是,为了将来的参考和启发,可以使您的 shell 脚本变得更好,或者至少不那么糟糕。

首先也是最重要的,你不需要运行awk 或者 cut多次分割田地;只要您的字段由单个字符分隔(确实如此),shellread就可以为您做到这一点。我不确定为什么您将分隔符指定awk[=\|]等号或者vert-rule-aka-pipe,当您的数据来自sqlite3仅使用 vert-rule 而从不使用等号的命令时。因此,您想从以下内容开始:

 while IFS='=|' read var1 var2 var3 var4 var5; do ... done <filelist1
 # change IFS='|' if you don't actually need to split on equal-sign 

 # could skip the first temp file, if you don't need it for anything else,
 # with either a pipeline (any shell):
 sqlite3 ... 'select ...' | while IFS.. read ...; do ... done
 # or process substitution (only bash and some others):
 while IFS.. read ...; do ... done < <(sqlite3 ... 'select ...')

最好将-r选项添加到read;您的示例数据不包含任何反斜杠,但如果实际数据包含任何反斜杠,那么如果没有-r.管道方法更便携,但通常风险更大,因为当需要设置 var(s) 或进行其他 shell 更改(如cd在循环内)时,它可能不起作用循环后仍然存在——但你没有。

其次,如果合并逻辑,则不需要多次传递和(这么多)中间文件:

while IFS.. read -r var1 var2 var3 var4 var5; do 
    if  [ "$var4" == 0 ]; then var1="/Users/spazek/Pictures/Fotos Library.photoslibrary/Masters/$var1"
    elif [ "$var5" == "macOS" ]; then var1="/$var1"
    else echo var1="/Volumes/$var5/$var1; fi
    test -f "$var1" || echo "Name = $var3 \n Path = $var1 \n"
done >~/Desktop/MissingPhotos.txt <filelist1 
# or options to avoid filelist1 per above

最后,我建议使用更有意义的变量名,例如path name id而不是var1等,但这只对阅读脚本的人有意义,比如几个月后的你;计算机不在乎。你可以自由地为 shell 变量选择小写的变量名;按照惯例环境变量(即导出到程序和子 shell 的 shell 变量)是大写的,但是您必须小心一点,不要与 shell 内置的或标准化系统范围内的某些特殊 vars/envvars 发生冲突。

相关内容