Làm cách nào để có được một phần của tệp sau dòng đầu tiên khớp với biểu thức chính quy?


169

Tôi có một tập tin với khoảng 1000 dòng. Tôi muốn một phần của tập tin của tôi sau dòng khớp với câu lệnh grep của tôi.

Đó là:

$ cat file | grep 'TERMINATE'     # It is found on line 534

Vì vậy, tôi muốn tệp từ dòng 535 đến dòng 1000 để xử lý thêm.

Làm thế nào tôi có thể làm điều đó?


34
UUOC (Sử dụng mèo vô dụng):grep 'TERMINATE' file
Jacob

30
Tôi biết điều đó, giống như tôi sử dụng nó theo cách đó. Hãy quay trở lại câu hỏi.
Joly Julum

3
Đây là một câu hỏi lập trình hoàn toàn tốt, và rất phù hợp cho stackoverflow.
aioobe

13
@Jacob Đó không phải là cách sử dụng mèo vô dụng. Sử dụng của nó là để in một tập tin để đầu ra tiêu chuẩn, có nghĩa là chúng ta có thể sử dụng grepcủa giao diện đầu vào tiêu chuẩn để đọc dữ liệu trong, thay vì phải tìm hiểu những gì chuyển sang áp dụng cho grep, và sed, và awk, và pandoc, và ffmpegvv khi chúng ta muốn đọc từ một tập tin. Nó tiết kiệm thời gian vì chúng ta không phải học một công tắc mới mỗi khi chúng ta muốn làm điều tương tự: đọc từ một tệp.
runeks

@runeks Tôi đồng ý với tình cảm của bạn - nhưng bạn có thể đạt được điều đó mà không cần mèo : grep 'TERMINATE' < file. Có thể nó làm cho việc đọc khó hơn một chút - nhưng đây là kịch bản shell, vì vậy điều đó luôn luôn là một vấn đề :)
LOAS

Câu trả lời:


307

Sau đây sẽ in dòng khớp TERMINATEcho đến cuối tập tin:

sed -n -e '/TERMINATE/,$p'

Giải thích: -n vô hiệu hóa hành vi mặc định của sedviệc in từng dòng sau khi thực thi tập lệnh của nó trên tập lệnh đó, -echỉ ra một tập lệnh đến sed, /TERMINATE/,$là một lựa chọn phạm vi địa chỉ (dòng) có nghĩa là dòng đầu tiên khớp với TERMINATEbiểu thức chính quy (như grep) ở cuối tệp ( $) và plà lệnh in in dòng hiện tại.

Điều này sẽ in từ dòng theo dòng phù hợp TERMINATEcho đến cuối tệp:
(từ SAU dòng phù hợp đến EOF, KHÔNG bao gồm dòng phù hợp)

sed -e '1,/TERMINATE/d'

Giải thích: 1,/TERMINATE/ là lựa chọn phạm vi địa chỉ (dòng) có nghĩa là dòng đầu tiên cho đầu vào của dòng thứ 1 khớp với TERMINATEbiểu thức chính quy và dlà lệnh xóa sẽ xóa dòng hiện tại và chuyển sang dòng tiếp theo. Vì sedhành vi mặc định là in các dòng, nó sẽ in các dòng sau khi TERMINATE kết thúc đầu vào.

Biên tập:

Nếu bạn muốn các dòng trước TERMINATE:

sed -e '/TERMINATE/,$d'

Và nếu bạn muốn cả hai dòng trước và sau TERMINATEtrong 2 tệp khác nhau trong một lần chạy:

sed -e '1,/TERMINATE/w before
/TERMINATE/,$w after' file

Các tập tin trước và sau sẽ chứa dòng kết thúc, vì vậy để xử lý mỗi tập tin bạn cần sử dụng:

head -n -1 before
tail -n +2 after

Chỉnh sửa2:

NẾU bạn không muốn mã hóa tên tập tin trong tập lệnh sed, bạn có thể:

before=before.txt
after=after.txt
sed -e "1,/TERMINATE/w $before
/TERMINATE/,\$w $after" file

Nhưng sau đó, bạn phải thoát khỏi $ý nghĩa của dòng cuối cùng để shell sẽ không cố gắng mở rộng $wbiến (lưu ý rằng bây giờ chúng ta sử dụng dấu ngoặc kép quanh tập lệnh thay vì dấu ngoặc đơn).

