我有一堆文件,我想找到哪个文件包含以某个字符串开头的连续行。
例如对于以下文件:
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}' {} \;
在此命令中,+find
将exec
遍历每个文件并对每个文件执行类似的过滤,并在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
简而言之,解决方案的工作原理如下:
set
s所有文件的子 shell 位置,以及每个
set
s嵌套子shell在循环时到每个文件中每行的第一个字母的位置。
[ tests ]
如果$1
否定$2
表示匹配,如果是的话
echoes
然后是文件名break
s当前循环迭代别的
shift
s到下一个单个字符位置再试一次