Làm cách nào tôi có thể trích xuất một phạm vi dòng được xác định trước từ tệp văn bản trên Unix?


531

Tôi có một bãi chứa SQL dòng ~ 23000 chứa một số cơ sở dữ liệu có giá trị dữ liệu. Tôi cần trích xuất một phần nhất định của tệp này (tức là dữ liệu cho một cơ sở dữ liệu) và đặt nó vào một tệp mới. Tôi biết cả số dòng bắt đầu và số cuối của dữ liệu mà tôi muốn.

Có ai biết một lệnh Unix (hoặc một loạt các lệnh) để trích xuất tất cả các dòng từ một tệp giữa dòng say 16224 và 16482 và sau đó chuyển hướng chúng thành một tệp mới không?


Vì bạn đề cập đến các tệp lớn, tôi khuyên bạn nên kiểm tra bình luận stackoverflow.com/questions/83329/ từ
sancho.s RebstateMonicaCellio

Câu trả lời:


792
sed -n '16224,16482p;16483q' filename > newfile

Từ hướng dẫn sử dụng sed :

p - In không gian mẫu (đến đầu ra tiêu chuẩn). Lệnh này thường chỉ được sử dụng cùng với tùy chọn dòng lệnh -n.

n - Nếu tự động in không bị tắt, hãy in không gian mẫu, sau đó, bất kể, thay thế không gian mẫu bằng dòng đầu vào tiếp theo. Nếu không có thêm đầu vào thì sed thoát ra mà không xử lý thêm lệnh nào.

q - Thoát sedmà không xử lý thêm bất kỳ lệnh hoặc đầu vào. Lưu ý rằng không gian mẫu hiện tại được in nếu tự động in không bị tắt với tùy chọn -n.

Địa chỉ trong tập lệnh sed có thể ở bất kỳ dạng nào sau đây:

số Chỉ định số dòng sẽ chỉ khớp với dòng đó trong đầu vào.

Một phạm vi địa chỉ có thể được chỉ định bằng cách chỉ định hai địa chỉ được phân tách bằng dấu phẩy (,). Phạm vi địa chỉ khớp với các dòng bắt đầu từ nơi địa chỉ đầu tiên khớp và tiếp tục cho đến khi địa chỉ thứ hai khớp (bao gồm).


3
Tôi đã tò mò nếu điều này sửa đổi các tập tin ban đầu. Tôi đã sao lưu nó chỉ trong trường hợp và có vẻ như điều này KHÔNG sửa đổi bản gốc, như mong đợi.
Andy Groff

@AndyGroff. Để sửa đổi tập tin tại chỗ, sử dụng tham số "-i". Nếu không nó sẽ không sửa đổi các tập tin.
youri

175
Nếu, giống như tôi, bạn cần thực hiện việc này trên một tệp RẤT lớn, sẽ hữu ích nếu bạn thêm lệnh thoát trên dòng tiếp theo. Sau đó là nó sed -n '16224,16482p;16483q' filename. Nếu không, sed sẽ tiếp tục quét cho đến hết (hoặc ít nhất là phiên bản của tôi).
wds

7
@MilesRout mọi người dường như hỏi "tại sao downvote?" khá thường xuyên, có lẽ bạn có nghĩa là "Tôi không quan tâm" thay vì "không ai quan tâm"
Đánh dấu

1
@wds - Nhận xét của bạn cũng xứng đáng có một câu trả lời leo lên đầu. Nó có thể tạo ra sự khác biệt giữa ngày và đêm.
sancho.s RebstateMonicaCellio

203
sed -n '16224,16482 p' orig-data-file > new-file

Trong đó 16224,16482 là số dòng bắt đầu và số dòng kết thúc, đã bao gồm. Đây là 1 chỉ mục. -ntriệt tiêu tiếng vang đầu vào là đầu ra mà bạn rõ ràng không muốn; các con số biểu thị phạm vi của các dòng để thực hiện lệnh sau; lệnh pin ra các dòng có liên quan.


7
Trên các tệp lớn, lệnh trên sẽ tiếp tục di chuyển toàn bộ tệp sau khi tìm thấy phạm vi mong muốn. Có cách nào để sed dừng xử lý tệp khi phạm vi đã được xuất không?
Gary

39
Chà, từ câu trả lời ở đây , dường như việc dừng lại ở cuối phạm vi có thể được thực hiện bằng : sed -n '16224,16482p;16482q' orig-data-file > new-file.
Gary

5
Tại sao bạn sẽ đặt trong một không gian không cần thiết, và sau đó phải trích dẫn? (Tất nhiên, tạo ra những vấn đề không cần thiết và giải quyết chúng là bản chất của một nửa khoa học máy tính, nhưng ý tôi là bên cạnh lý do đó ...)
Kaz

