【问题标题】:How to check if one file is part of other?如何检查一个文件是否是另一个文件的一部分?
【发布时间】:2015-10-11 00:23:28
【问题描述】:

我需要通过 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 可以解决这个问题?

【问题讨论】:

  • 您是想查明文件中是否已经存在任何给定行,或者整个新行集是否已经作为单个行块存在于文件中?
  • 我想检查输入文件中是否存在完整模式(作为单个行块)。
  • 您可能需要更新您的问题,以便更早地说明这与忽略换行符的子字符串匹配略有不同。因为正如您的...\n*222\n333\n... 不匹配大小写所示,您需要对块进行模式匹配以从行首开始匹配,并在行尾结束。

标签: linux bash command-line pcregrep


【解决方案1】:

我只会使用diff 来完成这项任务:

diff pattern <(grep -f file pattern)

说明

  • diff file1 file2 报告两个文件是否不同。

  • 通过说grep -f file pattern,您可以看到pattern 的内容在file 中。

所以你要做的是检查pattern 中的哪些行在file 中,然后将其与pattern 本身进行比较。如果它们匹配,则意味着patternfile 的子集!

测试

seq 10seq 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

【讨论】:

  • 效果不错!但它不注意行的顺序(in pattern),每行的开头和结尾可能会有额外的字符。几乎,我想要达到的目标。
  • @user51390233 那么顺序重要吗?使用一些更相关的示例输入更好地更新您的问题。否则我的答案只是猜测。
  • @user51390233 我根据你提到的文件检查了我的 sn-p,它适用于所有情况。
  • 你是对的。在行首或行尾添加字符不通过(return 1),但不遵守行的顺序(return 0 示例模式:“222\ n333" 文件:"333\n222")。
  • 将命令的参数顺序更改为:diff pattern &lt;(grep -f pattern file) 可防止错误的行顺序,但不处理输入文件中的额外行,例如:pattern: "222\n333"文件:“222\n555\n333”。
【解决方案2】:

我有一个使用 perl 的工作版本。

我以为我可以使用 GNU awk,但我没有。 RS=空字符串在空行上拆分。查看损坏的 awk 版本的编辑历史记录。

How can I search for a multiline pattern in a file? 展示了如何使用 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);

请参阅 What is the best way to slurp a file into a string in Perl? 以避免对 File::Slurp 的依赖(它不是标准 perl 发行版或默认 Ubuntu 15.04 系统的一部分)。我选择 File::Slurp 的部分原因是为了让非 perl-geek 的程序更易读,对比:

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 可能是一个安全的选择,不会与文件或模式中已有的任何内容发生冲突。)

【讨论】:

  • 谢谢,这对我来说非常有用。我看到awk非常强大。但这仅适用于没有空行的 target filename (通过将 RS="" / 读取目标文件仅设置为空行)。不知道怎么升级。
  • 哦,打扰了,我误读了手册页。你是对的,RS="" 仍然在空白行上分裂,所以它不是一个啜饮。那么,perl 可能是要走的路。
  • 这个 perl 版本应该可以工作。我在几个输入上测试了它,模式为“222\n\n333\n”。逻辑非常简单,不会将任何输入视为行,只是字符。
  • 是的,您有权在我的脚本中省略 echo。谢谢。
  • 你的 perl 脚本看起来不错。但是我是 perl 的新手,所以我遇到了一些我无法处理的基本问题,例如 IO::Handle: bad open mode: O_RDONLY。我将花点时间熟悉这种语言。我鼓励将输入视为字符的方法,这是个好方法。
【解决方案3】:

我又遇到了这个问题,我认为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

这个想法是: - 在数组a[line_number] = line 中读取内存中的所有文件file。 - 计算数组中的元素。 - 遍历文件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

【讨论】:

  • 谢谢,这很有帮助。我可以使用它。但这不是行注入证明,这意味着有人可以在输入文件中添加一些行(在模式出现内部)。它可能需要多次读取文件模式(但我不知道如何,close() 对我不起作用),或者将它作为 file 加载到变量中。
  • 嗯不知道你的意思。我猜你是在问这个问题,这是一个更大问题的一部分。也许澄清一下,这样我就可以更好地回答我的答案。展示一些反例也会有所帮助。顺便说一句,这是一个有趣的问题:)
  • @user51390233:这个 awk 脚本只读取目标文件一次(在 FNR == NR {next} 块中。next 跳过检查该行的其他块)。然后它通过模式文件一次,与其他两个块。
  • @fedorqui:计算a[i] 长度的第二个块可以替换为len = length(a)。 (GNU 扩展)。对于 POSIX,len = FNR 在第一个块中,不会对第二个文件执行。
  • @fedorqui:我认为这允许按顺序匹配所有行,但混合其他行。我认为 OP 想要的是 strstrmemmem 样式搜索,以换行符为锚,根本不是基于行的东西。就像我在回答中试图做的那样。
猜你喜欢
  • 2020-04-30
  • 1970-01-01
  • 2012-10-08
  • 2012-06-03
  • 1970-01-01
  • 1970-01-01
  • 2021-02-16
  • 2017-06-26
  • 1970-01-01
相关资源
最近更新 更多