ใช้ส่วนหัวและส่วนท้ายเพื่อจับเส้นชุดต่าง ๆ และบันทึกเป็นไฟล์เดียวกัน


10

ดังนั้นนี่คือการบ้าน แต่ฉันจะไม่ถามคำถามการบ้านเฉพาะ

ฉันต้องใช้ส่วนหัวและส่วนท้ายเพื่อจับชุดของบรรทัดที่แตกต่างจากไฟล์เดียว เช่นเดียวกับบรรทัดที่ 6-11 และบรรทัดที่ 19-24 และบันทึกทั้งคู่เป็นไฟล์อื่น ฉันรู้ว่าฉันสามารถทำได้โดยใช้ผนวกเช่น

head -11 file|tail -6 > file1; head -24 file| tail -6 >> file1. 

แต่ฉันไม่คิดว่าเราควรจะทำ
มีวิธีเฉพาะที่ฉันสามารถรวมคำสั่งส่วนหัวและส่วนท้ายแล้วบันทึกลงในไฟล์ได้หรือไม่


1
พวกเขาขอให้คุณใช้โดยเฉพาะheadและtail? ถ้าเป็นเช่นนั้นทางออกของคุณดีที่สุดที่คุณสามารถทำได้ หากคุณได้รับอนุญาตให้ใช้โปรแกรมอื่นsedหรือawkอาจอนุญาตให้ใช้โซลูชันที่ดีกว่า (เช่นมีการเรียกใช้กระบวนการน้อยลง)
n.st

ใช่พวกเขาขอให้เราใช้หัวและหาง ขอบคุณสำหรับคำตอบ.
user2709291

อีกสิ่งหนึ่งที่ฉันสามารถเพิ่ม: คุณสามารถรับรอบการเปลี่ยนเส้นทางการส่งออกท้าย ( >>) (head -11 file | tail -6; head -24 file | tail -6) > file1โดยแนบคำสั่งที่สองในวงเล็บเปลี่ยนเส้นทางออกตัดแบ่งของพวกเขา มันลงมาตามความชอบส่วนตัวซึ่งดีกว่า
n.st

ขอบคุณที่จะได้ผลดีมาก ฉันซาบซึ้งจริงๆ
user2709291

คำตอบ:


11

คุณสามารถทำได้ด้วยตัวheadเองและเลขคณิตพื้นฐานถ้าคุณจัดกลุ่มคำสั่งด้วยการ{ ... ; }ใช้โครงสร้างเช่น

{ head -n ...; head -n ...; ...; } < input_file > output_file

โดยที่คำสั่งทั้งหมดแชร์อินพุตเดียวกัน (ขอบคุณ@mikeserv )
รับสาย 6-11 และบรรทัดที่ 19-24 เทียบเท่ากับ:

head -n 5 >/dev/null  # dump the first 5 lines to `/dev/null` then
head -n 6             # print the next 6 lines (i.e. from 6 to 11) then
head -n 7 >/dev/null  # dump the next 7 lines to `/dev/null` ( from 12 to 18)
head -n 6             # then print the next 6 lines (19 up to 24)

ดังนั้นโดยพื้นฐานแล้วคุณจะต้องวิ่ง:

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } < input_file > output_file

มันไม่ทำงานสำหรับฉัน การป้อนข้อมูลจะถูกป้อนอย่างมีประสิทธิภาพโดยหัวแรก
Whimusical

6

คุณสามารถใช้โครงสร้างการ{ … }จัดกลุ่มเพื่อใช้ตัวดำเนินการเปลี่ยนเส้นทางกับคำสั่งผสม

{ head -n 11 file | tail -n 6; head -n 24 file | tail -n 6; } >file1

แทนการทำซ้ำครั้งแรกสาย M + N และการรักษาเฉพาะที่อยู่สุดท้ายคุณสามารถข้ามเส้น M แรกและทำซ้ำ N. ต่อไปนี้เป็นวัดได้เร็วขึ้นในไฟล์ขนาดใหญ่ ระวังว่า+Nอาร์กิวเมนต์ของtailไม่ใช่จำนวนบรรทัดที่จะข้าม แต่อย่างหนึ่งบวกนั่นคือจำนวนของบรรทัดแรกที่พิมพ์ด้วยบรรทัดที่มีหมายเลขตั้งแต่ 1