Tôi quên nói rằng dòng mới là quan trọng sau khi tên tệp trong kịch bản để sed biết rằng tên tệp kết thúc.


Chỉnh sửa: 2016-0530

Sébastien Clément hỏi: "Làm thế nào bạn sẽ thay thế mã hóa cứng TERMINATEbằng một biến?"

Bạn sẽ tạo một biến cho văn bản phù hợp và sau đó thực hiện theo cách tương tự như ví dụ trước:

matchtext=TERMINATE
before=before.txt
after=after.txt
sed -e "1,/$matchtext/w $before
/$matchtext/,\$w $after" file

để sử dụng một biến cho văn bản phù hợp với các ví dụ trước:

## Print the line containing the matching text, till the end of the file:
## (from the matching line to EOF, including the matching line)
matchtext=TERMINATE
sed -n -e "/$matchtext/,\$p"
## Print from the line that follows the line containing the 
## matching text, till the end of the file:
## (from AFTER the matching line to EOF, NOT including the matching line)
matchtext=TERMINATE
sed -e "1,/$matchtext/d"
## Print all the lines before the line containing the matching text:
## (from line-1 to BEFORE the matching line, NOT including the matching line)
matchtext=TERMINATE
sed -e "/$matchtext/,\$d"

Các điểm quan trọng về việc thay thế văn bản bằng các biến trong các trường hợp này là:

  1. Các biến ( $variablename) được bao trong single quotes[ '] sẽ không "mở rộng" nhưng các biến bên trong double quotes[ "] sẽ. Vì vậy, bạn phải thay đổi tất cả các single quotesđể double quotesnếu chúng chứa văn bản mà bạn muốn thay thế bằng một biến.
  2. Các sedphạm vi cũng chứa một $và ngay lập tức được theo sau bởi một chữ cái như:$p , $d, $w. Họ cũng sẽ giống như biến để được mở rộng, vì vậy bạn phải thoát khỏi những $ký tự với một dấu chéo ngược [ \] như: \$p, \$d, \$w.

Làm thế nào chúng ta có thể có được các dòng trước TERMINATE và xóa tất cả những gì sau đây?
Joly Julum

Làm thế nào bạn sẽ thay thế TERMINAL mã hóa bằng một biến?
Sébastien Clément

2
Một trường hợp sử dụng bị thiếu ở đây là cách in các dòng sau điểm đánh dấu cuối cùng (nếu có thể có nhiều trong số chúng trong tệp .. nghĩ tệp nhật ký, v.v.).
mato

Ví dụ sed -e "1,/$matchtext/d"không hoạt động khi $matchtextxảy ra trong dòng đầu tiên. Tôi đã phải thay đổi nó thành sed -e "0,/$matchtext/d".
Karalga

61

Là một xấp xỉ đơn giản, bạn có thể sử dụng

grep -A100000 TERMINATE file

greps cho TERMINATEvà đầu ra lên đến 100000 dòng theo dòng đó.

Từ trang người đàn ông

-A NUM, --after-context=NUM

In NUM dòng ngữ cảnh sau khi phù hợp với dòng. Đặt một dòng chứa dấu phân cách nhóm (-) giữa các nhóm khớp nhau. Với tùy chọn khớp -o hoặc --only, điều này không có hiệu lực và cảnh báo được đưa ra.


Điều đó có thể làm việc cho điều này, nhưng tôi cần mã nó vào tập lệnh của mình để xử lý nhiều tệp. Vì vậy, hiển thị một số giải pháp chung chung.
Joly Julum

3
Tôi nghĩ rằng đây là một giải pháp thiết thực!
michelgotta

2
tương tự -B NUM, --b Before-bối ​​cảnh = NUM ​​In NUM dòng ngữ cảnh hàng đầu trước khi khớp các dòng. Đặt một dòng chứa dấu phân cách nhóm (-) giữa các nhóm khớp nhau. Với tùy chọn khớp -o hoặc --only, điều này không có hiệu lực và cảnh báo được đưa ra.
PiyusG

giải pháp này hiệu quả với tôi vì tôi có thể dễ dàng sử dụng các biến làm chuỗi của mình để kiểm tra.
Jose Martinez

3
Ý kiến ​​hay! Nếu bạn không chắc chắn về kích thước của bối cảnh, bạn có thể đếm các dòng filethay thế:grep -A$(cat file | wc -l) TERMINATE file
Lemming

26

Một công cụ để sử dụng ở đây là awk:

cat file | awk 'BEGIN{ found=0} /TERMINATE/{found=1}  {if (found) print }'

Cái này hoạt động ra sao:

  1. Chúng tôi đặt biến 'tìm thấy' thành 0, đánh giá sai
  2. nếu một kết quả khớp cho 'HẠN' được tìm thấy với biểu thức chính quy, chúng tôi sẽ đặt nó thành một.
  3. Nếu biến 'tìm thấy' của chúng tôi ước tính là True, hãy in :)