92

Khá đơn giản bằng cách sử dụng đầu / đuôi:

head -16482 in.sql | tail -258 > out.sql

sử dụng sed:

sed -n '16482,16482p' in.sql > out.sql

sử dụng awk:

awk 'NR>=10&&NR<=20' in.sql > out.sql

1
Tùy chọn thứ hai và thứ ba là OK, nhưng tùy chọn thứ nhất chậm hơn nhiều lựa chọn thay thế vì nó sử dụng 2 lệnh trong đó 1 là đủ. Nó cũng đòi hỏi tính toán để có được lý lẽ đúng tail.
Jonathan Leffler

3
Đáng lưu ý rằng để giữ các số dòng giống như câu hỏi, lệnh sed phải là sed -n 16224,16482p' in.sql >out.sqlvà lệnh awk phải làawk 'NR>=16224&&NR<=16482' in.sql > out.sql
sibaz

3
Cũng đáng để biết rằng trong trường hợp ví dụ đầu tiên, head -16482 in.sql | tail -$((16482-16224)) >out.sqlviệc tính toán sẽ giảm xuống bash
sibaz

1
Cái đầu tiên có đầu và đuôi WAYYYY nhanh hơn trên các tệp lớn hơn phiên bản sed, ngay cả khi có thêm tùy chọn q. phiên bản đầu ngay lập tức và phiên bản sed Tôi Ctrl-C sau một phút ... Cảm ơn
Miyagi

2
Cũng có thể sử dụng tail -n +16224để giảm tính toán
SOFe

35

Bạn có thể sử dụng 'vi' và sau đó là lệnh sau:

:16224,16482w!/tmp/some-file

Cách khác:

cat file | head -n 16482 | tail -n 258

EDIT: - Chỉ cần thêm lời giải thích, bạn sử dụng head -n 16482 để hiển thị 16482 dòng đầu tiên sau đó sử dụng đuôi -n 258 để có được 258 dòng cuối cùng từ đầu ra đầu tiên.


2
Và thay vì vi bạn có thể sử dụng ex, đó là vi trừ công cụ bảng điều khiển tương tác.
Tadeusz A. Kadłubowski

1
Bạn không cần catlệnh; headcó thể đọc một tập tin trực tiếp. Điều này chậm hơn so với nhiều lựa chọn thay thế vì nó sử dụng các lệnh 2 (3 như được hiển thị) trong đó 1 là đủ.
Jonathan Leffler

1
@JonathanLeffler Bạn khá sai. Nó nhanh như chớp. Tôi trích xuất 200k dòng, khoảng 1G, từ tệp 2G với 500k dòng, trong vài giây (không có cat). Các giải pháp khác cần ít nhất một vài phút. Ngoài ra, biến thể nhanh nhất trên GNU dường như là tail -n +XXX filename | head XXX.
Antonis Christofides

28

Có một cách tiếp cận khác với awk:

awk 'NR==16224, NR==16482' file

Nếu tệp rất lớn, có thể tốt exitsau khi đọc dòng mong muốn cuối cùng. Bằng cách này, nó sẽ không đọc các dòng sau đây một cách không cần thiết:

awk 'NR==16224, NR==16482-1; NR==16482 {print; exit}' file

awk 'NR==16224, NR==16482; NR==16482 {exit}' file

2
1+ để lưu thời gian chạy và tài nguyên bằng cách sử dụng print; exit. Cảm ơn !
Bernie Reiter

Đơn giản hóa một chút về ví dụ thứ 2:awk 'NR==16224, NR==16482; NR==16482 {exit}' file
Robin A. Meade

Thật tuyệt, cảm ơn @ RobinA.Meade! Tôi đã chỉnh sửa ý tưởng của bạn trong bài đăng
fedorqui 'SO ngừng gây hại'


9
 # print section of file based on line numbers
 sed -n '16224 ,16482p'               # method 1
 sed '16224,16482!d'                 # method 2

6
cat dump.txt | head -16224 | tail -258

nên làm thủ thuật. Nhược điểm của phương pháp này là bạn cần thực hiện số học để xác định đối số cho đuôi và tính toán xem bạn có muốn 'giữa' bao gồm dòng kết thúc hay không.


4
Bạn không cần catlệnh; headcó thể đọc một tập tin trực tiếp. Điều này chậm hơn so với nhiều lựa chọn thay thế vì nó sử dụng các lệnh 2 (3 như được hiển thị) trong đó 1 là đủ.
Jonathan Leffler

