Как вставить только новые и / или обновленные строки в другой файл
Первые дни имеем дело с Perl и уже заблокированы:)
Вот ситуация: файл обновляется в папке A, но также существует в папках B, C & D, и, чтобы упростить его, он может отличаться во всех из них, поэтому я не могу просто сделать diff. Новые строки, предназначенные для копирования в другие файлы, помечаются флагом, например, #I, в конце строки.
Файл перед обновлением выглядит так:
First line
Second line
Fifth line
После обновления это выглядит так:
First line
Second line
Third line #I
Fourth line #I
Fifth line
Sixth line #I
Что мне нужно сделать, так это искать "вторую строку" в других файлах, вставлять строки, помеченные #I - в том порядке, в котором они были вставлены - затем искать "пятую строку" и вставлять "шестую строку #I",
В этом примере они все последовательны, но в файлах, которые мне нужно обновить, может быть несколько строк между первым блоком обновления и вторым (и третьим, и т. Д. И т. Д.).
Файлы, которые будут обновлены, могут быть sh-скриптами, awk-скриптами, текстовыми файлами и т. Д., Скрипт должен быть универсальным. Скрипт будет иметь два параметра ввода: обновленный файл и файл, который будет обновлен.
Любые советы о том, как это сделать, приветствуются. Я могу предоставить код, который у меня пока есть - близко, но пока не работает - при необходимости.
Спасибо,
João
PS: вот что у меня пока
# Pass the content of the file $FileUpdate to the updateFile array
@updateFile = <UPD>;
# Pass the content of the file $FileOriginal to the originalFile array
@originalFile = <ORG>;
# Remove empty lines from the array contained on the updated file
@updateFile = grep(/\S/, @updateFile);
# Create an array that will contain the modifications and the line
# prior to the first modification.
@modifications = ();
# Counter initialization
$i = 0;
# Loop the array to find out which lines are flagged as new and
# which lines immediately precede those
foreach $linha (@updateFile) {
# Remove \n characters
chomp($linha);
# Find the new lines flagged with #I
if ($linha =~ m/#I$/) {
# Verify that the previous line is not flagged as updated.
# If it is not, it means that the update starts here.
unless ($updateFile[$i-1] =~ m/#I$/) {
print "Line where the update starts $updateFile[$i-1]\n";
# Add that line to the array modifications
push(@modifications, $updateFile[$i-1]);
} # END OF unless
print "$updateFile[$i]\n";
# Add the lines tagged for insertion into the array
push(@modifications, $updateFile[$i]);
} # END OF if ($linha =~ m/#I$/)
# Increment the counter
$i = $i + 1;
} # END OF foreach $linha (@updateFile)
foreach $modif (@modifications) {
unless ($modif =~ m/#I$/) {
foreach $original (@originalFile) {
chomp($original);
if ($original ne $modif) {
push (@newOriginal, $originalFile[$n]);
}
elsif ($original eq $modif) { #&& $modif[$n+1] =~ m/#I$/) {
push (@newOriginal, $originalFile[$n]);
last;
}
$n = $n + 1;
}
}
if ($modif =~ m/#I$/) {
push (@newOriginal, $modifications[$m]);
}
$m = $m + 1;
}
Полученный результат - почти тот, который я хочу, но пока нет.
2 ответа
Я наконец смог вернуться к этой проблеме, и, кажется, я смог решить эту проблему. Вероятно, не лучшее решение или "самое красивое", но то, что делает то, что мне нужно:) .
# Open the file
# First parameter is the file containing the update
my ($FileUpdate) = $ARGV[0];
# Second parameter is the file to be updated
my ($FileOriginal) = $ARGV[1];
# \s whitespace characters
# Open both files and give them handles to be referred to further ahead
open(UPD, $FileUpdate) || die("Could not open file $FileUpdate!");
open(ORG, $FileOriginal) || die("Could not open file $FileOriginal!");
# ------------------------------------------------ #
# ---------------- ARRAY CREATION ---------------- #
# ------------------------------------------------ #
# Pass the content of the file $FileUpdate to the updateFile array
@updateFile = <UPD>;
# Pass the content of the file $FileOriginal to the originalFile array
@originalFile = <ORG>;
# Remove empty lines from the array contained on the updated file
@updateFile = grep(/\S/, @updateFile);
# Create an array that will contain the modifications and the line
# prior to the first modification.
@modifications = ();
# Counter initialization
$i = 0;
# ------------------------------------------------ #
# ----- LOOP TO IDENTIFY LINES FOR INSERTION ----- #
# ------------------------------------------------ #
# Loop the array to find out which lines are flagged as new and
# which lines immediately precede those
foreach $linha (@updateFile) {
# Remove \n characters
chomp($linha);
# Find the new lines flagged with #I
if ($linha =~ m/#I$/) {
# Verify that the previous line is not flagged as updated.
# If it is not, it means that the update starts here.
unless ($updateFile[$i-1] =~ m/#I$/) {
# Add that line to the array modifications
push(@modifications, $updateFile[$i-1]);
} # END OF unless
# Add the lines tagged for insertion into the array
push(@modifications, $updateFile[$i]);
} # END OF if ($linha =~ m/#I$/)
# Increment the counter
$i = $i + 1;
} # END OF foreach $linha (@updateFile)
# ------------------------------------------------ #
# --------- ADD VALUES TO MODIFICATIONS --------- #
# ------------------------------------------------ #
foreach $valor (@modifications) {
print "$valor\n";
}
# ------------------------------------------------ #
# -------------------- BACKUP -------------------- #
# ------------------------------------------------ #
# Make a backup copy from the original file
# in case something goes wrong when updating it
# Obtain the current time
$tt=localtime();
use POSIX qw(strftime);
$tt = strftime "%Y%m%d-%H%M\n", localtime;
system("cp $FileOriginal $FileOriginal.$tt");
# ------------------------------------------------ #
# ------------- INSERT THE NEW LINES ------------- #
# ------------------------------------------------ #
# Counter initialization
$m = 0;
# New file array
@newOriginal = ();
# Goes through the original file and for each line not present in modifs, writes it .
foreach $original (@originalFile) {
# Initialize counter
$n = 0;
# Remove spaces
chomp ($original);
# Check if the value already exists on the array
# If it doesnt, adds it
if (grep {$_ eq $original} @newOriginal) {
}
else {
push (@newOriginal, $originalFile[$m]);
}
# Iterate over the array containing the modifications
# These new lines shall be added to the final file.
foreach $modif (@modifications) {
# Remove spaces
chomp ($modif);
#print "Original: $original, Modif: $modif\n";
# Initialize counter
$k = 0;
# Compare the current value from the original file with
# the elements that exist on the modifications array.
# If they are equal push that line in order to be added
# to the results file.
if ($original eq $modif) {
# Increment the counter
$k = $n+1;
# Iterate the array with the modifications
# in order to insert all lines that end with #I
# immediately after the common line between files.
foreach my $igual ($k..$#modifications) {
# Remove spaces
chomp($igual);
# If the line ends with #I add it to the final file.
if ($modifications[$igual] =~ m/#I$/) {
foreach $newO (@newOriginal) {
# Remove spaces
chomp($newO);
if ($newO ne $modifications[$igual]) {
push (@newOriginal, $modifications[$igual]);
last;
}
}
}
else {
last;
}
}
}
# Increment the counter
$n = $n + 1;
}
# Increment the counter
$m = $m + 1;
}
# ------------------------------------------------ #
# ------------- RESULTS PRESENTATION ------------- #
# ------------------------------------------------ #
$v = 0;
print "--------------------\n";
foreach $vl (@newOriginal) {
print "newOriginal: $newOriginal[$v]\n";
$v = $v + 1;
}
print "--------------------\n";
# ------------------------------------------------ #
# ------------- CREATE UPDATED FILE -------------- #
# ------------------------------------------------ #
$v = 0;
# Create the new name for the file - only for testing purposes now, it will be the original name afterwards
$NewFileToWriteTo = $FileOriginal;
# Retrieve the extension of the file to be updated
my ($ext) = $FileOriginal =~ /(\.[^.]+)$/;
# Remove the extension - just for testing purposes because I want to change the file name now
$NewFileToWriteTo =~ s/$ext//;
# Create the new file name by adding the suffix _tst and the correct extension to it.
$NewFileToWriteTo = $NewFileToWriteTo . '_tst' . ${ext};
# Create the new file or die in case it is not possible to open it
open DAT, ">$NewFileToWriteTo" or die("Could not open file!");
# Write to the new file. This will be the UPDATED version of the ORIGINAL file.
foreach $vl (@newOriginal) {
print DAT "$newOriginal[$v]\n";
$v = $v + 1;
}
# Close all files
close(DAT);
close(UPD);
close(ORG);
ОК, я понимаю, что вам нужно, и программа ниже реализует решение.
Мне не совсем понятно, как выглядят исходные (B, C, D) файлы, но я предполагаю, что они совпадают с целевым (A) файлом в его обновленном состоянии в вашем вопросе.
Я столкнулся с еще одним крайним случаем: что, если первая строка исходных файлов (B, C, D) помечена #I
? Я предположил, что это должно быть вставлено в начале вывода.
Я также решил die
если предыдущая строка в исходном файле не найдена в цели.
Дайте нам знать, если это правильно.
use strict;
use warnings;
open my $fa, '<', 'A.txt' or die $!;
open my $fb, '<', 'B.txt' or die $!;
my $keyline;
my $inserting;
while (<$fb>) {
if (/#I$/) {
if ($keyline) { # We have to search for a match
while () {
my $source = <$fa>; # read from the target
if (defined $source) { # copy to output. stop reading if key is found
print $source;
last if $source eq $keyline;
}
else { # die if key nowhere in target
chomp $keyline;
die qq(Key Line "$keyline" not found);
}
}
undef $keyline; # don't have to search next time
}
print; # insert the new line
}
else {
$keyline = $_; # remember the line to search for
}
}