Các giải pháp khác có thể tiêu tốn rất nhiều bộ nhớ nếu bạn sử dụng chúng trên các tệp rất lớn.


Đơn giản, thanh lịch và rất chung chung. Trong trường hợp của tôi, nó đã in mọi thứ cho đến khi xuất hiện lần thứ hai '###':cat file | awk 'BEGIN{ found=0} /###/{found=found+1} {if (found<2) print }'
Aleksander Stelmaczonek

3
Một công cụ không sử dụng ở đây là cat. awkhoàn toàn có khả năng lấy một hoặc nhiều tên tệp làm đối số. Xem thêm stackoverflow.com/questions/11710552/usless-use-of-cat
tripleee

9

Nếu tôi hiểu chính xác câu hỏi của bạn, bạn sẽ muốn các dòng sau TERMINATE , không bao gồm TERMINATE-line. awkcó thể làm điều này một cách đơn giản:

awk '{if(found) print} /TERMINATE/{found=1}' your_file

Giải trình:

  1. Mặc dù không thực hành tốt nhất, bạn có thể dựa vào thực tế là tất cả các vars mặc định là 0 hoặc chuỗi rỗng nếu không được xác định. Vì vậy, biểu thức đầu tiên ( if(found) print) sẽ không in bất cứ thứ gì để bắt đầu.
  2. Sau khi in xong, chúng tôi kiểm tra xem đây có phải là dòng bắt đầu không (không nên bao gồm).

Điều này sẽ in tất cả các dòng sau khi các TERMINATEline.


Sự khái quát:

  • Bạn có một tập tin với sự khởi đầu - và cuối -lines và bạn muốn ranh giới giữa những dòng trừ các đầu - và cuối -lines.
  • bắt đầu - và kết thúc dòng có thể được xác định bởi một biểu thức chính quy khớp với dòng.

Thí dụ:

$ cat ex_file.txt 
not this line
second line
START
A good line to include
And this line
Yep
END
Nope more
...
never ever
$ awk '/END/{found=0} {if(found) print} /START/{found=1}' ex_file.txt 
A good line to include
And this line
Yep
$

Giải trình:

  1. Nếu dòng cuối được tìm thấy, không nên in. Lưu ý rằng kiểm tra này được thực hiện trước khi in thực tế để loại trừ dòng kết thúc khỏi kết quả.
  2. In dòng hiện tại nếu foundđược đặt.
  3. Nếu dòng bắt đầu được tìm thấy thì hãy đặt found=1để các dòng sau được in. Lưu ý rằng kiểm tra này được thực hiện sau khi in thực tế để loại trừ dòng bắt đầu khỏi kết quả.

Ghi chú:

  • Mã dựa trên thực tế là tất cả các awk-vars mặc định là 0 hoặc chuỗi rỗng nếu không được xác định. Điều này là hợp lệ nhưng có thể không phải là cách thực hành tốt nhất để bạn có thể thêm BEGIN{found=0}phần bắt đầu của biểu thức awk.
  • Nếu nhiều khóa bắt đầu được tìm thấy, tất cả đều được in.