@JonathanLeffler Câu trả lời này là dễ đọc và dễ nhớ nhất. Nếu bạn thực sự quan tâm đến hiệu suất, bạn sẽ không sử dụng vỏ ở nơi đầu tiên. Đó là thực hành tốt để cho các công cụ cụ thể cống hiến cho một nhiệm vụ nhất định. Hơn nữa, "số học" có thể được giải quyết bằng cách sử dụng | tail -$((16482 - 16224)).
Yeti

6

Đứng trên vai của boxxar, tôi thích điều này:

sed -n '<first line>,$p;<last line>q' input

ví dụ

sed -n '16224,$p;16482q' input

Nghĩa $là "dòng cuối cùng", vì vậy lệnh đầu tiên thực hiện sedin tất cả các dòng bắt đầu bằng dòng 16224và lệnh thứ hai thực hiện sedthoát sau khi in dòng 16428. (Thêm 1cho qtrung cấp, trong dung dịch boxxar dường như không cần thiết.)

Tôi thích biến thể này vì tôi không cần chỉ định số dòng kết thúc hai lần. Và tôi đã đo lường rằng việc sử dụng $không có tác động bất lợi đến hiệu suất.



3

Nhanh chóng và hèn hạ:

head -16428 < file.in | tail -259 > file.out

Có lẽ không phải là cách tốt nhất để làm điều đó nhưng nó nên hoạt động.

BTW: 259 = 16482-16224 + 1.


Điều này chậm hơn nhiều lựa chọn thay thế vì nó sử dụng 2 lệnh trong đó 1 là đủ.
Jonathan Leffler

3

Tôi đã viết một chương trình Haskell có tên là splitter thực hiện chính xác điều này: đọc qua bài đăng trên blog phát hành của tôi .

Bạn có thể sử dụng chương trình như sau:

$ cat somefile | splitter 16224-16482

Và đó là tất cả những gì có nó. Bạn sẽ cần Haskell để cài đặt nó. Chỉ:

$ cabal install splitter

Và bạn đã hoàn thành. Tôi hy vọng rằng bạn thấy chương trình này hữu ích.


splitterchỉ đọc từ đầu vào tiêu chuẩn? Theo một nghĩa nào đó, nó không thành vấn đề; các catlệnh là không cần thiết cho dù nó hay không. Sử dụng splitter 16224-16482 < somefilehoặc (nếu nó lấy đối số tên tệp) splitter 16224-16482 somefile.
Jonathan Leffler

3

Thậm chí chúng ta có thể làm điều này để kiểm tra tại dòng lệnh:

cat filename|sed 'n1,n2!d' > abc.txt

Ví dụ:

cat foo.pl|sed '100,200!d' > abc.txt

6
Bạn không cần catlệnh trong một trong hai; sedhoàn toàn có khả năng tự đọc các tệp hoặc bạn có thể chuyển hướng đầu vào tiêu chuẩn từ một tệp.
Jonathan Leffler

3

Sử dụng ruby:

ruby -ne 'puts "#{$.}: #{$_}" if $. >= 32613500 && $. <= 32614500' < GND.rdf > GND.extract.rdf

2

Tôi đã định đăng mẹo lừa đầu / đuôi, nhưng thực sự có lẽ tôi chỉ cần kích hoạt emacs. ;-)

  1. esc-x goto-line ret16224
  2. dấu (ctrl - space)
  3. esc-x goto-line ret16482
  4. esc-w

mở tập tin đầu ra mới, lưu ctl-y

Hãy để tôi xem những gì đang xảy ra.


4
Emacs không hoạt động tốt trên các tệp rất lớn theo kinh nghiệm của tôi.
Greg Mattes

Bạn có thể chạy nó như một hành động theo kịch bản hay chỉ là một tùy chọn tương tác?
Jonathan Leffler

2

Tôi sẽ dùng:

awk 'FNR >= 16224 && FNR <= 16482' my_file > extracted.txt

FNR chứa số bản ghi (dòng) của dòng được đọc từ tệp.


2

Tôi muốn làm điều tương tự từ một tập lệnh bằng cách sử dụng một biến và đạt được nó bằng cách đặt dấu ngoặc kép quanh biến $ để tách tên biến khỏi p:

sed -n "$first","$count"p imagelist.txt >"$imageblock"

Tôi muốn chia một danh sách thành các thư mục riêng biệt và tìm thấy câu hỏi ban đầu và trả lời một bước hữu ích. (lệnh split không phải là một tùy chọn trên hệ điều hành cũ mà tôi phải chuyển mã sang).


1

Tôi đã viết một tập lệnh bash nhỏ mà bạn có thể chạy từ dòng lệnh của mình, miễn là bạn cập nhật PATH của mình để bao gồm thư mục của nó (hoặc bạn có thể đặt nó trong một thư mục đã có trong PATH).

Cách sử dụng: $ pinch tên tập tin bắt đầu dòng cuối

