bash查找以字符串开头的行


10

我有一堆文件,我想找到其中一个包含以某个字符串开头的连续行的文件。

例如以下文件:

Aaaaaaaaaaaa
Baaaaaaaaaaa
Cxxxxxxxxx
Cyyyyyyyyy
Czzzzzzzzz
Abbbbbbbbbbb
Bbbbbbbbbbbb
Caaaaaa
Accccccccccc
Bccccccccccc
Cdddddd
Ceeeeee

有多个以'C'开头的行,因此我希望通过命令找到该文件。
例如以下文件:

Aaaaaaaaaaaa
Baaaaaaaaaaa
Cxxxxxxxxx
Abbbbbbbbbbb
Bbbbbbbbbbbb
Caaaaaa
Accccccccccc
Bccccccccccc
Cdddddd

总会有一行以'C'开头,我不要这个文件。我想到使用a grep或a,sed但我不知道该怎么做。也许使用正则表达式^C.*$^C或类似的东西。任何的想法 ?


C在第二个示例中,从两行开始。
cuonglm 2014年

5
这个问题尚不清楚。您是否要查找以开头的连续行多于一个的文件C
Graeme 2014年

是的,这就是我想要的。很抱歉对于这个误会。
杰米2014年

2
@terdon,看来-P的多行搜索在2.5.4之前有效,此后不再可用,尽管我在变更日志中找不到任何可以解释原因的信息。
斯特凡Chazelas

1
@Graeme您可能想取消删除答案,请参阅Stephane的评论,显然它确实适用于某些较旧的grep版本。
terdon

Answers:


5

pcregrep

pcregrep -rMl '^C.*\nC' .

POSIXly:

find . -type f -exec awk '
  FNR==1 {last=0; printed=0; next}
  printed {next}
  /^C/ {if (last) {print FILENAME; printed=1; nextfile} else last=1; next}
  {last=0}' {} +

(尽管这意味着使用awk不支持的实现完全读取所有文件nextfile)。


对于GNU grep2.5.4以下的版本:

grep -rlP '^C.*\nC' .

似乎可以工作,但这是偶然的,不能保证能工作。

在2.6中(通过commit修复)之前,GNU grep忽略了它使用的pcre搜索功能将与当前正在处理的整个缓冲区匹配grep,从而导致各种令人惊讶的行为。例如:

grep -P 'a\s*b'

与包含以下内容的文件匹配:

bla
bla

这将匹配:

printf '1\n2\n' | grep -P '1\n2'

但是这个:

(printf '1\n'; sleep 1; printf '2\n') | grep -P '1\n2'

要么:

(yes | head -c 32766; printf '1\n2\n') > file; grep -P '1\n2' file

不会(因为1\n2\n跨两个缓冲区由处理grep)。

该行为最终被记录了下来:

15-如何跨线匹配?

标准grep无法执行此操作,因为它基本上是基于行的。因此,仅使用'[:space:]'字符类并不符合您期望的换行符。但是,如果您的grep是在启用Perl模式的情况下编译的,则可以使用Perl的s修饰符(使。匹配换行符):

     printf 'foo\nbar\n' | grep -P '(?s)foo.*?bar'

在2.6中修复该文件后,该文档未作修改(我曾经在那里报告过)。


有什么理由不使用exit-exec \;不是使用nextfile吗?
terdon

@terdon,这意味着awk每个文件运行一个。仅在awk不支持nextfile并且文件很大且文件开头有匹配行的情况下,才需要这样做。
斯特凡Chazelas

这个怎么样的grep技术(我猜有较新版本的GNU的grep的),有利于通过使整个文件看起来像行终止设置为NUL一个字符串匹配的多-你会知道,如果有它的任何限制?
iruvar

