我想读取我的照片库中的所有文件并检查它们是否确实存在。到目前为止,我的 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 - 这不是因为 Appleawk
bash
bash
不能升级bash,因为他们惯于。使用酿造如果您需要更高版本的 GNUbash
或awk
)。
无论如何,如果您没有安装 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 发生冲突。