Preencha 10000 caminhos no documento.txt e verifique se existem arquivos… com o awk?

0

Eu quero ler todos os arquivos da minha biblioteca de fotos e verificar se eles realmente existem. Meu conhecimento AppleScript é tão bom e suficiente para perceber isso. Mas isso é sobre um grande número de arquivos e o AppleScript é - definitivamente - inadequado para isso. Para 10.000 arquivos, leva 20 minutos. Então eu decidi fazer as partes mais importantes do script com shell scripts .... mas eu sou bastante inexperiente no mundo Unix e tive que completar um curso intensivo de busca de internet de dois dias. No entanto, cheguei agora a um ponto em que gostaria de receber sua ajuda!

Aqui estão minhas experiências:

Vou incorporar tudo em um AppleScript. Como muitos arquivos precisam ser editados, achei que seria melhor salvá-los em arquivos de texto temporários entre as etapas. Na primeira etapa, o banco de dados é lido. Levará apenas um segundo:

Caminho | Nome | ID | Referência | Nome do disco rígido externo

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

Na próxima etapa, as partes do caminho ausentes são adicionadas

/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

Demora 2:30 minutos com a minha solução para 10.000 arquivos no meu Mac. O AppleScript em execução parece estar no limite de sobrecarga! Rodando no Terminal.app, vejo no cabeçalho da janela que há sempre um salto entre awk e bash ... Eu acho que tem algo errado.

Na próxima etapa, quero verificar os caminhos para ver se eles existem. Como é semelhante ao script anterior, ele também demora um pouco mais. A última etapa grava arquivos ausentes em um arquivo de texto.

.

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

Eu ficaria muito feliz com ajuda ou sugestões para melhorar os scripts

    
por spazek 01.04.2018 / 00:23

2 respostas

2

Se você tiver o GNU awk versão 4 ou posterior instalado, ele tem a capacidade de carregar módulos externos que fornecem funcionalidade não presente no padrão awk ou mesmo% de awk aprimorada pelo GNU. Ele vem com um conjunto de módulos, incluindo um chamado filefuncs . O módulo filefuncs inclui um wrapper awk para a função stat do sistema, que pode ser usada para obter informações sobre arquivos (incluindo se eles existem ou não).

O script awk a seguir carrega o módulo filefuncs , lê cada linha de entrada, verifica a quinta coluna para decidir qual caminho será pré-pendurado em cada nome de arquivo de entrada e verifica se o arquivo existe. Em caso afirmativo, imprime o caminho completo e o nome do arquivo para stdout. Se isso não acontecer, imprime uma mensagem de aviso para stderr.

A matriz associativa paths (AKA um "hash" ou "array com hash") e o caminho pré-pendido padrão são meus melhores palpites sobre o que você pretende. Ajuste conforme necessário. Ele corresponde aos dados em suas amostras fornecidas (até mesmo o erro óbvio com a mídia - > / Volumes / Logic), não o que você disse em um dos seus comentários. Se o seu comentário estiver correto, o código pode ser simplificado.

#!/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;
}

Salvar como, por exemplo ./exists.awk , torne-o executável com chmod +x (o mesmo que você faria com um shell script) e execute-o assim:

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

ou canalize o sqlite3 diretamente para ele:

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

Eu não sei qual versão do awk vem com o Mac OS atualmente. Eu suspeito que é provavelmente um BSD awk ou uma versão antiga do GNU awk de um tempo antes de a Free Software Foundation mudar para a licença GPLv3 (é por isso que os Macs estão presos no antigo bash v3 bash versão 4 - não é porque a Apple não pode atualizar o bash, é porque eles não serão . Use brew se você precisar de uma versão posterior do GNU bash ou awk ).

De qualquer forma, se você não tem o GNU awk > = v4.0 instalado, você pode fazer o mesmo com qualquer versão de perl .

O script perl a seguir não usa nenhum módulo ou recurso perl não padrão e nem precisa usar a função perl integrada do stat() , porque o perl tem operadores semelhantes aos do sh para testar se existe um arquivo. Usaremos o operador -e aqui, que testa a existência de um arquivo, igual ao de 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";
}

Novamente, salve-o como exists.pl e torne-o executável com chmod +x . Executar como:

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

Qualquer um desses dois scripts será centenas ou milhares de vezes mais rápido que um script de shell usando um while read ou loop similar.

    
por 08.04.2018 / 04:50
1

Concordo que gawk4 ou perl - ou python - é uma solução melhor para esse problema. No entanto, para futura referência e edificação, é possível tornar seu script de shell melhor ou, pelo menos, menos ruim.

Primeiro e mais importante, você não precisa executar awk ou cut muitas vezes para dividir os campos; contanto que seus campos estejam separados por um único caractere, o que eles são, o shell read pode fazer isso por você. Não sei por que você especificou o delimitador como awk as [=\|] , o que significa sinal de igual ou vert-rule-aka-pipe, quando seus dados são de um comando sqlite3 que usa apenas regra vertical e nunca sinal de igual. Assim, você quer começar com algo como:

 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 ...')

Provavelmente, é melhor adicionar a opção -r em read ; seus dados de exemplo não continham nenhuma barra invertida, mas se os dados reais fossem obtidos, eles seriam corrompidos sem -r . A abordagem de pipeline é um pouco mais portátil, mas em geral um pouco mais arriscada, porque pode não funcionar quando é necessário definir var (s) ou fazer outras alterações shell como cd dentro do loop que persistem após o loop - mas você não faz.

Em segundo lugar, você não precisa de várias passagens e (tantos) arquivos intermediários se você mesclar a lógica:

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

Por fim, sugiro usar nomes de variáveis mais significativos, como path name id em vez de var1 etc, mas isso só é importante para os humanos que estão lendo o script, como você, daqui a alguns meses; o computador não se importa. Você pode escolher livremente nomes de variáveis em minúsculas para variáveis de shell; por convenção ambiente variáveis (isto é, variáveis de shell que são exportadas para programas e shells filho) são maiúsculas, mas então você deve ter um pouco de cuidado para não entrar em conflito com alguns especiais vars / envvars embutidos o shell ou padronizado em todo o sistema.

    
por 08.04.2018 / 11:17

Tags