{ tail -n +6 file | head -n 6; tail -n +19 file | head -n 6; } >file1

ไม่ว่าจะด้วยวิธีใดไฟล์เอาต์พุตจะเปิดเพียงครั้งเดียว แต่ไฟล์อินพุตจะถูกสำรวจหนึ่งครั้งเพื่อให้แต่ละสนิปเพตแยกออก วิธีการจัดกลุ่มอินพุต?

{ tail -n +6 | head -n 6; tail -n +14 | head -n 6; } <file >file1

โดยทั่วไปสิ่งนี้ใช้ไม่ได้ (มันอาจทำงานได้ในบางระบบอย่างน้อยที่สุดเมื่ออินพุตเป็นไฟล์ปกติ) ทำไม? เพราะบัฟเฟอร์อินพุต โปรแกรมส่วนใหญ่รวมถึงtailอย่าอ่านอินพุตไบต์ต่อไบต์ แต่อ่านครั้งละไม่กี่กิโลไบต์เพราะเร็วขึ้น ดังนั้นtailอ่านสักสองสามกิโลไบต์ข้ามไปเล็กน้อยที่จุดเริ่มต้นส่งผ่านไปอีกเล็กน้อยheadและหยุด - แต่สิ่งที่อ่านจะอ่านและไม่พร้อมใช้งานสำหรับคำสั่งถัดไป

อีกวิธีคือใช้headpiped /dev/nullเพื่อข้ามบรรทัด

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } <file >file1

อีกครั้งนี้ไม่รับประกันว่าจะทำงานได้เนื่องจากการบัฟเฟอร์ มันเกิดขึ้นกับการทำงานกับheadคำสั่งจาก GNU coreutils (ที่พบในระบบ Linux ที่ไม่ได้ฝังตัว) เมื่ออินพุตมาจากไฟล์ปกติ นั่นเป็นเพราะเมื่อการติดตั้งนี้headได้อ่านสิ่งที่ต้องการแล้วมันจะกำหนดตำแหน่งไฟล์เป็นไบต์แรกที่ไม่ได้แสดงผล ไม่สามารถใช้งานได้หากอินพุตเป็นไพพ์

วิธีที่ง่ายในการพิมพ์หลายลำดับของสายจากแฟ้มคือการเรียกเครื่องมือ generalist มากขึ้นเช่นsedหรือawk (สิ่งนี้อาจช้ากว่านี้ได้ แต่จะสำคัญกับไฟล์ที่มีขนาดใหญ่มากเท่านั้น)

sed -n -e '6,11p' -e '19,24p' <file >file1
sed -e '1,5d' -e '12,18d' -e '24q' <file >file1
awk '6<=NR && NR<=11 || 19<=NR && NR<=24' <file >file1
awk 'NR==6, NR==11; NR==19, NR==24' <file >file1

2
มันไม่ได้เกิดขึ้นเพราะเป็นมาตรฐานพฤติกรรมที่ระบุ - แน่นอนว่าอย่างที่คุณพูดไปป์นั้นไม่ใช่แหล่งอินพุตที่เชื่อถือได้สำหรับอินพุตที่แชร์ คำอธิบายการใช้งานยูทิลิตี้
mikeserv

2

ฉันรู้ว่าคุณพูดว่าคุณต้องใช้หัวและหาง แต่แน่นอนว่าเครื่องมือที่ง่ายกว่าสำหรับงานที่นี่

$ cat foo
a 1 1
a 2 1
b 1 1
a 3 1
c 3 1
c 3 1
$ sed -ne '2,4p;6p' foo
a 2 1
b 1 1
a 3 1
c 3 1

คุณสามารถสร้างบล็อกในสตริงด้วยกระบวนการอื่นและเรียกใช้ผ่าน sed

$ a="2,4p;6p"
$ sed -ne $a foo
a 2 1
b 1 1
a 3 1
c 3 1

-n ลบล้างเอาต์พุตจากนั้นคุณระบุช่วงที่จะพิมพ์ด้วย p โดยมีหมายเลขแรกและหมายเลขสุดท้ายของช่วงคั่นด้วยเครื่องหมายจุลภาค

