awkを使用して.txtドキュメントに10000のパスを完成させ、ファイルが存在することを確認してください...?

awkを使用して.txtドキュメントに10000のパスを完成させ、ファイルが存在することを確認してください...?

私の写真ライブラリのすべてのファイルを読み、実際に存在することを確認したいと思います。これまでのところ、私のAppleScriptの知識はこれを行うのに十分です。ただし、これには多くのファイルが含まれており、AppleScriptはこれには適していません。 10,000ファイルには20分かかります。そのため、スクリプトの最も重要な部分を実行するためにシェルスクリプトを使用することにしました。しかし、私はUnixの世界の経験がなかったので、インターネット検索に関する2日間の集中講座を履修しなければなりませんでした。 しかし今、私はあなたの助けが必要なポイントに達しました!

私の実験は次のとおりです。

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

awkGNUバージョン4以降がインストールされている場合は、標準のawkまたはGNUアドバンストバージョンにはない機能を提供する外部モジュールをロードできますawkfilefuncsこのfilefuncsモジュールには、ファイルに関する情報(存在するかどうかを含む)を取得するために使用できるシステム機能のラッパーが含まれていawkますstat

次のawkスクリプトはfilefuncsモジュールをロードし、各入力行を読み取り、5番目の列を調べて各入力ファイル名の前のパスを確認し、ファイルが存在することを確認します。その場合は、フルパスとファイル名を標準出力として印刷します。それ以外の場合は、stderrに警告メッセージを印刷します。

連想配列paths(別名「ハッシュ」または「ハッシュ配列」)とデフォルトのプリセットパスは、ユーザーが意図したものに最適な推測です.必要に応じて調整してください。あなたのコメントの1つで述べたものではなく、あなたが提供した例のデータと一致します(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によるものではありません)。awkbashbashできないbashをアップグレードしてください。に慣れる。使用醸造より高いバージョンのGNUが必要な場合、bashまたはawk)。

とにかく、GNU awk> = v4.0がインストールされていない場合は、すべてのバージョンのperl.

次のperlスクリプトは非標準のPerlモジュールや機能を使用せず、Perlにはperlファイルの存在をテストするためのstat()同様の演算子があるため、組み込み関数の使用も必要ありません。ここでは、次のようにファイルが存在するかどうかをテストするために演算子を使用しshます。-esh

#!/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これらのスクリプトの1つは、同様のループを使用するシェルスクリプトよりも数百または数千倍高速です。

答え2

私はgawk4またはPerl(またはPython)がこの問題を解決するより良い方法であることに同意します。しかし、後で参照してインスピレーションを得るために、シェルスクリプトをより良くするか、少なくとも悪くすることは可能です。

何よりも走る必要はありません。awk または cutフィールドを複数回分割します。フィールドが単一文字で区切られている限り、シェルはreadこれを実行できます。なぜ区切り記号を等号awkで指定したのか分かりません。[=\|]またはvert-rule-aka-pipe、データがsqlite3vert-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と競合しないように注意する必要があります。

関連情報