unix - đầu VÀ đuôi của tập tin


131

Giả sử bạn có tệp txt, lệnh nào để xem đồng thời 10 dòng trên cùng và 10 dòng dưới cùng của tệp?

tức là nếu tệp dài 200 dòng, thì xem các dòng 1-10 và 190-200 trong một lần.


Bạn có ý nghĩa gì "trong một lần"?
cnicutar

@cnicutar tức là. không truy cập tệp -10 để xem dữ liệu và sau đó tách riêng tệp đuôi -10 và xem dữ liệu
lên

@toop Nếu bạn muốn có một ví dụ hoạt động thực sự, hãy xem stackoverflow.com/a/44849814/99834
sorin

Câu trả lời:


208

Bạn có thể chỉ cần:

(head; tail) < file.txt

Và nếu bạn cần sử dụng đường ống vì một số lý do thì như thế này:

cat file.txt | (head; tail)

Lưu ý: sẽ in các dòng trùng lặp nếu số dòng trong file.txt nhỏ hơn các dòng đầu mặc định + các dòng đuôi mặc định.


54
Nói một cách chính xác, điều này không cung cấp cho bạn phần đuôi của tệp gốc, nhưng phần đuôi của luồng sau headđã tiêu thụ 10 dòng đầu tiên của tệp. (So ​​sánh điều này với head < file.txt; tail < file.txttrên một tệp có ít hơn 20 dòng). Chỉ là một điểm rất nhỏ để ghi nhớ. (Nhưng vẫn là +1.)
chepner

15
Đẹp. Nếu bạn muốn có một khoảng cách giữa phần đầu và phần đuôi: (head; echo; tail) <file.txt
Simon Hibbs

3
Tò mò về lý do tại sao / làm thế nào điều này hoạt động. Đã hỏi nó như một câu hỏi mới: stackoverflow.com/questions/13718242
zellyn

9
@namET Thật ra, bạn thậm chí có thể không nhận được nhiều như vậy. Mặc dù headchỉ hiển thị 10 dòng đầu tiên của đầu vào, nhưng không có gì đảm bảo rằng nó không tiêu thụ nhiều hơn để tìm dòng thứ 10 kết thúc, để lại ít đầu vào lessđể hiển thị.
chepner

20
Xin lỗi để nói, nhưng câu trả lời chỉ hoạt động trong một số trường hợp. seq 100 | (head; tail)chỉ cho tôi 10 số đầu tiên. Chỉ trên kích thước đầu vào lớn hơn nhiều (như seq 2000), đuôi mới có được một số đầu vào.
mô-đun

18

edstandard text editor

$ echo -e '1+10,$-10d\n%p' | ed -s file.txt

2
Nếu tệp có nhiều hơn hoặc ít hơn 200 dòng thì sao? Và bạn không biết số lượng dòng ab initio?
Paul

@Paul Tôi đã đổi sedthànhed
kev

14

Đối với một luồng thuần (ví dụ: đầu ra từ một lệnh), bạn có thể sử dụng 'tee' để rẽ nhánh luồng và gửi một luồng đến đầu và một luồng tới đuôi. Điều này yêu cầu sử dụng tính năng '> (danh sách)' của bash (+ / dev / fd / N):

( COMMAND | tee /dev/fd/3 | head ) 3> >( tail )

hoặc sử dụng / dev / fd / N (hoặc / dev / stderr) cộng với các chuỗi con với chuyển hướng phức tạp:

( ( seq 1 100 | tee /dev/fd/2 | head 1>&3 ) 2>&1 | tail ) 3>&1
( ( seq 1 100 | tee /dev/stderr | head 1>&3 ) 2>&1 | tail ) 3>&1

(Cả hai thứ này sẽ không hoạt động trong csh hoặc tcsh.)

Đối với một cái gì đó có kiểm soát tốt hơn một chút, bạn có thể sử dụng lệnh perl này:

COMMAND | perl -e 'my $size = 10; my @buf = (); while (<>) { print if $. <= $size; push(@buf, $_); if ( @buf > $size ) { shift(@buf); } } print "------\n"; print @buf;'

1
+1 để hỗ trợ truyền phát. Bạn có thể sử dụng lại stderr:COMMAND | { tee >(head >&2) | tail; } |& other_commands
jfs

2
btw, nó phá vỡ các tệp lớn hơn kích thước bộ đệm (8K trên hệ thống của tôi). cat >/dev/nullsửa nó:COMMAND | { tee >(head >&2; cat >/dev/null) | tail; } |& other_commands
jfs

Tôi yêu các giải pháp, nhưng sau khi chơi bình đẳng cho aa trong khi tôi nhận thấy rằng trong một số trường hợp đuôi chạy trước khi người đứng đầu ... có được không được bảo đảm trật tự giữa headtaillệnh: \ ...
Jan

7
(sed -u 10q; echo ...; tail) < file.txt

