Làm cách nào tôi có thể xóa dòng đầu tiên của tệp văn bản bằng tập lệnh bash / sed?


554

Tôi cần phải liên tục xóa dòng đầu tiên khỏi một tệp văn bản lớn bằng cách sử dụng tập lệnh bash.

Ngay bây giờ tôi đang sử dụng sed -i -e "1d" $FILE- nhưng phải mất khoảng một phút để xóa.

Có cách nào hiệu quả hơn để thực hiện điều này?


-i có nghĩa là gì?
cikatomo

4
@cikatomo: viết tắt của chỉnh sửa nội tuyến - nó chỉnh sửa tệp với bất cứ thứ gì bạn tạo.
drewrockshard

4
Đuôi là RẤT NHIỀU hơn sed. Đuôi cần 13,5s, sed cần 0,85s. Tệp của tôi có ~ 1M dòng, ~ 100MB. MacBook Air 2013 với SSD.
jcsahnwaldt nói GoFundMonica

Câu trả lời:


1029

Thử đuôi :

tail -n +2 "$FILE"

-n x: Chỉ cần in những xdòng cuối cùng . tail -n 5sẽ cung cấp cho bạn 5 dòng cuối cùng của đầu vào. Các +loại dấu hiệu đảo ngược đối số và thực hiện tailin bất cứ điều gì ngoại trừ các x-1dòng đầu tiên . tail -n +1sẽ in toàn bộ tập tin, tail -n +2mọi thứ trừ dòng đầu tiên, v.v.

GNU tailnhanh hơn nhiều sed. tailcũng có sẵn trên BSD và -n +2cờ phù hợp trên cả hai công cụ. Kiểm tra các trang man FreeBSD hoặc OS X để biết thêm.

Phiên bản BSD có thể chậm hơn nhiều sed, mặc dù. Tôi tự hỏi làm thế nào họ quản lý điều đó; tailchỉ nên đọc từng dòng tệp trong khi sedthực hiện các thao tác khá phức tạp liên quan đến việc diễn giải một tập lệnh, áp dụng các biểu thức thông thường và tương tự.

Lưu ý: Bạn có thể bị cám dỗ để sử dụng

# THIS WILL GIVE YOU AN EMPTY FILE!
tail -n +2 "$FILE" > "$FILE"

nhưng điều này sẽ cung cấp cho bạn một tập tin trống . Lý do là việc chuyển hướng ( >) xảy ra trước đó tailđược gọi bởi shell:

  1. Shell cắt ngắn tập tin $FILE
  2. Shell tạo ra một quy trình mới cho tail
  3. Shell chuyển hướng xuất sắc của tailquá trình để$FILE
  4. tail đọc từ bây giờ trống rỗng $FILE

Nếu bạn muốn xóa dòng đầu tiên bên trong tệp, bạn nên sử dụng:

tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"

Các &&sẽ đảm bảo rằng các tập tin không bị ghi đè khi có một vấn đề.


3
Theo ss64.com/bash/tail.html bộ đệm thông thường này mặc định là 32k khi sử dụng 'đuôi' BSD với -rtùy chọn. Có lẽ có một thiết lập bộ đệm ở đâu đó trong hệ thống? Hoặc -nlà một số có chữ ký 32 bit?
Yzmir Ramirez

41
@Eddie: user869097 cho biết nó không hoạt động khi một dòng duy nhất là 15Mb trở lên. Miễn là các dòng ngắn hơn, tailsẽ làm việc cho bất kỳ kích thước tệp.
Aaron Digulla

6
bạn có thể giải thích những tranh luận này?
Dreampuf

17
@Dreampuf - từ trang người đàn ông:-n N means output the last N lines, instead of the last 10; or use +N to output lines starting with the Nth
Will Sheppard

11
Tôi sẽ đồng tình với @JonaChristopherSahnwaldt - đuôi rất nhiều, chậm hơn nhiều so với biến thể sed, theo một độ lớn. Tôi đang thử nghiệm nó trên một tệp gồm 500.000K dòng (không quá 50 ký tự trên mỗi dòng). Tuy nhiên, sau đó tôi nhận ra rằng tôi đang sử dụng phiên bản đuôi FreeBSD (đi kèm với OS X theo mặc định). Khi tôi chuyển sang đuôi GNU, cuộc gọi đuôi nhanh hơn 10 lần so với cuộc gọi sed (và cuộc gọi sed của GNU cũng vậy). AaronDigulla là chính xác ở đây, nếu bạn đang sử dụng GNU.
Dân Nguyễn

179