1
Tuyệt vời ví dụ tuyệt vời. Chỉ cần dành 2 giờ để xem csplit, sed và tất cả các cách thức của các lệnh awk phức tạp. Điều này không chỉ làm những gì tôi muốn mà còn hiển thị đủ đơn giản để suy ra cách sửa đổi nó để làm một vài điều liên quan khác mà tôi cần. Làm cho tôi nhớ awk là tuyệt vời và không chỉ trong mớ hỗn độn không thể giải mã được. Cảm ơn.
dùng1169420

{if(found) print}là một chút của một mô hình chống trong awk, nó là thành ngữ hơn để thay thế khối bằng chỉ foundhoặc found;nếu bạn cần một bộ lọc khác sau đó.
user000001

@ user000001 vui lòng giải thích. Tôi không hiểu những gì để thay thế và làm thế nào. Dù sao, tôi nghĩ rằng cách viết của nó làm cho nó rất rõ ràng những gì đang xảy ra.
UlfR

1
Bạn sẽ thay thế awk '{if(found) print} /TERMINATE/{found=1}' your_filebằng awk 'found; /TERMINATE/{found=1}' your_file, cả hai nên làm điều tương tự.
user000001

7

Sử dụng mở rộng tham số bash như sau:

content=$(cat file)
echo "${content#*TERMINATE}"

Bạn có thể giải thích những gì bạn đang làm?
Joly Julum