ดังที่ได้กล่าวไว้คุณสามารถทำการจัดกลุ่มคำสั่งที่ @don_crissti แนะนำหรือวนรอบไฟล์สองสามครั้งด้วยหัว / หางเพื่อจับเส้นจำนวนมากในแต่ละครั้งที่คุณผ่านไป

$ head -4 foo | tail -3; head -6 foo | tail -1
a 2 1
b 1 1
a 3 1
c 3 1

ยิ่งบรรทัดในไฟล์และบล็อกมากเท่าไหร่คุณก็จะยิ่งมีประสิทธิภาพมากขึ้นเท่านั้น


2

ด้วยsedคุณอาจทำ:

sed '24q;1,5d;12,18d' <infile >outfile

... headอาจจะเป็นทางออกที่มีประสิทธิภาพมากขึ้นอาจจะมีกับ ดอนได้แสดงให้เห็นแล้วว่ามันอาจทำงานได้ดีมาก แต่ฉันก็เล่นกับมันเช่นกัน บางสิ่งที่คุณอาจทำเพื่อจัดการกรณีเฉพาะนี้:

for   n in 5 6 7 6
do    head -n"$n" >&"$((1+n%2))"
done  <infile >outfile 2>/dev/null

... ซึ่งจะเรียกhead4 ครั้งที่เขียนไปยังoutfileหรือ/dev/nullขึ้นอยู่กับว่าค่าของการวนซ้ำ$nนั้นเป็นจำนวนคู่หรือคี่

สำหรับกรณีทั่วไปมากขึ้นฉันเอาก้อนกรวดนี้มารวมกันจากสิ่งอื่น ๆ ที่ฉันมีอยู่แล้ว:

somehead()( 
### call it like:
### somehead -[repeat] [-][numlines]* <infile >outfile
    set -e -- "${1#-}" "$@"                             #-e for arg validation
    r=; cd -- "${TMP:-/tmp}"                            #go to tmp
    dd bs=4096 of="$$$$" <&4 2>&3 &                     #dd <in >tmpfile &bg
    until [ -s "$$$$" ]; do :; done                     #wait while tmpfile empty
    exec <"$$$$" 4<&-;   rm "$$$$"                      #<tmpfile; rm tmpfile
    [ "$3${1}0" -ne "$3${2#?}0" ]          ||           #validate args - chk $1
            shift "$(((r=-${1:--1})||1))"; shift        #shift 1||2
    while [ "$(((r+=(_n=1))-1))" -ne 0 ]   &&           #while ! $rptmax &&
          IFS= read -r l                   &&           #      ! EOF     &&
          printf "%.$(($1>0?${#l}+1:0))s" "$l           #      ? printf  do
";  do    for n do [ "${n#-}" -gt 0 ]      || exit      #args all -[nums>0]
          head "-n$((${n#-}-_n))" >&"$((n>(_n=0)?1:3))" #head -n?$1 >?[+-]
    done; done                                          #done and done
)   4<&0 3>/dev/null                                    #4<for dd 3>for head

สิ่งนี้สามารถทำสิ่งที่คุณต้องการ:

 seq 100 | somehead -1 -5 6 -7 6

... ที่พิมพ์ ...

6
7
8
9
10
11
19
20
21
22
23
24

คาดว่าหาเรื่องเป็นครั้งแรกที่จะทำซ้ำนับนำหน้าด้วยหรือความล้มเหลวที่เพียง- -หากมีการนับจำนวนมันจะทำซ้ำรูปแบบของเส้นที่กำหนดใน args ต่อไปนี้หลาย ๆ ครั้งตามที่ระบุและหยุดทันทีที่ทำเช่นนั้น

สำหรับหาเรื่องว่าต่อไปนี้มันจะแปลความหมายเชิงลบจำนวนเต็มแต่ละคนที่จะแสดงให้เห็นเป็นเส้นนับซึ่งควรจะเขียนไปและเป็นจำนวนเต็มบวกเพื่อบ่งชี้ถึงการนับบรรทัดซึ่งควรจะเขียนไป/dev/nullstdout

ดังนั้นในตัวอย่างข้างต้นจะพิมพ์ 5 สายแรกที่จะ/dev/nullต่อไป 6 stdout, 7 ถัดไป/dev/nullอีกครั้งและอีก 6 stdoutอีกครั้งเพื่อ เมื่อมาถึงจุดสุดท้ายของ args และกรณืเต็มที่ผ่านการ-1นับซ้ำมันก็หยุด ถ้าหาเรื่องครั้งแรกที่เคยเป็น-2มันจะได้ทำซ้ำกระบวนการอีกครั้งหรือถ้า-นานเท่าที่จะทำได้

สำหรับแต่ละวงหาเรื่องwhileวงรอบจะถูกประมวลผลครั้งเดียวผ่าน ที่ด้านบนของแต่ละวงบรรทัดแรกจากถูกอ่านในตัวแปรเปลือกstdin $lสิ่งนี้มีความจำเป็นเพราะwhile head </dev/null; do :; doneจะทำซ้ำอย่างไม่มีกำหนด - headบ่งบอกถึงการกลับมาเมื่อไฟล์ถึงจุดสิ้นสุด ดังนั้นการตรวจสอบกับ EOF จึงมีความมุ่งมั่นreadและprintfจะเขียน$lรวมถึงการขึ้นบรรทัดใหม่ต่อstdoutเมื่ออาร์กิวเมนต์ที่สองเป็นจำนวนเต็มบวก

การreadตรวจสอบจะทำให้เกิดความซับซ้อนของลูปเพียงเล็กน้อยเพราะทันทีหลังจากที่ลูปอื่นถูกเรียก - forลูปที่วนซ้ำข้าม args 2-$#ดังที่แสดงไว้$nสำหรับการวนซ้ำของwhileลูปพาเรนต์แต่ละครั้ง ซึ่งหมายความว่าสำหรับการทำซ้ำแต่ละครั้งอาร์กิวเมนต์แรกจะต้องลดค่าลงจากค่าที่ระบุในบรรทัดคำสั่ง แต่ค่าอื่น ๆ ทั้งหมดควรเก็บค่าดั้งเดิมไว้ดังนั้นค่าของตัว$_nทำเครื่องหมาย var จะถูกลบออกจากกัน ค่ามากกว่า 0 สำหรับ ARG แรก

ซึ่งถือว่าเป็นลูปหลักของฟังก์ชั่น แต่ส่วนใหญ่ของรหัสอยู่ที่ด้านบนและมีวัตถุประสงค์เพื่อเปิดใช้งานฟังก์ชั่นในการบัฟเฟอร์สะอาดแม้ท่อเป็นอินพุต วิธีนี้ใช้งานได้โดยเรียกพื้นหลังddเพื่อคัดลอกลงใน tmpfile บนเอาต์พุตที่ขนาดบล็อก 4k ต่อชิ้น จากนั้นฟังก์ชั่นจะตั้งค่าลูปการถือ - ซึ่งเกือบจะไม่สมบูรณ์แม้แต่รอบเดียว - เพื่อให้แน่ใจว่าddอย่างน้อยหนึ่งการเขียนไปยังไฟล์ก่อนที่ฟังก์ชั่นนั้นจะแทนที่ stdin ของมันด้วยไฟล์ descriptor ที่เชื่อมโยงกับ tmpfile และ หลังจากนั้นยกเลิกการเชื่อมโยงไฟล์ด้วยทันทีrm. สิ่งนี้ช่วยให้ฟังก์ชั่นประมวลผลสตรีมได้อย่างน่าเชื่อถือโดยไม่ต้องใช้กับดักหรือทำความสะอาด - ทันทีที่ฟังก์ชั่นเปิดตัวมันอ้างสิทธิ์ใน fd tmpfile จะหยุดอยู่เพราะลิงก์ระบบไฟล์ที่ระบุชื่อเท่านั้นถูกลบไปแล้ว


0

ใช้ฟังก์ชั่นทุบตีดังนี้:

seq 1 30 > input.txt
f(){ head $1 input.txt | tail $2 >> output.txt ;}; f -11 -2; f -24 -3
cat output.txt
10
11
22
23
24

นี่เป็นบิตที่เกินความจริงในกรณีนี้ แต่ถ้าตัวกรองของคุณขยายใหญ่ขึ้นก็อาจกลายเป็นประโยชน์

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