Как проверить, является ли один файл частью другого?
Мне нужно проверить, если один файл находится внутри другого файла с помощью 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