Lệnh hiển thị vài dòng đầu tiên và vài dòng cuối cùng của tệp


23

Tôi có một tệp có nhiều hàng và mỗi hàng có dấu thời gian ở đầu, như

[Thread-3] (21/09/12 06:17:38:672) logged message from code.....

Vì vậy, tôi thường xuyên kiểm tra 2 thứ từ tệp nhật ký này.

  1. Một vài hàng đầu tiên, có các điều kiện toàn cầu và thời gian bắt đầu cũng được đưa ra.
  2. Một vài hàng cuối cùng, có trạng thái thoát với một số thông tin khác.

Có bất kỳ lệnh đơn tiện dụng nhanh chóng nào có thể cho phép tôi chỉ hiển thị vài dòng đầu tiên và cuối cùng của một tệp không?


2
Điều kiện toàn cầu là gì, và không head and taillàm việc cho bạn?
cúc

Đó là một phần của tệp nhật ký của tôi. Tôi đã cố gắng để được xây dựng. Bạn có thể bỏ qua điều đó.
mtk

Giải pháp của bạn có vẻ tốt với tôi. Nếu bạn muốn thuận tiện hơn, hãy biến nó thành hàm shell (thậm chí bí danh có thể làm được).
vonbrand

@vonbrand Vấn đề là tôi không biếtN
Bernhard

@Bernhard, tôi không phải là sed(1)chuyên gia, nhưng có nhiều cách để loại bỏ mọi thứ để sử dụng sau này với nó. Có lẽ nó trả hết để nhìn vào đó. OTOH, có lẽ tôi đã sử dụng tập lệnh Perl (hoặc bất cứ điều gì) để làm điều đó nếu được sử dụng thường xuyên, vì tôi quen thuộc hơn với điều đó.
vonbrand

Câu trả lời:


12

Bạn có thể sử dụng sedhoặc awkđể thực hiện nó với một lệnh. Tuy nhiên, bạn sẽ mất tốc độ, nguyên nhân sedawksẽ cần phải chạy qua toàn bộ tệp. Từ quan điểm tốc độ, tốt hơn là tạo một hàm hoặc mỗi lần kết hợp tail+ head. Điều này không có nhược điểm là không hoạt động nếu đầu vào là một đường ống, tuy nhiên bạn có thể sử dụng thay thế Proccess, trong trường hợp shell của bạn hỗ trợ nó (xem ví dụ bên dưới).

first_last () {
    head -n 10 -- "$1"
    tail -n 10 -- "$1"
}

và chỉ khởi chạy nó như

first_last "/path/to/file_to_process"

để tiến hành thay thế quá trình (chỉ bash, zsh, ksh như shell):

first_last <( command )

ps. bạn thậm chí có thể thêm một grepđể kiểm tra xem "điều kiện toàn cầu" của bạn có tồn tại không.


-n 10là mặc định, không?
l0b0

@ l0b0 có, nó mặc định. -n 10không cần thiết ở đây
vội vàng

20

@rush đúng về việc sử dụng head + tail hiệu quả hơn cho các tệp lớn, nhưng đối với các tệp nhỏ (<20 dòng), một số dòng có thể được xuất hai lần.

{ head; tail;} < /path/to/file

sẽ hiệu quả như nhau, nhưng sẽ không có vấn đề ở trên.


Ngược lại với giải pháp vội vàng, điều này không hoạt động trong vỏ POSIX.
Marco

2
@Marco Hả? Chỉ các cấu trúc POSIX được sử dụng ở đây. Bạn thấy điều gì sai?
Gilles 'SO- ngừng trở nên xấu xa'

2
@Gilles Tôi đã bỏ lỡ khoảng trắng: {head; tail;} < filehoạt động trong zsh nhưng thất bại trong sh. { head; tail;} < fileluôn luôn làm việc Xin lỗi vì sự ồn ào.
Marco

@Marco, nếu có vấn đề với điều đó, nó sẽ xảy ra headchứ không phải vỏ. POSIX yêu cầu headđể lại con trỏ trong tệp chỉ qua 10 dòng đó cho các tệp thông thường. Một vấn đề có thể phát sinh đối với headviệc triển khai không phải POSIX (phiên bản rất cũ của đầu GNU đã từng không tuân thủ trong trường hợp đó, nhưng chúng ta đang nói chuyện trong nhiều thập kỷ) hoặc nếu tệp không thể tìm kiếm được (như tên ống hoặc ổ cắm, nhưng sau đó giải pháp khác sẽ có cùng một vấn đề).
Stéphane Chazelas