#!/bin/bash
# Display line number ranges of a file to the terminal.
# Usage: $ pinch filename start-line end-line
# By Evan J. Coon

FILENAME=$1
START=$2
END=$3

ERROR="[PINCH ERROR]"

# Check that the number of arguments is 3
if [ $# -lt 3 ]; then
    echo "$ERROR Need three arguments: Filename Start-line End-line"
    exit 1
fi

# Check that the file exists.
if [ ! -f "$FILENAME" ]; then
    echo -e "$ERROR File does not exist. \n\t$FILENAME"
    exit 1
fi

# Check that start-line is not greater than end-line
if [ "$START" -gt "$END" ]; then
    echo -e "$ERROR Start line is greater than End line."
    exit 1
fi

# Check that start-line is positive.
if [ "$START" -lt 0 ]; then
    echo -e "$ERROR Start line is less than 0."
    exit 1
fi

# Check that end-line is positive.
if [ "$END" -lt 0 ]; then
    echo -e "$ERROR End line is less than 0."
    exit 1
fi

NUMOFLINES=$(wc -l < "$FILENAME")

# Check that end-line is not greater than the number of lines in the file.
if [ "$END" -gt "$NUMOFLINES" ]; then
    echo -e "$ERROR End line is greater than number of lines in file."
    exit 1
fi

# The distance from the end of the file to end-line
ENDDIFF=$(( NUMOFLINES - END ))

# For larger files, this will run more quickly. If the distance from the
# end of the file to the end-line is less than the distance from the
# start of the file to the start-line, then start pinching from the
# bottom as opposed to the top.
if [ "$START" -lt "$ENDDIFF" ]; then
    < "$FILENAME" head -n $END | tail -n +$START
else
    < "$FILENAME" tail -n +$START | head -n $(( END-START+1 ))
fi

# Success
exit 0

1
Điều này chậm hơn nhiều lựa chọn thay thế vì nó sử dụng 2 lệnh trong đó 1 là đủ. Trong thực tế, nó đọc tệp hai lần vì wclệnh này gây lãng phí băng thông đĩa, đặc biệt là trên các tệp gigabyte. Trong tất cả các cách, đây là tài liệu tốt, nhưng nó cũng là kỹ thuật quá mức cần thiết.
Jonathan Leffler

1

Điều này có thể làm việc cho bạn (GNU sed):

sed -ne '16224,16482w newfile' -e '16482q' file

hoặc lợi dụng bash:

sed -n $'16224,16482w newfile\n16482q' file

1

Sử dụng ed:

ed -s infile <<<'16224,16482p'

-sngăn chặn đầu ra chẩn đoán; các lệnh thực tế nằm trong một chuỗi ở đây. Cụ thể, 16224,16482pchạy lệnh p(in) trên phạm vi địa chỉ dòng mong muốn.


0

Các -n trong câu trả lời chấp nhận làm việc. Đây là một cách khác trong trường hợp bạn nghiêng.

cat $filename | sed "${linenum}p;d";

Điều này thực hiện như sau:

  1. ống trong nội dung của tệp (hoặc nguồn cấp dữ liệu trong văn bản theo cách bạn muốn).
  2. sed chọn dòng đã cho, in nó
  3. d được yêu cầu xóa các dòng, nếu không sed sẽ cho rằng tất cả các dòng cuối cùng sẽ được in. tức là, không có d, bạn sẽ nhận được tất cả các dòng được in bởi dòng đã chọn được in hai lần vì bạn có phần $ {linenum} p yêu cầu in. Tôi khá chắc chắn rằng - về cơ bản là làm điều tương tự như d ở đây.

3
lưu ý cat file | sedđược viết tốt hơn làsed file
fedorqui 'SO ngừng gây hại'

Ngoài ra, điều này chỉ in một dòng, trong khi câu hỏi là về một phạm vi của chúng.
fedorqui 'SO ngừng làm hại'

0

Vì chúng ta đang nói về việc trích xuất các dòng văn bản từ một tệp văn bản, tôi sẽ đưa ra một trường hợp đặc biệt khi bạn muốn trích xuất tất cả các dòng khớp với một mẫu nhất định.

myfile content:
=====================
line1 not needed
line2 also discarded
[Data]
first data line
second data line
=====================
sed -n '/Data/,$p' myfile

Sẽ in dòng [Dữ liệu] và phần còn lại. Nếu bạn muốn văn bản từ dòng1 đến mẫu, bạn nhập: sed -n '1, / Data / p' myfile. Hơn nữa, nếu bạn biết hai mẫu (tốt hơn là duy nhất trong văn bản của bạn), cả dòng đầu và cuối của phạm vi có thể được chỉ định bằng các kết quả khớp.

sed -n '/BEGIN_MARK/,/END_MARK/p' myfile
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.