내 사진 라이브러리에 있는 모든 파일을 읽고 실제로 존재하는지 확인하고 싶습니다. 지금까지 나의 AppleScript 지식은 이 작업을 수행하기에 충분합니다. 그러나 여기에는 많은 파일이 포함되며 AppleScript는 이에 적합하지 않습니다. 파일 10,000개에는 20분이 소요됩니다. 그래서 스크립트의 가장 중요한 부분을 수행하기 위해 쉘 스크립트를 사용하기로 결정했습니다.하지만 나는 유닉스 세계에 대한 경험이 없었기 때문에 인터넷 검색에 관한 이틀간의 집중 강좌를 이수해야 했습니다. 하지만 이제 나는 당신의 도움이 필요한 지점에 이르렀습니다!
내 실험은 다음과 같습니다.
AppleScript에 모두 포함하겠습니다. 편집해야 할 파일이 많기 때문에 각 단계 사이에 임시 텍스트 파일로 저장해 두는 것이 가장 좋은 것 같습니다. 첫 번째 단계는 데이터베이스를 읽는 것입니다. 단 1초밖에 걸리지 않습니다:
경로|이름|ID|참조|외장 하드 드라이브 이름
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
(일명 "해시" 또는 "해시 배열")과 기본 사전 설정 경로는 사용자가 의도한 바에 대한 최선의 추측입니다. 필요에 따라 조정하십시오. 귀하의 의견 중 하나에서 말한 내용이 아니라 귀하가 제공한 예제의 데이터와 일치합니다(Media->/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
(셸 스크립트를 사용하는 것과 동일) 다음과 같이 실행합니다.
./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나 Free Software Foundation이 GPLv3 라이센스로 전환하기 전의 GNU의 고대 버전일지도 모른다고 생각합니다 awk
(이것이 Mac이 현재 버전 4 대신 고대 v3에 붙어 있는 이유입니다. 이는 Apple 때문이 아닙니다.awk
bash
bash
할 수 없다bash를 업그레이드하세요.에 익숙해. 사용양조더 높은 버전의 GNU가 필요한 경우 bash
또는 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
이러한 스크립트 중 하나는 유사한 루프를 사용하는 쉘 스크립트보다 수백 또는 수천 배 빠릅니다.
답변2
나는 gawk4나 Perl(또는 Python)이 이 문제를 해결하는 더 좋은 방법이라는 데 동의합니다. 그러나 나중에 참조하고 영감을 얻기 위해 쉘 스크립트를 더 좋게 만들거나 적어도 덜 나쁘게 만드는 것이 가능합니다.
무엇보다도 달릴 필요가 없습니다.awk
또는 cut
필드를 여러 번 분할하십시오. 필드가 단일 문자로 구분되어 있는 한 쉘이 read
이를 수행할 수 있습니다 . 왜 구분 기호를 등호 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
파이프 접근 방식이 더 이식성이 있지만 일반적으로 더 위험합니다. 설정) 또는 기타 쉘 변경(예: 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
. 그러나 이는 몇 달 후에 컴퓨터가 신경 쓰지 않는 것처럼 스크립트를 읽는 사람에게만 의미가 있습니다. 규칙에 따라 쉘 변수에 대한 소문자 변수 이름을 자유롭게 선택할 수 있습니다.환경변수(즉, 프로그램 및 하위 쉘로 내보낸 쉘 변수)는 대문자이지만 쉘의 일부 내장 또는 표준화된 시스템 전체 특수 변수/envvar와 충돌하지 않도록 주의해야 합니다.