1
@FCTW,sudo sh -c '{ head; tail;} < /path/to/file'
Stéphane Chazelas 10/03/2016

9

Các { head; tail; }giải pháp sẽ không làm việc trên đường ống (hoặc ổ cắm hoặc bất kỳ tập tin không seekable khác) vì headcó thể tiêu thụ quá nhiều dữ liệu như nó đọc bởi khối và không thể tìm lại trên đường ống khả năng rời khỏi con trỏ bên trong tập tin ngoài những gì tailcó ý nghĩa chọn.

Vì vậy, bạn có thể sử dụng một công cụ đọc một ký tự cùng một lúc như shell read(ở đây sử dụng hàm lấy số dòng đầu và dòng đuôi làm đối số).

head_tail() {
  n=0
  while [ "$n" -lt "$1" ]; do
    IFS= read -r line || { printf %s "$line"; break; }
    printf '%s\n' "$line"
    n=$(($n + 1))
  done
  tail -n "${2-$1}"
}
seq 100 | head_tail 5 10
seq 20 | head_tail 5

hoặc thực hiện tailtrong awk chẳng hạn như:

head_tail() {
  awk -v h="$1" -v t="${2-$1}" '
    {l[NR%t]=$0}
    NR<=h
    END{
      n=NR-t+1
      if(n <= h) n = h+1
      for (;n<=NR;n++) print l[n%t]
    }'
}

Với sed:

head_tail() {
  sed -e "1,${1}b" -e :1 -e "$(($1+${2-$1})),\$!{N;b1" -e '}' -e 'N;D'
}

(mặc dù hãy cẩn thận rằng một số sedtriển khai có giới hạn thấp về kích thước của không gian mẫu của chúng, do đó sẽ thất bại đối với các giá trị lớn của số lượng dòng đuôi).


4

Sử dụng bashthay thế quá trình, bạn có thể làm như sau:

make_some_output | tee >(tail -n 2) >(head -n 2; cat >/dev/null) >/dev/null

Lưu ý rằng các dòng không được đảm bảo theo thứ tự, mặc dù đối với các tệp dài hơn khoảng 8kB, chúng rất có thể sẽ có. Điểm cắt 8kB này là kích thước điển hình của bộ đệm đọc và có liên quan đến lý do | {head; tail;}không hoạt động đối với các tệp nhỏ.

Điều cat >/dev/nullcần thiết là giữ cho headđường ống sống. Nếu không thì teesẽ thoát sớm và trong khi bạn sẽ nhận được đầu ra từ tailđó, thì nó sẽ đến từ một nơi nào đó ở giữa đầu vào, thay vì kết thúc.

Cuối cùng, tại sao >/dev/nullthay vì, nói, di chuyển tailsang người khác |? Trong trường hợp sau:

make_some_output | tee >(head -n 2; cat >/dev/null) | tail -n 2  # doesn't work

headThiết bị xuất chuẩn được đưa vào đường ống tailthay vì bảng điều khiển, đây không phải là thứ chúng ta muốn.


Khi đầu hoặc đuôi kết thúc việc viết đầu ra mà họ muốn, họ đóng stdin và thoát. Đó là nơi SIGPIPE đến từ. Thông thường đây là một điều tốt, họ đang loại bỏ phần còn lại của đầu ra, vì vậy không có lý do gì để phía bên kia của ống tiếp tục dành thời gian để tạo ra nó.
derobert

Điều gì làm cho trật tự có khả năng được duy trì? Nó có thể sẽ dành cho một tệp lớn, vì tailphải làm việc lâu hơn, nhưng tôi mong đợi (và có thấy) nó thất bại khoảng một nửa thời gian cho các đầu vào ngắn.
Gilles 'SO- ngừng trở nên xấu xa'

Bạn sẽ nhận được SIGPIPE với tee >(head) >(tail)cùng một lý do (nhân tiện >(...)là tính năng ksh hiện được hỗ trợ bởi cả zsh và bash) cũng sử dụng các đường ống. Bạn có thể làm ... | (trap '' PIPE; tee >(head) >(tail) > /dev/null)nhưng bạn vẫn sẽ thấy một số thông báo lỗi đường ống bị hỏng từ tee.
Stéphane Chazelas

