Как я могу сравнить список файлов из архива tar и каталога?

Я все еще изучаю Perl. Может ли кто-нибудь предложить мне код Perl для сравнения файлов из .tar.gz и пути к каталогу.

Допустим, у меня есть резервная копия tar.gz следующего пути к каталогу, которую я сделал несколько дней назад.

a/file1
a/file2
a/file3
a/b/file4
a/b/file5
a/c/file5
a/b/d/file and so on..

Теперь я хочу сравнить файлы и каталоги по этому пути с файлом резервной копии tar.gz.

Пожалуйста, предложите код Perl для этого.


person Space    schedule 13.08.2009    source источник


Ответы (5)


Это может быть хорошей отправной точкой для хорошей программы Perl. Тем не менее, он делает то, о чем задавался вопрос.

Он был просто взломан и игнорирует большинство лучших практик для Perl.

perl test.pl full                            \
     Downloads/update-dnsomatic-0.1.2.tar.gz \
     Downloads/                              \
     update-dnsomatic-0.1.2
#! /usr/bin/env perl
use strict;
use 5.010;
use warnings;
use autodie;

use Archive::Tar;
use File::Spec::Functions qw'catfile catdir';

my($action,$file,$directory,$special_dir) = @ARGV;

if( @ARGV == 1 ){
  $file = *STDOUT{IO};
}
if( @ARGV == 3 ){
  $special_dir = '';
}

sub has_file(_);
sub same_size($$);
sub find_missing(\%$);

given( lc $action ){

  # only compare names
  when( @{[qw'simple name names']} ){
    my @list = Archive::Tar->list_archive($file);

    say qq'missing file: "$_"' for grep{ ! has_file } @list;
  }

  # compare names, sizes, contents
  when( @{[qw'full aggressive']} ){
    my $next = Archive::Tar->iter($file);
    my( %visited );

    while( my $file = $next->() ){
      next unless $file->is_file;
      my $name = $file->name;
      $visited{$name} = 1;

      unless( has_file($name) ){
        say qq'missing file: "$name"' ;
        next;
      }

      unless( same_size( $name, $file->size ) ){
        say qq'different size: "$name"';
        next;
      }

      next unless $file->size;

      unless( same_checksum( $name, $file->get_content ) ){
        say qq'different checksums: "$name"';
        next;
      }
    }

    say qq'file not in archive: "$_"' for find_missing %visited, $special_dir;
  }

}

sub has_file(_){
  my($file) = @_;
  if( -e catfile $directory, $file ){
    return 1;
  }
  return;
}

sub same_size($$){
  my($file,$size) = @_;
  if( -s catfile($directory,$file) == $size ){
    return $size || '0 but true';
  }
  return; # empty list/undefined
}

sub same_checksum{
  my($file,$contents) = @_;
  require Digest::SHA1;

  my($outside,$inside);

  my $sha1 = Digest::SHA1->new;
  {
    open my $io, '<', catfile $directory, $file;
    $sha1->addfile($io);
    close $io;
    $outside = $sha1->digest;
  }

  $sha1->add($contents);
  $inside = $sha1->digest;


  return 1 if $inside eq $outside;
  return;
}

sub find_missing(\%$){
  my($found,$current_dir) = @_;

  my(@dirs,@files);

  {
    my $open_dir = catdir($directory,$current_dir);
    opendir my($h), $open_dir;

    while( my $elem = readdir $h ){
      next if $elem =~ /^[.]{1,2}[\\\/]?$/;

      my $path = catfile $current_dir, $elem;
      my $open_path = catfile $open_dir, $elem;

      given($open_path){
        when( -d ){
          push @dirs, $path;
        }
        when( -f ){
          push @files, $path, unless $found->{$path};
        }
        default{
          die qq'not a file or a directory: "$path"';
        }
      }
    }
  }

  for my $path ( @dirs ){
    push @files, find_missing %$found, $path;
  }

  return @files;
}

После переименования config в config.rm, добавления дополнительного символа в README, изменения символа в install.sh и добавления файла .test. Вот что он выдал:

missing file: "update-dnsomatic-0.1.2/config"
different size: "update-dnsomatic-0.1.2/README"
different checksums: "update-dnsomatic-0.1.2/install.sh"
file not in archive: "update-dnsomatic-0.1.2/config.rm"
file not in archive: "update-dnsomatic-0.1.2/.test"
person Brad Gilbert    schedule 13.08.2009

См. Archive::Tar.

person Sinan Ünür    schedule 13.08.2009

Archive::Tar и File::Find будут полезны. Базовый пример показан ниже. Он просто выводит информацию о файлах в tar и файлах в дереве каталогов.

Из вашего вопроса неясно, как вы хотите сравнивать файлы. Если вам нужно сравнить фактический контент, вероятно, понадобится метод get_content() в Archive::Tar::File. Если подходит более простое сравнение (например, имя, размер и mtime), вам не потребуется гораздо больше, чем методы, используемые в приведенном ниже примере.

#!/usr/bin/perl
use strict;
use warnings;

# A utility function to display our results.
sub Print_file_info {
    print map("$_\n", @_), "\n";
}

