bash tìm dòng bắt đầu bằng chuỗi


10

Tôi có một loạt các tệp và tôi muốn tìm cái nào chứa các dòng liên tiếp bắt đầu bằng một chuỗi nhất định.

Ví dụ cho tệp sau:

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

Có nhiều hơn một dòng bắt đầu bằng 'C', vì vậy tôi muốn tìm thấy tệp này bằng lệnh.
Ví dụ cho tệp sau:

Aaaaaaaaaaaa
Baaaaaaaaaaa
Cxxxxxxxxx
Abbbbbbbbbbb
Bbbbbbbbbbbb
Caaaaaa
Accccccccccc
Bccccccccccc
Cdddddd

Luôn có một dòng bắt đầu bằng 'C', tôi không muốn tệp này. Tôi đã nghĩ đến việc sử dụng một grephoặc một sednhưng tôi không biết chính xác làm thế nào để làm điều đó. Có thể sử dụng một regrec ^C.*$^Choặc một cái gì đó như thế. Bất kỳ ý tưởng ?


Có hai dòng bắt đầu với Ctrong ví dụ thứ hai của bạn.
cuonglm

5
Câu hỏi này không rõ ràng. Bạn đang tìm kiếm các tập tin có nhiều hơn một dòng liên tiếp bắt đầu bằng C?
Graeme

Vâng, đây là những gì tôi muốn. Xin lỗi vì sự hiểu lầm.
Jérémie

2
@terdon, có vẻ như các tìm kiếm nhiều dòng với -P đã hoạt động cho đến 2.5.4 và không còn nữa sau đó, mặc dù tôi không thể tìm thấy bất cứ điều gì trong danh sách thay đổi sẽ giải thích tại sao.
Stéphane Chazelas

1
@Graeme bạn có thể muốn xóa lại câu trả lời của mình, xem bình luận của Stephane, rõ ràng nó hoạt động với một số grepphiên bản cũ hơn .
terdon

Câu trả lời:


5

Với 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}' {} +

(mặc dù điều đó có nghĩa là đọc tất cả các tệp đầy đủ với những awktriển khai không hỗ trợ nextfile).


Với các phiên bản GNU greplên tới 2.5.4:

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

có vẻ như hoạt động, nhưng đó là do tình cờ và nó không được đảm bảo để làm việc.

Trước khi được sửa trong 2.6 (theo cam kết này ), GNU grepđã bỏ qua rằng chức năng tìm kiếm pcre mà nó đang sử dụng sẽ khớp với toàn bộ bộ đệm hiện đang được xử lý grep, gây ra tất cả các loại hành vi đáng ngạc nhiên. Ví dụ:

grep -P 'a\s*b'

sẽ khớp trên một tệp chứa:

bla
bla

Điều này sẽ phù hợp:

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

Nhưng điều này:

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

Hoặc là:

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

sẽ không (vì 1\n2\ntrên hai bộ đệm được xử lý bởi grep).

Hành vi đó cuối cùng đã được ghi nhận:

15- Làm thế nào tôi có thể kết hợp trên các dòng?

Grep tiêu chuẩn không thể làm điều này, vì về cơ bản là dựa trên dòng. Do đó, chỉ sử dụng lớp ký tự '[: space:]' không khớp với các dòng mới theo cách bạn có thể mong đợi. Tuy nhiên, nếu grep của bạn được biên dịch với các mẫu Perl được bật, thì công cụ sửa đổi của Perl (tạo ra các dòng mới phù hợp với '.') Có thể được sử dụng:

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

Sau khi được sửa trong 2.6, tài liệu không được sửa đổi (tôi đã từng báo cáo ở đó ).


Có bất kỳ lý do để không sử dụng exit-exec \;thay vì nextfile?
terdon

@terdon, điều đó có nghĩa là chạy một awktệp trên mỗi tệp. Bạn chỉ muốn làm điều đó nếu bạn awkkhông hỗ trợ nextfilevà bạn đã có một tỷ lệ lớn các tệp lớn và có các dòng khớp với phần đầu của tệp.
Stéphane Chazelas

Làm thế nào về kỹ thuật grep này (tôi đoán với các phiên bản GNU grep gần đây hơn) tạo điều kiện cho các kết hợp đa dòng bằng cách làm cho toàn bộ tệp trông giống như một chuỗi bằng cách đặt đầu cuối dòng thành NUL - bạn có biết nếu có bất kỳ giới hạn nào đối với nó không?
iruvar

1
@ 1_CR, Điều đó sẽ tải toàn bộ tệp trong bộ nhớ nếu không có ký tự NUL trong đó và giả sử các dòng không chứa ký tự NUL. Cũng lưu ý rằng phiên bản cũ của GNU grep (mà OP có) không thể sử dụng -zvới -P. Không có \Nmà không có -P, bạn cần phải viết nó $'[\01-\011\013-\0377]'chỉ hoạt động ở các địa phương C (xem thread.gmane.org/gmane.comp.gnu.grep.bugs/5187 )
Stéphane Chazelas

@StephaneChazelas, chi tiết rất hữu ích, cảm ơn
iruvar

2

Với awk:

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

Điều này sẽ in nội dung của tệp nếu có các dòng liên tiếp bắt đầu bằng a C. Biểu thức (p ~ /^C/ && $1 ~ /^C/)sẽ xem xét các dòng liên tiếp trong tệp và sẽ đánh giá là đúng nếu ký tự đầu tiên trong cả hai khớp C. Nếu đó là trường hợp, dòng sẽ được in.

