bash 查找以字符串开头的行

bash 查找以字符串开头的行

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

例如对于以下文件:

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

以“C”开头的行不止一行,所以我希望通过命令找到该文件。
例如对于以下文件:

Aaaaaaaaaaaa
Baaaaaaaaaaa
Cxxxxxxxxx
Abbbbbbbbbbb
Bbbbbbbbbbbb
Caaaaaa
Accccccccccc
Bccccccccccc
Cdddddd

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

答案1

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 版本grep最高可达 2.5.4:

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

出现工作,但这是偶然的,并且不能保证工作。

在 2.6 中修复之前(由这次提交),GNUgrep忽略了它正在使用的 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修复后,文档没有修改(我曾经举报过那里)。

答案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}' {} \;

在此命令中,+findexec遍历每个文件并对每个文件执行类似的过滤,并在awk 表达式计算结果为 true 时awk打印其名称。FILENAME为了避免FILENAME对具有多个匹配项的单个文件进行多次打印exit,使用了该语句(感谢@terdon)。

答案3

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

答案4

解决方案:

( 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

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

样本:

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所有文件的子 shell 位置,以及每个

sets嵌套子shell在循环时到每个文件中每行的第一个字母的位置。

[ tests ]如果$1否定$2表示匹配,如果是的话

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

别的shifts到下一个单个字符位置再试一次

相关内容