Trên hệ thống của tôi (bash 4.2.37, coreutils 8.13), tail là cái bị SIGPIPE giết, không tee, và tailkhông được ghi vào đường ống. Vì vậy, nó phải là từ một kill(), phải không? Và điều này chỉ xảy ra khi tôi sử dụng |cú pháp. stracenói rằng teekhông gọi kill()... vậy có lẽ bash?
Jander

1
@Jander, hãy thử cho ăn hơn 8k nhưseq 100000 | tee >(head -n1) >(tail -n1) > /dev/null
Stéphane Chazelas

3

Sử dụng ed(sẽ đọc toàn bộ tệp vào RAM):

# cf. http://wiki.bash-hackers.org/howto/edit-ed
printf '%s\n' 'H' '1,10p' '$-10,$p' 'q' | ed -s file

Ngắn hơn:ed -s file <<< $'11,$-10d\n,p\nq\n'
gọn don_crissti

2

Giải pháp đầu tiên của Stephane trong một hàm để bạn có thể sử dụng các đối số (hoạt động trong bất kỳ shell nào giống như Bourne):

head_tail() {
    head "$@";
    tail "$@";
}

Bây giờ bạn có thể làm điều này:

head_tail -n 5 < /path/to/file

Tất nhiên, điều này giả định rằng bạn chỉ xem một tệp và như giải pháp của Stephane chỉ hoạt động (đáng tin cậy) trên các tệp thông thường (có thể tìm kiếm).


2

Với tùy chọn -u( --unbuffered) của GNU sed, bạn có thể sử dụng sed -u 2qnhư một giải pháp thay thế không có bộ đệm cho head -n2:

$ seq 100|(sed -u 2q;tail -n2)
1
2
99
100

(head -n2;tail -n2)thất bại khi các dòng cuối cùng là một phần của khối đầu vào được sử dụng bởi head:

$ seq 1000|(head -n2;tail -n2)
1
2
999
1000
$ seq 100|(head -n2;tail -n2)
1
2

đây sẽ là câu trả lời hàng đầu hoạt động như một lá bùa!
Ben Usman

1

Tôi đã gặp một cái gì đó như thế này hôm nay khi tôi chỉ cần dòng cuối cùng và một vài dòng từ phía trước của một dòng và đưa ra sau đây.

sed -n -e '1{h}' -e '2,3{H}' -e '${H;x;p}'

Tôi đọc điều này như: khởi tạo không gian giữ với nội dung của dòng đầu tiên, nối các dòng 2-3 trong không gian giữ, tại EOF nối dòng cuối cùng vào không gian giữ, hoán đổi không gian giữ và mẫu và in mẫu không gian.

Có lẽ ai đó có nhiều sed-fu hơn tôi có thể tìm ra cách khái quát hóa điều này để in một vài dòng cuối cùng được chỉ ra trong câu hỏi này nhưng tôi không cần nó và không thể tìm thấy một cách dễ dàng để làm toán dựa trên $địa chỉ trong sedhoặc có lẽ bằng cách quản lý không gian giữ sao cho chỉ có một vài dòng cuối cùng ở trong đó khi EOFđạt được.


1

Bạn có thể thử Perl, nếu bạn đã cài đặt nó:

perl -e '@_ = <>; @_=@_[0, -3..-1]; print @_'

Điều này sẽ làm việc cho hầu hết các tệp, nhưng đọc toàn bộ tệp vào bộ nhớ trước khi xử lý nó. Nếu bạn không quen thuộc với các lát Perl, "0" trong ngoặc vuông có nghĩa là "lấy dòng đầu tiên" và "-3 ...- 1" có nghĩa là "lấy ba dòng cuối". Bạn có thể điều chỉnh cả hai theo nhu cầu của bạn. Nếu bạn cần xử lý các tệp thực sự lớn (cái gì là 'lớn' có thể phụ thuộc vào RAM của bạn và có lẽ kích thước trao đổi), bạn có thể muốn sử dụng:

perl -e 'while($_=<>){@_=(@_,$_)[0,-3..-1]}; print @_'

nó có thể chậm hơn một chút, bởi vì nó tạo ra một lát mỗi lần lặp, nhưng nó độc lập với kích thước tệp.

Cả hai lệnh nên hoạt động cả trong đường ống và với các tệp thông thường.

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.