# Print some basic information about files in a tar.
use Archive::Tar qw();
my $tar_file = 'some_tar_file.tar.gz';
my $tar = Archive::Tar->new($tar_file);
for my $ft ( $tar->get_files ){
    # The variable $ft is an Archive::Tar::File object.
    Print_file_info(
        $ft->name,
        $ft->is_file ? 'file' : 'other',
        $ft->size,
        $ft->mtime,
    );
}

# Print some basic information about files in a directory tree.
use File::Find;
my $dir_name = 'some_directory';
my @files;
find(sub {push @files, $File::Find::name}, $dir_name);
Print_file_info(
    $_,
    -f $_ ? 'file' : 'other',
    -s,
    (stat)[9],
) for @files;
person FMc    schedule 13.08.2009
comment
@FM AFAIK, Archive::Tar->new нужно сказать, что файл сжат. - person Sinan Ünür; 13.08.2009
comment
@Синан Унур. Хорошая точка зрения; вот как я тоже читаю документацию. Однако я только что протестировал $ft->get_content в приведенном выше сценарии, и он вернул правильное содержимое, даже без добавления флага сжатия (в окне Windows). На данный момент я не уверен, так или иначе... звучит как хороший вопрос для ТАК. - person FMc; 13.08.2009
comment
@FM А-ха! Глядя на исходный код, кажется, что флаг $compressed используется для вывода Arcive::Tar, тогда как внутренний _get_handle определяет, сжат ли файл. - person Sinan Ünür; 14.08.2009
comment
@Синан Унур. Хорошо знать. Спасибо. - person FMc; 14.08.2009

На самом деле Perl для этого как бы излишество. Сценарий оболочки подойдет. Шаги, которые вам необходимо предпринять:

  • Извлеките tar во временную папку куда-нибудь.
  • diff -uR две папки и куда-то перенаправить вывод (или, возможно, направить в less, если это необходимо)
  • Очистите временную папку.

И вы сделали. Не более 5-6 строк. Что-то быстрое и непроверенное:

#!/bin/sh
mkdir $TEMP/$$
tar -xz -f ../backups/backup.tgz $TEMP/$$
diff -uR $TEMP/$$ ./ | less
rm -rf $TEMP/$$
person Matthew Scharley    schedule 13.08.2009
comment
Я не хочу создавать какую-либо папку. Нет ли способа прочитать файлы из .tar.gz, добавить хэш и сравнить. - person Space; 13.08.2009
comment
зачем извлекать и сравнивать с помощью diff.. почему бы не сжимать, а затем сравнивать с помощью zdiff.. это должно занимать меньше места, хотя я не уверен, как работает zdiff, но просто любопытно :) - person sud03r; 13.08.2009
comment
Я думаю, что zdiff будет работать только для файлов, но у меня есть каталоги в файле .tar.gz. - person Space; 13.08.2009
comment
Я не хочу создавать папку. Не хочу или не могу? Относительно сложно сделать то, что вы описываете, конечно, это нетривиально и выходит за рамки всего, что кто-либо захочет сделать для вас здесь. - person Matthew Scharley; 13.08.2009
comment
Кроме того, tar (с точки зрения файловой структуры) на самом деле не заботится о файлах/каталогах, кроме идеи о том, что файлы в подкаталогах имеют очень длинные имена с '/' в них. - person Matthew Scharley; 13.08.2009
comment
Примечание о zdiff... хотя он (вероятно) будет работать правильно, будет трудно сказать, в каких файлах на самом деле произошли изменения (если только zdiff не имеет переключателей для работы с файлами tar, что возможно, я никогда не использовал его до). - person Matthew Scharley; 13.08.2009
comment
Чтобы сделать это без распаковки архива, вы можете использовать Archive::Tar, циклически перебирать каждый член архива, а затем сравнивать его с существующим файлом на диске способом, зависящим от типа файла (сравнивая содержимое и, возможно, время для обычные файлы, readlink для символических ссылок, просмотр stat информации для специальных устройств и т. д.) Это не идеальная задача для новичка. Да, и Archive::Tar не знает, как передать файл с диска; он загружает все данные в память. Я думаю, что низкотехнологичное решение diff выигрывает. Вы можете помочь себе, поместив /tmp в файл tmpfs. - person hobbs; 13.08.2009

Вот пример, который проверяет, существует ли каждый файл в архиве в папке.

# $1 is the file to test
# $2 is the base folder
for file in $( tar --list -f $1 | perl -pe'chomp;$_=qq["'$2'$_" ]' )
do
  # work around bash deficiency
  if [[ -e "$( perl -eprint$file )" ]]
    then
      echo "   $file"
    else
      echo "no $file"
  fi
done

Вот как я тестировал это:

Я удалил/переименовал config, затем выполнил следующее:

bash test Downloads/update-dnsomatic-0.1.2.tar.gz Downloads/

Что дало результат:

   "Downloads/update-dnsomatic-0.1.2/"
no "Downloads/update-dnsomatic-0.1.2/config"
   "Downloads/update-dnsomatic-0.1.2/update-dnsomatic"
   "Downloads/update-dnsomatic-0.1.2/README"
   "Downloads/update-dnsomatic-0.1.2/install.sh"

Я новичок в программировании bash/shell, поэтому, вероятно, есть лучший способ сделать это.

person Brad Gilbert    schedule 13.08.2009