unix - หัวและส่วนท้ายของไฟล์


131

สมมติว่าคุณมีไฟล์ txt คำสั่งในการดู 10 บรรทัดบนสุดและ 10 บรรทัดล่างพร้อมกันคืออะไร?

เช่นถ้าไฟล์มีความยาว 200 บรรทัดให้ดูบรรทัด 1-10 และ 190-200 ในครั้งเดียว


คุณหมายถึงอะไร "ในครั้งเดียว"?
cnicutar

@cnicutar คือ. ไม่ไปหัว -10 ไฟล์ดูข้อมูลแล้วแยกไฟล์ tail -10 แล้วดูข้อมูล
toop

@toop หากต้องการตัวอย่างการใช้งานจริงโปรดดูที่stackoverflow.com/a/44849814/99834
sorin

คำตอบ:


208

คุณสามารถ:

(head; tail) < file.txt

และหากคุณจำเป็นต้องใช้ท่อด้วยเหตุผลบางประการดังนี้:

cat file.txt | (head; tail)

หมายเหตุ: จะพิมพ์บรรทัดที่ซ้ำกันหากจำนวนบรรทัดใน file.txt มีขนาดเล็กกว่าบรรทัดเริ่มต้นของ head + บรรทัดเริ่มต้นของ tail


54
พูดอย่างเคร่งครัดนี่ไม่ได้ให้หางของไฟล์ต้นฉบับ แต่หางของสตรีมหลังจากheadกินไฟล์ 10 บรรทัดแรกไปแล้ว (เปรียบเทียบกับhead < file.txt; tail < file.txtไฟล์ที่มีน้อยกว่า 20 บรรทัด) เป็นเพียงจุดเล็ก ๆ น้อย ๆ ที่ควรทราบ (แต่ยังคง +1)
chepner

15
ดี หากคุณต้องการช่องว่างระหว่างส่วนหัวและส่วนท้าย: (head; echo; tail) <file.txt
Simon Hibbs

3
อยากรู้ว่าทำไม / ทำงานอย่างไร ถามเป็นคำถามใหม่stackoverflow.com/questions/13718242
zellyn

9
@nametal จริงๆแล้วคุณอาจจะไม่ได้มากขนาดนั้น แม้ว่าจะแสดงheadเฉพาะ10 บรรทัดแรกของอินพุต แต่ก็ไม่มีการรับประกันว่าจะไม่กินมากขึ้นเพื่อค้นหาการสิ้นสุดบรรทัดที่ 10 โดยเหลืออินพุตไว้ให้แสดงน้อยลง less
chepner

20
ขออภัยที่ต้องพูด แต่คำตอบใช้ได้ในบางกรณีเท่านั้น seq 100 | (head; tail)ให้ฉันเพียง 10 หมายเลขแรก เฉพาะขนาดอินพุตที่ใหญ่กว่ามากเท่านั้น (เช่นseq 2000) tail เท่านั้นที่ได้รับอินพุต
โมดูลาร์

18

ed คือ standard text editor

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

2
จะเกิดอะไรขึ้นถ้าไฟล์มีมากกว่าหรือน้อยกว่า 200 บรรทัด? และคุณไม่รู้จำนวนบรรทัด ab initio?
พอล

@Paul ฉันเปลี่ยนsedเป็นed
kev

14

สำหรับสตรีมที่บริสุทธิ์ (เช่นเอาต์พุตจากคำสั่ง) คุณสามารถใช้ 'tee' เพื่อแยกสตรีมและส่งหนึ่งสตรีมไปยังส่วนหัวและหนึ่งต่อท้าย สิ่งนี้ต้องใช้คุณสมบัติ '> (list)' ของ bash (+ / dev / fd / N):

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

หรือใช้ / dev / fd / N (หรือ / dev / stderr) บวกกับ subshells ที่มีการเปลี่ยนเส้นทางที่ซับซ้อน:

( ( 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

(สิ่งเหล่านี้จะไม่ทำงานใน csh หรือ tcsh)

สำหรับบางสิ่งที่มีการควบคุมที่ดีขึ้นเล็กน้อยคุณสามารถใช้คำสั่ง perl นี้:

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 สำหรับการสนับสนุนสตรีม คุณสามารถใช้ stderr ซ้ำได้:COMMAND | { tee >(head >&2) | tail; } |& other_commands
jfs

2
btw มันแบ่งไฟล์ที่ใหญ่กว่าขนาดบัฟเฟอร์ (8K ในระบบของฉัน) cat >/dev/nullแก้ไข:COMMAND | { tee >(head >&2; cat >/dev/null) | tail; } |& other_commands
jfs

ฉันรักการแก้ปัญหา แต่หลังจากที่เล่นให้กับ AA ในขณะที่ผมสังเกตเห็นว่าในบางกรณีหางถูกเรียกใช้ก่อนหัว ... มีมีการรับประกันที่ไม่มีการสั่งซื้อระหว่างheadและtailคำสั่ง: \ ...
ม.ค.

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

รูปแบบอื่นใน(head;tail)ธีม แต่หลีกเลี่ยงปัญหาการเติมบัฟเฟอร์เริ่มต้นสำหรับไฟล์ขนาดเล็ก


4

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

นอกเหนือจากนั้นคุณจะต้องเขียนโปรแกรม / สคริปต์ของคุณเอง


1
ดีฉันใช้catและheadหรือใช้tailท่อมาโดยตลอดดีใจที่รู้ว่าฉันสามารถใช้ทีละรายการได้!
พอล

แล้วฉันจะไพพ์ 10 + 10 สุดท้ายนี้ไปเป็นคำสั่งอื่นได้อย่างไร
toop

1
@Paul - ด้วย 'your_program' เป็น wc -l จะส่งกลับ 10 แทนที่จะเป็น 20
toop

3
หรือโดยไม่ต้องวาง subshell: { head file; tail file; } | prog(ต้องเว้นระยะห่างภายในวงเล็บปีกกาและต้องมีอัฒภาคต่อท้าย)
Glenn Jackman

1
ว้าว ... การโหวตลงคะแนนสำหรับคำตอบที่ค่อนข้างคล้ายกับคนอื่น ๆ (ยังประทับเวลาก่อนหน้านั้น) หลังจากผ่านไปเกือบสองปีจากคนที่เลือกที่จะไม่โพสต์ว่าทำไมพวกเขาถึงโหวตไม่ลง ดี!
mAh

4

จากความคิดเห็นของ JF Sebastian :

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

ด้วยวิธีนี้คุณสามารถประมวลผลบรรทัดแรกและส่วนที่เหลือแตกต่างกันในท่อเดียวซึ่งมีประโยชน์สำหรับการทำงานกับข้อมูล 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

ปัญหาคือโปรแกรมที่เน้นสตรีมไม่ทราบความยาวของไฟล์ล่วงหน้า (เนื่องจากอาจไม่มีไฟล์หากเป็นสตรีมจริง)

เครื่องมือเช่น tailบัฟเฟอร์ n บรรทัดสุดท้ายที่เห็นและรอให้สิ้นสุดสตรีมจากนั้นพิมพ์

หากคุณต้องการทำสิ่งนี้ในคำสั่งเดียว (และให้มันทำงานกับออฟเซ็ตใด ๆ และอย่าทำซ้ำบรรทัดหากมันทับซ้อนกัน) คุณจะต้องเลียนแบบพฤติกรรมนี้ที่ฉันพูดถึง

ลอง awk นี้:

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

ต้องทำงานมากขึ้นเพื่อหลีกเลี่ยงปัญหาเมื่อออฟเซ็ตมีขนาดใหญ่กว่าไฟล์
Samus_

ใช่มันใช้งานได้กับเอาต์พุตแบบa.out | awk -v ...
piped

แน่นอน :) แต่นั่นเป็นพฤติกรรมปกติของ awk โปรแกรม commandline ส่วนใหญ่ทำงานบน stdin เมื่อเรียกใช้โดยไม่มีข้อโต้แย้ง
Samus_

1
ใกล้เคียงกับพฤติกรรมที่ต้องการมาก แต่ดูเหมือนว่าสำหรับ <10 บรรทัดจะเพิ่มบรรทัดใหม่พิเศษ
sorin

3

ต้องใช้เวลานานพอสมควรในการจบลงด้วยโซลูชันนี้ซึ่งดูเหมือนจะเป็นวิธีเดียวที่ครอบคลุมกรณีการใช้งานทั้งหมด (จนถึงตอนนี้):

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]}
           }'

รายการคุณสมบัติ:

  • เอาต์พุตสดสำหรับหัว (เห็นได้ชัดว่าเป็นไปไม่ได้สำหรับหาง)
  • ไม่มีการใช้ไฟล์ภายนอก
  • แถบความคืบหน้าหนึ่งจุดสำหรับแต่ละบรรทัดหลัง MAX_LINES ซึ่งมีประโยชน์มากสำหรับงานที่ต้องใช้เวลานาน
  • แถบความคืบหน้าบน stderr ทำให้มั่นใจได้ว่าจุดความคืบหน้าจะแยกออกจากส่วนหัว + หาง (มีประโยชน์มากหากคุณต้องการวางท่อ stdout)
  • หลีกเลี่ยงลำดับการบันทึกที่ไม่ถูกต้องเนื่องจากการบัฟเฟอร์ (stdbuf)
  • หลีกเลี่ยงการทำสำเนาเอาต์พุตเมื่อจำนวนบรรทัดทั้งหมดน้อยกว่า head + tail

2

ฉันมองหาวิธีแก้ปัญหานี้มาระยะหนึ่งแล้ว พยายามด้วยตัวเอง แต่ปัญหาเกี่ยวกับการไม่ทราบความยาวของไฟล์ / สตรีมก่อนล่วงหน้านั้นผ่านไม่ได้ จากตัวเลือกทั้งหมดที่มีอยู่ข้างต้นฉันชอบโซลูชัน awk ของ Camille Goudeseune เขาจดบันทึกว่าโซลูชันของเขาทิ้งบรรทัดว่างพิเศษไว้ในเอาต์พุตพร้อมชุดข้อมูลขนาดเล็กเพียงพอ ที่นี่ฉันจัดเตรียมการปรับเปลี่ยนโซลูชันของเขาที่ลบบรรทัดพิเศษ

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