Để tìm tất cả các tệp có mẫu như vậy, bạn có thể chạy awk ở trên thông qua findlệnh:

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

Trong lệnh này, find+ execsẽ đi qua từng tệp và thực hiện awklọc tương tự trên mỗi tệp và in tên của nó thông qua FILENAMEnếu biểu thức awk được ước tính là đúng. Để tránh in FILENAMEnhiều lần cho một tệp có nhiều kết quả khớp, exitcâu lệnh được sử dụng (cảm ơn @terdon).


Câu hỏi của tôi không đủ rõ ràng, tôi muốn biết tên của các tệp có nhiều hơn một dòng liên tiếp bắt đầu bằngC
Jérémie

@ Jérémie Tôi đã cập nhật câu trả lời của tôi.
mkc

Bạn có thể vui lòng thêm một lời giải thích về cách thức này hoạt động? Ngoài ra, không cần flag, chỉ cần exitthay thế. Bằng cách đó, bạn không cần tiếp tục xử lý tệp sau khi trận đấu được tìm thấy.
terdon

2

Một tùy chọn khác với GNU sed:

Đối với một tệp duy nhất:

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

(mặc dù nó cũng sẽ báo cáo các tập tin mà nó không thể đọc được).

Dành cho find:

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

Có thể tránh sự cố với các tệp không thể đọc được bằng cách viết:

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

Bạn có thể xin vui lòng chi tiết sed -n '$q1;/^C/{n;/^C/q}'?
Jérémie

Có ai giải thích cho tôi không?
Jérémie

@ Jérémie $q1- buộc sed bỏ cuộc với một lỗi nếu không tìm thấy mẫu. Nó cũng sẽ kết thúc với lỗi nếu có lỗi với tệp (không thể đọc được hoặc bị hỏng). Vì vậy, nó sẽ thoát với trạng thái thoát 0 chỉ trong trường hợp mẫu được tìm thấy và nó sẽ được chuyển qua để in. Phần với /^C/{n;/^C/qkhá đơn giản. Nếu nó tìm thấy chuỗi bắt đầu bằng C, nó sẽ đọc dòng tiếp theo và nếu nó cũng bắt đầu bằng C, nó sẽ thoát với trạng thái thoát không.
vội vàng

1

Giả sử các tệp của bạn đủ nhỏ để đọc vào bộ nhớ:

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

Giải trình:

  • - 000: được đặt \n\nlàm dấu tách bản ghi, điều này sẽ bật chế độ đoạn văn sẽ coi các đoạn văn (được phân tách bằng các dòng mới liên tiếp) dưới dạng các dòng đơn.
  • -ne: áp dụng tập lệnh được cung cấp làm đối số -echo từng dòng của (các) tệp đầu vào.
  • $ARGV : là tệp hiện đang được xử lý
  • /^C[^\n]*\nC/: khớp Cở đầu một dòng (xem mô tả về các smsửa đổi bên dưới để biết lý do tại sao điều này hoạt động ở đây) theo sau là 0 hoặc nhiều ký tự không phải dòng mới, một dòng mới và sau đó là C. Nói cách khác, tìm các dòng liên tiếp bắt đầu bằng C. * //sm: các công cụ sửa đổi khớp này là (như tài liệu [ở đây]):

    • m : Coi chuỗi là nhiều dòng. Đó là, thay đổi "^" và "$" từ khớp đầu hoặc cuối dòng chỉ ở đầu bên trái và bên phải của chuỗi thành khớp với chúng ở bất cứ đâu trong chuỗi.

    • s : Coi chuỗi là một dòng đơn. Đó là, thay đổi "." để phù hợp với bất kỳ nhân vật nào, ngay cả một dòng mới, mà thông thường nó sẽ không phù hợp.

Bạn cũng có thể làm một cái gì đó xấu xí như:

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

Ở đây, perlđang thay thế dòng mới với %%như vậy, giả sử bạn không có %%trong tập tin đầu vào của bạn (lớn nếu tất nhiên) thì grepsẽ phù hợp với dòng liên tiếp bắt đầu với C.


1

GIẢI PHÁP:

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

BẢN GIỚI THIỆU:

Đầu tiên, chúng tôi sẽ tạo một cơ sở thử nghiệm:

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

Ở trên tạo ra 26 tập tin /tmpđược đặt tên file1-26. Trong mỗi tệp có 27 hoặc 28 dòng bắt đầu bằng các chữ cái a-zvà tiếp theo là phần còn lại của bảng chữ cái. Mỗi tệp thứ 3 chứa hai dòng liên tiếp trong đó ký tự đầu tiên được nhân đôi.

MẪU VẬT:

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

Và khi tôi thay đổi:

set -- *files

đến:

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

Tôi có...

ĐẦU RA:

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

Vì vậy, tóm lại, giải pháp hoạt động như thế này:

sets subshell vị trí cho tất cả các tệp của bạn và cho mỗi tệp

sets vị trí của một khung con được lồng vào chữ cái đầu tiên của mỗi dòng trong mỗi tệp khi nó lặp.

[ tests ]nếu $1phủ $2định chỉ ra một trận đấu, và nếu vậy

echoestên tập tin sau đó break vòng lặp hiện tại

khác shifts đến vị trí ký tự đơn tiếp theo để thử lại


0

Kịch bản này sử dụng grepcutđể có được số dòng của các dòng khớp và kiểm tra hai số liên tiếp bất kỳ. Tệp được giả sử là một tên tệp hợp lệ được truyền làm đối số đầu tiên cho tập lệnh:

#!/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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.