Как проверить, является ли один файл частью другого?

Мне нужно проверить, если один файл находится внутри другого файла с помощью bash-скрипта. Для данного многострочного шаблона и входного файла.

Возвращаемое значение:

Я хочу получить статус (как в команде grep) 0, если совпадений найдено, 1, если совпадений не найдено.

Шаблон:

  • многострочный,
  • важен порядок строк (рассматривается как один блок строк),
  • включает такие символы, как цифры, буквы,?, &, *, # и т. д.,

объяснение

Только следующие примеры должны найти совпадения:

pattern     file1 file2 file3 file4
222         111   111   222   222
333         222   222   333   333
            333   333         444
            444

следующее не должно:

pattern     file1 file2 file3 file4 file5 file6 file7
222         111   111   333   *222  111   111   222
333         *222  222   222   *333  222   222   
            333   333*        444   111         333
            444                     333   333 

Вот мой сценарий:

#!/bin/bash

function writeToFile {
    if [ -w "$1" ] ; then
        echo "$2" >> "$1"
    else
        echo -e "$2" | sudo tee -a "$1" > /dev/null
    fi
}

function writeOnceToFile {
        pcregrep --color -M "$2" "$1"
        #echo $?

        if [ $? -eq 0 ]; then
            echo This file contains text that was added previously
        else
            writeToFile "$1" "$2"
        fi
}

file=file.txt 
#1?1
#2?2
#3?3
#4?4

pattern=`cat pattern.txt`
#2?2
#3?3

writeOnceToFile "$file" "$pattern"

Я могу использовать команду grep для всех строк шаблона, но в этом примере это не получается:

file.txt 
#1?1
#2?2
#=== added line
#3?3
#4?4

pattern.txt
#2?2
#3?3

или даже если вы измените строки: 2 с 3

file=file.txt 
#1?1
#3?3
#2?2
#4?4

возвращая 0, когда это не должно быть.

Как я могу это исправить? Обратите внимание, что я предпочитаю использовать собственные установленные программы (если это может быть без pcregrep). Может быть, sed или awk могут решить эту проблему?

3 ответа

Решение

У меня есть рабочая версия с использованием Perl.

Я думал, что это работает с GNU awk, но я не сделал. RS= пустая строка разбивается на пустые строки. Смотрите историю изменений для сломанной версии awk.

Как мне найти многострочный шаблон в файле? показывает, как использовать pcregrep, но я не вижу способа заставить его работать, когда шаблон для поиска может содержать специальные символы регулярного выражения. -F режим с фиксированными строками бесполезно работает с многострочным режимом: он по-прежнему рассматривает шаблон как набор строк, которые должны сопоставляться отдельно. (Не как многострочную фиксированную строку для сопоставления.) Я вижу, что вы уже использовали pcregrep в своей попытке.

Кстати, я думаю, что у вас есть ошибка в вашем коде в случае не sudo:

function writeToFile {
    if [ -w "$1" ] ; then
        "$2" >> "$1"   # probably you mean  echo "$2" >> "$1"
    else
        echo -e "$2" | sudo tee -a "$1" > /dev/null
    fi
}

В любом случае, попытки использовать линейные инструменты потерпели неудачу, поэтому пришло время вытащить более серьезный язык программирования, который не навязывает нам соглашение о новой строке. Просто прочитайте оба файла в переменные и используйте поиск без регулярных выражений:

#!/usr/bin/perl -w
# multi_line_match.pl  pattern_file  target_file
# exit(0) if a match is found, else exit(1)

#use IO::File;
use File::Slurp;
my $pat = read_file($ARGV[0]);
my $target = read_file($ARGV[1]);

if ((substr($target, 0, length($pat)) eq $pat) or index($target, "\n".$pat) >= 0) {
    exit(0);
}
exit(1);

См. Каков наилучший способ поместить файл в строку в Perl? чтобы избежать зависимости от File::Slurp (который не является частью стандартного дистрибутива Perl или системы Ubuntu 15.04 по умолчанию). Я выбрал File::Slurp отчасти для удобства чтения программы для не-perl-гиков по сравнению с:

my $contents = do { local(@ARGV, $/) = $file; <> };