คุณสามารถผูกมันเข้าด้วยกันได้เสมอ เช่นนั้น, head fiename_foo && tail filename_foo. หากยังไม่เพียงพอคุณสามารถเขียนฟังก์ชัน bash ในไฟล์. profile หรือไฟล์ล็อกอินที่คุณใช้:

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

และเรียกใช้ในภายหลังจากพร้อมต์เชลล์ของคุณ: head_and_tail filename_foo.


1

10 บรรทัดแรกของ file.ext จากนั้น 10 บรรทัดสุดท้าย:

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

10 บรรทัดสุดท้ายของไฟล์จากนั้น 10 บรรทัดแรก:

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

จากนั้นคุณสามารถไปป์เอาต์พุตที่อื่นได้เช่นกัน:

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


5
ทำไมต้องใช้ cat เมื่อคุณสามารถเรียก head -10 file.txt ได้
jstarek

คุณสามารถสร้างตัวแปรจำนวนบรรทัดได้หรือไม่ดังนั้นการเรียกจึงเป็นดังนี้ head_ tail (foo, m, n) - ส่งคืนข้อความ m snd บรรทัดสุดท้าย n ตัวแรกได้หรือไม่
ricardo

@ricardo ที่จะเกี่ยวข้องกับการเขียน bash script ที่ใช้ 3 args และส่งต่อไปยังtailและheadหรือฟังก์ชั่นโดยใช้นามแฝง
พอล


1

วาดตามแนวคิดข้างต้น (ทดสอบ bash & zsh)

แต่ใช้นามแฝงว่า Head and Tails

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


hat large.sql

0

ทำไมไม่ใช้sedสำหรับงานนี้?

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


3
ใช้ได้กับไฟล์ที่มีความยาวที่ทราบ แต่ไม่ใช่ไฟล์ที่ไม่ทราบความยาว
Kevin

0

ในการจัดการไปป์ (สตรีม) และไฟล์ให้เพิ่มสิ่งนี้ในไฟล์. bashrc หรือ. profile ของคุณ:

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] }' ; }

จากนั้นคุณสามารถไม่เพียง

headtail 10 < file.txt

แต่ยัง

a.out | headtail 10

(สิ่งนี้ยังคงต่อท้ายบรรทัดว่างปลอมเมื่อ 10 เกินความยาวของอินพุตซึ่งแตกต่างจากแบบเก่าa.out | (head; tail)ขอบคุณผู้ตอบก่อนหน้านี้)

หมายเหตุ: ไม่headtail 10headtail -10


0

จากสิ่งที่ @Samus_ อธิบายที่นี่เกี่ยวกับวิธีการทำงานของคำสั่งของ @Aleksandra Zalcman รูปแบบนี้มีประโยชน์เมื่อคุณไม่สามารถระบุตำแหน่งที่หางเริ่มต้นได้อย่างรวดเร็วโดยไม่ต้องนับเส้น

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

หรือถ้าคุณเริ่มทำงานกับอย่างอื่นที่ไม่ใช่ 20 บรรทัดการนับบรรทัดอาจช่วยได้

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

0

ในการพิมพ์ 10 บรรทัดแรกและ 10 บรรทัดสุดท้ายของไฟล์คุณสามารถลองสิ่งนี้:

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


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

หมายเหตุ : ผู้aFileตัวแปรมีแฟ้มเส้นทางแบบเต็ม


0

ฉันจะบอกว่าขึ้นอยู่กับขนาดของไฟล์การอ่านเนื้อหาอย่างกระตือรือร้นอาจไม่เป็นที่ต้องการ ในกรณีนั้นฉันคิดว่าเชลล์สคริปต์แบบง่าย ๆ ก็น่าจะเพียงพอแล้ว

ต่อไปนี้เป็นวิธีที่ฉันเพิ่งจัดการกับไฟล์ CSV ขนาดใหญ่จำนวนมากที่ฉันกำลังวิเคราะห์:

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

ซึ่งจะพิมพ์ 10 บรรทัดแรกและ 10 บรรทัดสุดท้ายของแต่ละไฟล์ในขณะที่พิมพ์ชื่อไฟล์และจุดไข่ปลาก่อนและหลัง

สำหรับไฟล์ขนาดใหญ่ไฟล์เดียวคุณสามารถเรียกใช้สิ่งต่อไปนี้เพื่อให้ได้เอฟเฟกต์เดียวกัน:

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

0

สิ้นเปลือง stdin แต่เรียบง่ายและใช้ได้กับ 99% ของกรณีการใช้งาน

head_and_tail

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

ตัวอย่าง

$ seq 100 | head_and_tail 4
1
2
3
4
...
97
98
99
100
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.