1
@ 1_CR,如果其中没有NUL字符,则将整个文件加载到内存中,并且假定行中不包含NUL字符。另外请注意,较早版本的GNU grep(OP拥有)不能-z与一起使用-P。有没有\N没有-P,你需要写$'[\01-\011\013-\0377]'这将只在C语言环境中工作(见thread.gmane.org/gmane.comp.gnu.grep.bugs/5187
斯特凡Chazelas

@StephaneChazelas,非常有用的细节,谢谢
iruvar

2

awk

awk '{if (p ~ /^C/ && $1 ~ /^C/) print; p=$1}' afile.txt

如果以开头的连续行,将打印文件的内容C。该表达式(p ~ /^C/ && $1 ~ /^C/)将查找文件中的连续行,并且如果两个中的第一个字符都匹配,则该表达式的值为true C。在这种情况下,将打印该行。

为了找到所有具有这种模式的文件,您可以通过以下find命令运行上述awk :

find /your/path -type f -exec awk '{if (p ~ /^C/ && $1 ~ /^C/) {print FILENAME; exit;} p=$1}' {} \;

在此命令中,find+ exec将遍历每个文件,awk并对每个文件执行类似的过滤,并通过FILENAMEawk表达式评估为true来打印其名称。为了避免FILENAME对具有多个匹配项的单个文件多次打印该exit语句(感谢@terdon)。


我的问题是不够清楚,我想知道这些文件的名称与一个以上的连续行开始C
热雷米

@Jérémie我更新了答案。
mkc 2014年

您能补充一下它的工作原理吗?另外,也不需要flagexit而是。这样,您无需在找到匹配项后继续处理文件。
terdon

2

GNU的另一个选择sed

对于单个文件:

sed -n -- '/^C/{n;/^C/q 1}' "$file" || printf '%s\n' "$file"

(尽管它也会报告无法读取的文件)。

对于find

find . -type f ! -exec sed -n '/^C/{n;/^C/q 1}' {} \; -print

可以通过编写以下文件来避免打印不可读文件的问题:

find . -type f -size +2c -exec sed -n '$q1;/^C/{n;/^C/q}' {} \; -print

您能详细说明一下sed -n '$q1;/^C/{n;/^C/q}'吗?
热雷米

有人来解释我吗?
杰里米

@Jérémie- $q1如果未找到模式,则强制sed错误退出。如果文件有问题(无法读取或损坏),也会以错误结束。因此,只有在找到模式的情况下,它将以0退出状态退出,并将其传递给打印。部分/^C/{n;/^C/q非常简单。如果找到以C开头的字符串,则将读取下一行;如果也以C开头,则退出状态为零。
2014年

1

假设您的文件足够小,可以读取到内存中:

perl -000ne 'print "$ARGV\n" if /^C[^\n]*\nC/sm' *

说明:

  • - 000:设置\n\n为记录分隔符,这将启用段落模式,该模式会将段落(由连续的换行符分隔)视为单行。
  • -ne:将作为参数提供的脚本应用于-e输入文件的每一行。
  • $ARGV :当前正在处理的文件
  • /^C[^\n]*\nC/C在行的开头匹配(请参见sm下面的修饰符说明,以了解其在此处的作用),后跟0个或多个非换行符,一个换行符和另一个C。换句话说,找到以开头的连续行C。* //sm:这些匹配修饰符是(如[此处]所述):

    • m:将字符串视为多行。也就是说,将“ ^”和“ $”从仅在字符串的左右两端匹配行的开头或结尾,改为在字符串中的任意位置匹配它们。

    • s:将字符串视为单行。即,更改“。” 可以匹配任何字符,甚至是换行符(通常不会匹配)。

您也可以做一些丑陋的事情:

for f in *; do perl -pe 's/\n/%%/' "$f" | grep -q 'C[^%]*%%C' && echo "$f"; done

在这里,perl代码将用%%这样的方式替换换行符,假设您%%在输入文件中没有换行符(如果为大则为大),grep将匹配以开头的连续行C


1

解:

( set -- *files ; for f ; do (
set -- $(printf %c\  `cat <$f`)
while [ $# -ge 1 ] ;do [ -z "${1#"$2"}" ] && {
    echo "$f"; break ; } || shift
done ) ; done )

演示:

首先,我们将创建一个测试基础:

abc="a b c d e f g h i j k l m n o p q r s t u v w x y z" 
for l in $abc ; do { i=$((i+1)) h= c= ;
    [ $((i%3)) -eq 0 ] && c="$l" h="${abc%"$l"*}"
    line="$(printf '%s ' $h $c ${abc#"$h"})"
    printf "%s$(printf %s $line)\n" $line >|/tmp/file${i}
} ; done

上面在/tmpnamed中创建了26个文件file1-26在每个文件中,有27或28行以字母开头a-z,然后是其余字母。每个第3个文件包含两个连续的行,其中第一个字符重复。

样品:

cat /tmp/file12
...
aabcdefghijkllmnopqrstuvwxyz
babcdefghijkllmnopqrstuvwxyz
cabcdefghijkllmnopqrstuvwxyz
...
kabcdefghijkllmnopqrstuvwxyz
labcdefghijkllmnopqrstuvwxyz
labcdefghijkllmnopqrstuvwxyz
mabcdefghijkllmnopqrstuvwxyz
...

当我改变时:

set -- *files

至:

set -- /tmp/file[0-9]*

我知道了

输出:

/tmp/file12
/tmp/file15
/tmp/file18
/tmp/file21
/tmp/file24
/tmp/file3
/tmp/file6
/tmp/file9

因此,简单来说,解决方案的工作方式如下:

sets子外壳位置到您所有文件的位置

setş嵌套子shell的positionals到每一行中因为它可以将每个文件的第一个字母。

[ tests ]如果$1否定$2表示匹配,则为

echoes文件名然后break当前循环迭代

其他shift小号到下一个字符位置再试一次


0

该脚本使用grepcut获取匹配行的行号,并检查是否有两个连续的数字。假定该文件是作为脚本的第一个参数传递的有效文件名:

#!/bin/bash

checkfile () {
 echo checking $1
 grep -n -E "^C.*$" $1 | cut -d: -f1 | while read linenum
     do
        : $[ ++PRV ] 
        if [ $linenum == $PRV ]; then return 1; fi
        PRV=$linenum
     done
     return 0
}

PRV="-1"
checkfile $1
if [ $? == 0 ]; then
   echo Consecutive matching lines found in file $1
fi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.