Я работал над тем, чтобы избежать чтения полного файла в память, с идеей из http://www.perlmonks.org/?node_id=98208. Я думаю, что несовпадающие случаи обычно все равно читают весь файл одновременно. Кроме того, логика была довольно сложной для обработки совпадения в начале файла, и я не хотел тратить много времени на тестирование, чтобы убедиться, что оно корректно для всех случаев. Вот что у меня было до того, как я сдался:

#IO::File->input_record_separator($pat);
$/ = $pat;  # pat must include a trailing newline if you want it to match one

my $fh = IO::File->new($ARGV[2], O_RDONLY)
    or die 'Could not open file ', $ARGV[2], ": $!";

$tail = substr($fh->getline, -1);  #fast forward to the first match
#print each occurence in the file
#print IO::File->input_record_separator  while $fh->getline;

#FIXME: something clever here to handle the case where $pat matches at the beginning of the file.
do {
    # fixme: need to check defined($fh->getline)
    if (($tail eq '\n') or ($tail = substr($fh->getline, -1))) {
    exit(0);  # if there's a 2nd line
    }
} while($tail);

exit(1);
$fh->close;

Другая идея состояла в том, чтобы отфильтровать шаблоны и файлы для поиска tr '\n' '\r' или что-то, так что все они будут однострочными. (\r быть вероятным безопасным выбором, который не столкнется с чем-либо, уже находящимся в файле или шаблоне.)

Я бы просто использовал diff для этой задачи:

diff pattern <(grep -f file pattern)

объяснение

  • diff file1 file2 сообщает, если два файла отличаются или нет.

  • Говоря grep -f file pattern вы видите, какое содержание pattern в file,

Так что вы делаете, чтобы проверить, какие строки из pattern находятся в file а затем сравнивая это с pattern сам. Если они совпадают, это означает, что pattern это подмножество file!

тесты

seq 10 это часть seq 20! Давайте проверим это:

$ diff <(seq 10) <(grep -f <(seq 20) <(seq 10))
$

seq 10 не совсем внутри seq 2 20 (1 не во втором):

$ diff -q <(seq 10) <(grep -f <(seq 2 20) <(seq 10))
Files /dev/fd/63 and /dev/fd/62 differ

Я снова прошел через проблему, и я думаю, awk может справиться с этим лучше:

awk 'FNR==NR {a[FNR]=$0; next}
     FNR==1 && NR>1 {for (i in a) len++}
     {for (i=last; i<=len; i++) {
         if (a[i]==$0) 
            {last=i; next}
     } status=1}
     END {print status+0}' file pattern

Идея такова: - прочитать весь файл file в памяти в массиве a[line_number] = line, - Подсчитать элементы в массиве. - цикл по файлу pattern и проверьте, происходит ли текущая строка в file в любое время между тем, где находится курсор, и концом файла file, Если он совпадает, переместите курсор в положение, где он был найден. Если это не так, установите статус 1 - есть строка в pattern что не произошло в file после предыдущего матча. - распечатать статус, который будет 0 если не было установлено 1 в любое время раньше.

Тестовое задание

Они соответствуют:

$ tail f p
==> f <==
222
333
555

==> p <==
222
333
$ awk 'FNR==NR {a[FNR]=$0; next} FNR==1 && NR>1{for (i in a) len++} {for (i=last; i<=len; i++) {if (a[i]==$0) {last=i; next}} status=1} END {print status+0}' f p
0

Они не

$ tail f p
==> f <==
333
222
555

==> p <==
222
333
$ awk 'FNR==NR {a[FNR]=$0; next} FNR==1 && NR>1{for (i in a) len++} {for (i=last; i<=len; i++) {if (a[i]==$0) {last=i; next}} status=1} END {print status+0}' f p
1

С seq:

$ awk 'FNR==NR {a[FNR]=$0; next} FNR==1 && NR>1{for (i in a) len++} {for (i=last; i<=len; i++) {if (a[i]==$0) {last=i; next}} status=1} END {print status+0}' <(seq 2 20) <(seq 10)
1
$ awk 'FNR==NR {a[FNR]=$0; next} FNR==1 && NR>1{for (i in a) len++} {for (i=last; i<=len; i++) {if (a[i]==$0) {last=i; next}} status=1} END {print status+0}' <(seq 20) <(seq 10)
0
Другие вопросы по тегам