Chỉ là một biến thể khác của (head;tail)chủ đề, nhưng tránh vấn đề điền vào bộ đệm ban đầu cho các tệp nhỏ.


4

head -10 file.txt; tail -10 file.txt

Ngoài ra, bạn sẽ cần phải viết chương trình / kịch bản của riêng bạn.


1
Thật tuyệt, tôi đã luôn luôn sử dụng catheadhoặc tailđường ống, thật tốt khi biết rằng tôi có thể sử dụng chúng riêng lẻ!
Paul

Làm thế nào tôi có thể chuyển 10 + 10 đầu tiên này thành một lệnh khác?
lên

1
@Paul - với 'your_program' là wc -l, nó trả về 10 thay vì 20
vào

3
hoặc, mà không phải sinh ra một khung con: { head file; tail file; } | prog(khoảng cách bên trong dấu ngoặc nhọn và dấu chấm phẩy được yêu cầu)
glenn jackman

1
Wow ... một cuộc bỏ phiếu vì có một câu trả lời khá giống với những người khác (chưa được đánh dấu thời gian trước họ) sau gần hai năm, từ một người đã chọn không đăng lý do tại sao họ bỏ phiếu. Đẹp!
mah

4

Dựa trên nhận xét của JF Sebastian :

cat file | { tee >(head >&3; cat >/dev/null) | tail; } 3>&1

Bằng cách này, bạn có thể xử lý dòng đầu tiên và phần còn lại khác nhau trong một ống, rất hữu ích khi làm việc với dữ liệu CSV:

{ echo N; seq 3;} | { tee >(head -n1 | sed 's/$/*2/' >&3; cat >/dev/null) | tail -n+2 | awk '{print $1*2}'; } 3>&1
N * 2
2
4
6

3

vấn đề ở đây là các chương trình hướng luồng không biết trước độ dài của tệp (vì có thể không có tệp nào, nếu đó là luồng thực).

các công cụ như tailđệm n dòng cuối cùng nhìn thấy và đợi đến cuối luồng, sau đó in.

nếu bạn muốn thực hiện điều này trong một lệnh duy nhất (và để nó hoạt động với bất kỳ phần bù nào và không lặp lại các dòng nếu chúng trùng nhau), bạn sẽ phải mô phỏng hành vi này mà tôi đã đề cập.

thử cái này đi

awk -v offset=10 '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' yourfile

nó cần nhiều công việc hơn để tránh các vấn đề khi độ lệch lớn hơn tệp
Samus_

Yay, điều này hoạt động với đầu ra đường ống, không chỉ các tệp: a.out | awk -v ...
Camille Goudeseune

thực sự :) nhưng đó là hành vi bình thường của awk, hầu hết các chương trình dòng lệnh hoạt động trên stdin khi được gọi mà không có đối số.
Samus_

1
Rất gần với hành vi mong muốn nhưng dường như với <10 dòng, nó có thêm các dòng mới.
sorin

3

Phải mất rất nhiều thời gian để kết thúc với giải pháp này, dường như là giải pháp duy nhất bao gồm tất cả các trường hợp sử dụng (cho đến nay):

command | tee full.log | stdbuf -i0 -o0 -e0 awk -v offset=${MAX_LINES:-200} \
          '{
               if (NR <= offset) print;
               else {
                   a[NR] = $0;
                   delete a[NR-offset];
                   printf "." > "/dev/stderr"
                   }
           }
           END {
             print "" > "/dev/stderr";
             for(i=NR-offset+1 > offset ? NR-offset+1: offset+1 ;i<=NR;i++)
             { print a[i]}
           }'

Danh sách tính năng:

  • đầu ra trực tiếp cho đầu (rõ ràng là cho đuôi là không thể)
  • không sử dụng các tập tin bên ngoài
  • thanh tiến trình một dấu chấm cho mỗi dòng sau MAX_LINES, rất hữu ích cho các tác vụ chạy dài.
  • thanh tiến trình trên thiết bị xuất chuẩn, đảm bảo rằng các dấu chấm tiến trình được tách ra khỏi đầu + đuôi (rất tiện dụng nếu bạn muốn ống tiêu chuẩn)
  • tránh thứ tự ghi nhật ký không chính xác có thể do bộ đệm (stdbuf)
  • tránh trùng lặp đầu ra khi tổng số dòng nhỏ hơn đầu + đuôi.

2

Tôi đã tìm kiếm giải pháp này trong một thời gian. Đã thử nó với sed, nhưng vấn đề không biết chiều dài của tập tin / luồng trước đó là không thể vượt qua. Trong tất cả các tùy chọn có sẵn ở trên, tôi thích giải pháp awk của Camille Goudeseune. Anh ta đã lưu ý rằng giải pháp của anh ta để lại các dòng trống thừa ở đầu ra với một bộ dữ liệu đủ nhỏ. Ở đây tôi cung cấp một sửa đổi của giải pháp của mình mà loại bỏ các dòng thêm.

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { a_count=0; for (i in a) {a_count++}; for (i=NR-a_count+1; i<=NR; i++) print a[i] }' ; }