Tôi đã sao chép nội dung của "tập tin" vào biến nội dung $. Sau đó, tôi đã xóa tất cả các ký tự cho đến khi nhìn thấy "HẠN". Nó không sử dụng kết hợp tham lam, nhưng bạn có thể sử dụng kết hợp tham lam theo $ {content ## * TERMINATE}.
Mu Qiao

đây là đường dẫn của hướng dẫn sử dụng bash: gnu.org/software/bash/manual/ mẹo
Mu Qiao

6
Điều gì sẽ xảy ra nếu tập tin có kích thước 100GB?
Znik

1
Downvote: Điều này thật kinh khủng (đọc tệp thành một biến) và sai (sử dụng biến mà không trích dẫn nó; và bạn nên sử dụng đúng printfhoặc đảm bảo bạn biết chính xác những gì bạn đang chuyển đến echo.).
tripleee

6

grep -A 10000000 tệp 'HẠN CHẾ'

  • là nhiều, nhanh hơn nhiều so với sed đặc biệt là làm việc trên tập tin thực sự lớn. Nó hoạt động lên đến 10 triệu dòng (hoặc bất cứ thứ gì bạn đặt vào) vì vậy không có hại gì trong việc làm cho nó đủ lớn để xử lý mọi thứ bạn nhấn.

4

Có nhiều cách để làm điều đó với sedhoặc awk:

sed -n '/TERMINATE/,$p' file

Điều này tìm kiếm TERMINATEtrong tệp của bạn và in từ dòng đó đến cuối tệp.

awk '/TERMINATE/,0' file

Đây chính xác là hành vi tương tự như sed.

Trong trường hợp bạn biết số dòng mà bạn muốn bắt đầu in, bạn có thể chỉ định nó cùng với NR(số bản ghi, cuối cùng chỉ ra số của dòng):

awk 'NR>=535' file

Thí dụ

$ seq 10 > a        #generate a file with one number per line, from 1 to 10
$ sed -n '/7/,$p' a
7
8
9
10
$ awk '/7/,0' a
7
8
9
10
$ awk 'NR>=7' a
7
8
9
10

Đối với số bạn cũng có thể sử dụngmore +7 file
123

Điều này bao gồm dòng phù hợp, không phải là những gì muốn trong câu hỏi này.
mivk

@mivk tốt, đây cũng là trường hợp của câu trả lời được chấp nhận và được đánh giá cao thứ 2, vì vậy vấn đề có thể là với một tiêu đề sai lệch.
fedorqui 'SO ngừng làm hại'

3

Nếu vì bất kỳ lý do gì, bạn muốn tránh sử dụng sed, phần sau đây sẽ in dòng khớp TERMINATEcho đến hết tệp:

tail -n "+$(grep -n 'TERMINATE' file | head -n 1 | cut -d ":" -f 1)" file

và dòng sau sẽ in từ dòng khớp sau TERMINATEcho đến hết tệp:

tail -n "+$(($(grep -n 'TERMINATE' file | head -n 1 | cut -d ":" -f 1)+1))" file

Phải mất 2 quy trình để thực hiện những gì sed có thể làm trong một quy trình và nếu tệp thay đổi giữa việc thực hiện grep và tail, kết quả có thể không mạch lạc, vì vậy tôi khuyên bạn nên sử dụng sed. Hơn nữa, nếu tập tin không chứa TERMINATE, lệnh 1 sẽ thất bại.


tập tin được quét hai lần. Nếu nó có kích thước 100GB thì sao?
Znik

1
Bị từ chối vì đây là một giải pháp tào lao, nhưng sau đó bị bỏ qua vì 90% câu trả lời là hãy cẩn thận.
Nhà vật lý điên


0

Đây có thể là một cách để làm điều đó. Nếu bạn biết dòng nào của tệp bạn có từ grep của bạn và bạn có bao nhiêu dòng trong tệp của mình:

tệp grep -A466 'HẠN CHẾ'


1
Nếu số dòng được biết, thì grepthậm chí không cần thiết; bạn chỉ có thể sử dụng tail -n $NUM, vì vậy đây không thực sự là một câu trả lời.
Samveen

-1

sed là một công cụ tốt hơn cho công việc: tệp sed -n '/ re /, $ p'

trong đó re là regrec.

Một tùy chọn khác là cờ - sau ngữ cảnh của grep. Bạn cần chuyển một số để kết thúc tại, sử dụng wc trên tệp sẽ đưa ra giá trị đúng để dừng tại. Kết hợp điều này với -n và biểu thức khớp của bạn.


- sau đó bối cảnh là tốt nhưng không phải trong mọi trường hợp.
Joly Julum

Bạn có thể đề nghị một cái gì đó khác .. ??
Joly Julum

-2

Chúng sẽ in tất cả các dòng từ dòng tìm thấy cuối cùng "TERMINATE" cho đến hết tệp:

LINE_NUMBER=`grep -o -n TERMINATE $OSCAM_LOG|tail -n 1|sed "s/:/ \\'/g"|awk -F" " '{print $1}'`
tail -n +$LINE_NUMBER $YOUR_FILE_NAME

Trích xuất một số dòng với grepđể bạn có thể cung cấp cho nó taillà một antipotype lãng phí. Tìm trận đấu và in lên đến cuối tập tin (hoặc ngược lại, in và dừng ở trận đấu đầu tiên) được thực hiện rõ ràng bằng chính các công cụ regex thông thường, thiết yếu. Đồ sộ grep | tail | sed | awkcũng là một công dụng grepvà bạn bè vô dụng .
tripleee 17/2/2016

Tôi nghĩ rằng anh ấy đã cố gắng cung cấp cho chúng tôi thứ gì đó sẽ tìm thấy / trường hợp cuối cùng / của 'TERMINATE' và đưa ra các dòng từ trường hợp đó vào. Các triển khai khác cung cấp cho bạn phiên bản đầu tiên trở đi. LINE_NUMBER có lẽ sẽ trông như thế này, thay vào đó: LINE_NUMBER = $ (grep -o -n 'TERMINATE' $ OSCAM_LOG | tail -n 1 | awk -F: '{print $ 1}') Có thể không phải là cách thanh lịch nhất, nhưng nó dường như để hoàn thành công việc ^. ^
fbicknel

... hoặc tất cả trong một dòng, nhưng xấu xí: tail -n + $ (grep -o -n 'TERMINATE' $ YOU_FILE_NAME | tail -n 1 | awk -F: '{print $ 1}') $ YOU_FILE_NAME
fbicknel

.... và tôi sẽ quay lại và chỉnh sửa $ OSCAM_LOG thay cho $ YOU_FILE_NAME ... nhưng không thể vì một số lý do. Không biết $ OSCAM_LOG đến từ đâu; Tôi chỉ vô tâm vẹt nó. oO
fbicknel

Làm điều này trong Awk một mình là một nhiệm vụ phổ biến trong Awk 101. Nếu bạn đã sử dụng một công cụ có khả năng hơn chỉ để lấy số dòng, hãy bỏ qua tailvà thực hiện nhiệm vụ trong công cụ có khả năng cao hơn hoàn toàn. Dù sao, tiêu đề rõ ràng nói "trận đấu đầu tiên".
tripleee
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.