Bạn có thể sử dụng -i để cập nhật tệp mà không cần sử dụng toán tử '>'. Lệnh sau sẽ xóa dòng đầu tiên khỏi tệp và lưu nó vào tệp.

sed -i '1d' filename

1
Tôi gặp lỗi:unterminated transform source string
Daniel Kobe

10
điều này hoạt động mọi lúc và nên thực sự là câu trả lời hàng đầu!
xtheking

4
Chỉ cần nhớ, Mac yêu cầu một hậu tố được cung cấp khi sử dụng sed với các chỉnh sửa tại chỗ. Vì vậy, hãy chạy phần trên với -i.bak
mjp

3
Chỉ cần một lưu ý - để xóa một số dòng sử dụngsed -i '1,2d' filename
Bố già

4
Phiên bản này thực sự dễ đọc hơn và phổ quát hơn tail -n +2. Không chắc chắn tại sao nó không phải là câu trả lời hàng đầu.
Luke Davis

74

Đối với những người dùng SunOS không phải là GNU, đoạn mã sau sẽ giúp:

sed '1d' test.dat > tmp.dat 

18
Nhân khẩu học thú vị
đội trưởng

17

Không, đó là về hiệu quả như bạn sẽ nhận được. Bạn có thể viết chương trình C có thể thực hiện công việc nhanh hơn một chút (thời gian khởi động và xử lý đối số ít hơn) nhưng nó có thể sẽ có xu hướng với tốc độ tương tự như sed khi các tệp trở nên lớn (và tôi cho rằng chúng lớn nếu mất một phút ).

Nhưng câu hỏi của bạn gặp phải vấn đề tương tự như rất nhiều người khác ở chỗ nó đưa ra giải pháp trước. Nếu bạn muốn nói với chúng tôi chi tiết những gì bạn đang cố gắng thực hiện thì làm thế nào , chúng tôi có thể đề xuất một lựa chọn tốt hơn.

Ví dụ: nếu đây là tệp A mà một số chương trình B khác xử lý, một giải pháp sẽ là không loại bỏ dòng đầu tiên, nhưng sửa đổi chương trình B để xử lý nó theo cách khác.

Giả sử tất cả các chương trình của bạn nối vào tệp A và chương trình B hiện đang đọc và xử lý dòng đầu tiên trước khi xóa nó.

Bạn có thể thiết kế lại chương trình B để nó không cố xóa dòng đầu tiên nhưng vẫn duy trì phần bù (có thể dựa trên tệp) liên tục vào tệp A để lần sau chạy, nó có thể tìm cách bù đó, xử lý dòng ở đó, và cập nhật phần bù.

Sau đó, tại một thời điểm yên tĩnh (nửa đêm?), Nó có thể xử lý đặc biệt tệp A để xóa tất cả các dòng hiện đang xử lý và đặt giá trị bù về 0.

Nó chắc chắn sẽ nhanh hơn cho một chương trình để mở và tìm kiếm một tệp chứ không phải mở và viết lại. Thảo luận này giả định rằng bạn có quyền kiểm soát chương trình B, tất nhiên. Tôi không biết nếu đó là trường hợp nhưng có thể có các giải pháp khả thi khác nếu bạn cung cấp thêm thông tin.


Tôi nghĩ OP đang cố gắng đạt được điều khiến tôi tìm thấy câu hỏi này. Tôi có 10 tệp CSV với mỗi dòng 500k. Mỗi tệp có hàng tiêu đề giống như dòng đầu tiên. Tôi là mèo: ing các tệp này vào một tệp và sau đó nhập chúng vào DB cho phép DB tạo tên cột từ dòng đầu tiên. Rõ ràng tôi không muốn dòng đó lặp lại trong tệp 2-10.
db

1
@db Trong trường hợp đó, awk FNR-1 *.csvcó lẽ nhanh hơn.
jinawee

10

Bạn có thể chỉnh sửa các tệp tại chỗ: Chỉ cần sử dụng -icờ của perl , như thế này:

perl -ni -e 'print unless $. == 1' filename.txt

Điều này làm cho dòng đầu tiên biến mất, như bạn yêu cầu. Perl sẽ cần đọc và sao chép toàn bộ tệp, nhưng nó sắp xếp để đầu ra được lưu dưới tên của tệp gốc.


10

Bạn có thể dễ dàng làm điều này với:

cat filename | sed 1d > filename_without_first_line

trên dòng lệnh; hoặc để xóa dòng đầu tiên của tệp vĩnh viễn, hãy sử dụng chế độ tại chỗ của sed với -icờ:

sed -i 1d <filename>

9

Như Pax đã nói, có lẽ bạn sẽ không nhận được bất kỳ nhanh hơn thế này. Lý do là hầu như không có hệ thống tệp nào hỗ trợ cắt xén từ đầu tệp nên đây sẽ là nthao tác O ( ) trong đó nkích thước của tệp. Những gì bạn có thể làm nhanh hơn nhiều mặc dù ghi đè lên dòng đầu tiên có cùng số byte (có thể có khoảng trắng hoặc nhận xét) có thể phù hợp với bạn tùy thuộc vào chính xác những gì bạn đang cố gắng thực hiện (đó là gì?).


Re "... hầu như không có hệ thống tập tin nào hỗ trợ cắt ngắn ..." : thật thú vị; vui lòng xem xét bao gồm một ghi chú chính xác đặt tên một hệ thống tập tin như vậy.
agc

1
@agc: bây giờ không liên quan, nhưng công việc đầu tiên của tôi trong thập niên 70 là với Quadex, một công ty khởi nghiệp nhỏ (hiện đã ra đi và không liên quan đến hai công ty hiện đang sử dụng tên đó). Họ có một hệ thống tệp cho phép thêm hoặc xóa ở đầu hoặc cuối tệp, được sử dụng chủ yếu để thực hiện chỉnh sửa trong ít hơn 3KB bằng cách đặt cửa sổ bên trên và cửa sổ bên dưới vào tệp. Nó không có tên của riêng nó, nó chỉ là một phần của QMOS, Hệ điều hành Quadex Multiuser. ('Đa' thường là 2-3 trên LSI-11/02 với RAM dưới 64KB và thường là một vài đĩa mềm 8 "loại RX01 mỗi 250KB.) :-)
dave_thedom_085 24/11/19

9

Các spongeutil tránh sự cần thiết cho tung hứng một tập tin temp:

tail -n +2 "$FILE" | sponge "$FILE"

spongethực sự sạch sẽ và mạnh mẽ hơn nhiều so với giải pháp được chấp nhận ( tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE")
Jealie

1
Cần phải làm rõ rằng 'miếng bọt biển' yêu cầu gói 'moreutils' được cài đặt.
FedFranzoni

Đây là giải pháp duy nhất giúp tôi thay đổi tệp hệ thống (trên hình ảnh docker Debian). Các giải pháp khác không thành công do lỗi "Thiết bị hoặc tài nguyên bận" khi cố gắng ghi tệp.
FedFranzoni

Nhưng có spongeđệm toàn bộ tập tin trong bộ nhớ? Điều đó sẽ không hoạt động nếu hàng trăm GB.
OrangeDog

@OrangeDog, miễn là hệ thống tệp có thể lưu trữ nó, spongesẽ ngâm nó lên, vì nó sử dụng tệp / tmp làm bước trung gian, sau đó được sử dụng để thay thế bản gốc sau đó.
agc

8

Nếu bạn muốn thay đổi các tập tin tại chỗ, bạn luôn có thể sử dụng bản gốc edthay vì nó s kế treaming sed:

ed "$FILE" <<<$'1d\nwq\n'

Các edlệnh là soạn thảo văn bản gốc UNIX, ngay cả trước khi có thiết bị đầu cuối toàn màn hình, máy trạm ít hơn nhiều đồ họa. Các exbiên tập viên, tốt nhất được biết đến như những gì bạn đang sử dụng khi đánh máy tại ruột kết trong cửa sổ vi, là một cựu phiên bản chăm sóc của ed, rất nhiều công việc lệnh tương tự. Mặc dù edcó nghĩa là được sử dụng tương tác, nó cũng có thể được sử dụng trong chế độ hàng loạt bằng cách gửi một chuỗi lệnh đến nó, đó là những gì giải pháp này làm.

Chuỗi <<<$'1d\nwq\n'tận dụng sự hỗ trợ Bash cho đây-strings ( <<<) và dấu ngoặc kép POSIX ( $'... ') để đầu vào thức ăn cho edlệnh bao gồm hai dòng: 1d, mà d eletes dòng 1 , và sau đó wq, trong đó w nghi thức các tập tin trở lại ra đĩa và sau đó q uits phiên chỉnh sửa.


Đây là thanh lịch. +1
Armin

Nhưng bạn phải đọc toàn bộ tập tin vào bộ nhớ, sẽ không hoạt động nếu nó có hàng trăm GB.
OrangeDog

5

sẽ hiển thị các dòng ngoại trừ dòng đầu tiên:

cat textfile.txt | tail -n +2

4
- bạn nên làm "đuôi -n +2 textfile.txt"
niglesias

5
@niglesiais Tôi không đồng ý với "việc sử dụng mèo vô dụng", vì rõ ràng giải pháp này là ổn đối với nội dung được xử lý và không chỉ các tệp.
Titou

5

Có thể sử dụng vim để làm điều này:

vim -u NONE +'1d' +'wq!' /tmp/test.txt

Điều này sẽ nhanh hơn, vì vim sẽ không đọc toàn bộ tệp khi xử lý.


Có thể cần phải trích dẫn +wq!nếu vỏ của bạn là bash. Có lẽ không phải vì !không phải là bắt đầu của một từ, nhưng có được thói quen trích dẫn mọi thứ có lẽ là tốt xung quanh. (Và nếu bạn đang sử dụng siêu hiệu quả bằng cách không trích dẫn một cách không cần thiết, bạn cũng không cần các trích dẫn xung quanh 1d.)
Mark Reed

vim không cần phải đọc toàn bộ tập tin. Trong thực tế nếu tệp lớn hơn bộ nhớ, như được hỏi trong Q này, vim sẽ đọc toàn bộ tệp và ghi nó (hoặc hầu hết tệp) vào tệp tạm thời và sau khi chỉnh sửa ghi lại tất cả (vào tệp vĩnh viễn). Tôi không biết làm thế nào bạn nghĩ rằng nó có thể làm việc mà không có điều này.
dave_thndry_085

4

Làm thế nào về việc sử dụng csplit?

man csplit
csplit -k file 1 '{1}'

Cú pháp này cũng sẽ hoạt động, nhưng chỉ tạo hai tệp đầu ra thay vì ba : csplit file /^.*$/1. Hoặc đơn giản hơn : csplit file //1. Hoặc thậm chí đơn giản hơn : csplit file 2.
Marco Roy

1

Vì có vẻ như tôi không thể tăng tốc độ xóa, tôi nghĩ rằng một cách tiếp cận tốt có thể là xử lý tệp theo lô như thế này:

While file1 not empty
  file2 = head -n1000 file1
  process file2
  sed -i -e "1000d" file1
end

Hạn chế của điều này là nếu chương trình bị giết ở giữa (hoặc nếu có một số sql xấu ở đó - làm cho phần "process" bị chết hoặc bị khóa), sẽ có các dòng bị bỏ qua hoặc xử lý hai lần .

(file1 chứa các dòng mã sql)


Dòng đầu tiên chứa gì? Bạn có thể ghi đè lên nó bằng một bình luận sql như tôi đề nghị trong bài viết của tôi không?
Robert Gamble

0

Nếu những gì bạn đang muốn làm là phục hồi sau thất bại, bạn có thể xây dựng một tệp có những gì bạn đã làm cho đến nay.

if [[ -f $tmpf ]] ; then
    rm -f $tmpf
fi
cat $srcf |
    while read line ; do
        # process line
        echo "$line" >> $tmpf
    done

0

Điều này một lót sẽ làm:

echo "$(tail -n +2 "$FILE")" > "$FILE"

Nó hoạt động, vì tailđược thực thi trước echovà sau đó tệp được mở khóa, do đó không cần tệp tạm thời.


-1

Việc sử dụng đuôi trên các dòng N-1 và hướng nó vào một tệp, sau đó xóa tệp cũ và đổi tên tệp mới thành tên cũ có thực hiện được công việc không?

Nếu tôi đang làm điều này theo chương trình, tôi sẽ đọc qua tệp và nhớ phần bù tệp, sau khi đọc từng dòng, vì vậy tôi có thể tìm lại vị trí đó để đọc tệp có một dòng ít hơn.


Giải pháp đầu tiên về cơ bản là giống hệt với Brent hiện đang làm. Tôi không hiểu cách tiếp cận theo chương trình của bạn, chỉ cần xóa dòng đầu tiên, bạn chỉ cần đọc và loại bỏ dòng đầu tiên và sao chép phần còn lại vào một tệp khác giống như cách tiếp cận sed và tail.
Robert Gamble

Giải pháp thứ hai có hàm ý rằng tập tin không bị thu hẹp bởi dòng đầu tiên mỗi lần. Chương trình chỉ đơn giản là xử lý nó, như thể nó đã bị thu hẹp, nhưng bắt đầu từ dòng tiếp theo mỗi lần
EvilTeach

Tôi vẫn không hiểu giải pháp thứ hai của bạn là gì.
Robert Gamble
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.