1

Chà, bạn luôn có thể xâu chuỗi chúng lại với nhau. Giống như vậy , head fiename_foo && tail filename_foo. Nếu điều đó là không đủ, bạn có thể tự viết cho mình một hàm bash trong tệp .profile hoặc bất kỳ tệp đăng nhập nào bạn sử dụng:

head_and_tail() {
    head $1 && tail $1
}

Và, sau đó gọi nó từ dấu nhắc shell của bạn : head_and_tail filename_foo.


1

10 dòng đầu tiên của file.ext, sau đó là 10 dòng cuối cùng của nó:

cat file.ext | head -10 && cat file.ext | tail -10

10 dòng cuối cùng của tệp, sau đó là 10 dòng đầu tiên:

cat file.ext | tail -10 && cat file.ext | head -10

Sau đó, bạn cũng có thể dẫn đầu ra ở nơi khác:

(cat file.ext | head -10 && cat file.ext | tail -10 ) | your_program


5
Tại sao nên sử dụng cat khi bạn chỉ có thể gọi head -10 file.txt?
jstarek

Bạn có thể làm cho số lượng dòng biến, vì vậy cuộc gọi là một cái gì đó như: head_ tail (foo, m, n) - trả về m snd đầu tiên của n dòng văn bản?
ricardo

@ricardo sẽ liên quan đến việc viết một tập lệnh bash có 3 đối số và chuyển chúng đến tailheadhoặc một hàm bằng cách đặt bí danh cho nó.
Paul


1

dựa trên các ý tưởng trên (bash & zsh đã thử nghiệm)

nhưng sử dụng đầu và đuôi bí danh

alias hat='(head -5 && echo "^^^------vvv" && tail -5) < '


hat large.sql

0

Tại sao không sử dụng sedcho nhiệm vụ này?

sed -n -e 1,+9p -e 190,+9p textfile.txt


3
Điều này hoạt động cho các tệp có độ dài đã biết, nhưng không phải các tệp có độ dài không xác định.
Kevin

0

Để xử lý các đường ống (luồng) cũng như các tệp, hãy thêm tệp này vào tệp .bashrc hoặc .profile của bạn:

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' ; }

Sau đó, bạn không thể chỉ

headtail 10 < file.txt

nhưng cũng

a.out | headtail 10

(Điều này vẫn nối các dòng trống giả khi 10 vượt quá độ dài của đầu vào, không giống như cũ a.out | (head; tail). Cảm ơn bạn, những người trả lời trước.)

Lưu ý : headtail 10, không headtail -10.


0

Dựa trên những gì @Samus_ đã giải thích ở đây về cách hoạt động của lệnh @Aleksandra Zalcman, biến thể này rất hữu ích khi bạn không thể nhanh chóng phát hiện ra nơi đuôi bắt đầu mà không cần đếm dòng.

{ head; echo "####################\n...\n####################"; tail; } < file.txt

Hoặc nếu bạn bắt đầu làm việc với thứ gì đó ngoài 20 dòng, số lượng dòng thậm chí có thể giúp ích.

{ head -n 18; tail -n 14; } < file.txt | cat -n

0

Để in 10 dòng đầu tiên và 10 dòng cuối cùng của tệp, bạn có thể thử điều này:

cat <(head -n10 file.txt) <(tail -n10 file.txt) | less


0
sed -n "1,10p; $(( $(wc -l ${aFile} | grep -oE "^[[:digit:]]+")-9 )),\$p" "${aFile}"

LƯU Ý : Biến aFile chứa đường dẫn đầy đủ của tệp .


0

Tôi sẽ nói rằng tùy thuộc vào kích thước của tập tin, việc chủ động đọc nội dung của nó có thể không được mong muốn. Trong hoàn cảnh đó, tôi nghĩ rằng một số kịch bản shell đơn giản nên đủ.

Đây là cách gần đây tôi đã xử lý việc này cho một số tệp CSV rất lớn mà tôi đang phân tích:

$ for file in *.csv; do echo "### ${file}" && head ${file} && echo ... && tail ${file} && echo; done

Điều này in ra 10 dòng đầu tiên và 10 dòng cuối cùng của mỗi tệp, đồng thời in ra tên tệp và một số dấu chấm lửng trước và sau.

Đối với một tệp lớn, bạn có thể chỉ cần chạy như sau để có cùng hiệu quả:

$ head somefile.csv && echo ... && tail somefile.csv

0

Tiêu thụ stdin, nhưng đơn giản và hoạt động cho 99% trường hợp sử dụng

đầu và đuôi

#!/usr/bin/env bash
COUNT=${1:-10}
IT=$(cat /dev/stdin)
echo "$IT" | head -n$COUNT
echo "..."
echo "$IT" | tail -n$COUNT

thí dụ

$ seq 100 | head_and_tail 4
1
2
3
4
...
97
98
99
100
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.