ฉันจะแยกช่วงของเส้นที่กำหนดไว้ล่วงหน้าจากไฟล์ข้อความบน Unix ได้อย่างไร


531

ฉันมีการถ่ายโอนข้อมูล SQL บรรทัด ~ 23000 ที่มีฐานข้อมูลหลายค่าที่คุ้มค่าของข้อมูล ฉันต้องการแยกส่วนที่แน่นอนของไฟล์นี้ (เช่นข้อมูลสำหรับฐานข้อมูลเดียว) และวางไว้ในไฟล์ใหม่ ฉันรู้ทั้งหมายเลขเริ่มต้นและจุดสิ้นสุดของข้อมูลที่ฉันต้องการ

ไม่มีใครรู้คำสั่ง Unix (หรือชุดคำสั่ง) เพื่อแยกทุกบรรทัดจากไฟล์ระหว่างพูดบรรทัด 16224 และ 16482 แล้วเปลี่ยนเส้นทางพวกเขาเป็นไฟล์ใหม่?


เนื่องจากคุณพูดถึงไฟล์ขนาดใหญ่ฉันขอแนะนำให้ตรวจสอบความคิดเห็นstackoverflow.com/questions/83329/…
sancho.s ReinstateMonicaCellio

คำตอบ:


792
sed -n '16224,16482p;16483q' filename > newfile

จากคู่มือ sed :

p - พิมพ์พื้นที่ลวดลาย (ไปยังเอาต์พุตมาตรฐาน) คำสั่งนี้มักจะใช้ร่วมกับตัวเลือกบรรทัดคำสั่ง -n เท่านั้น

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

q - ออกsedโดยไม่ประมวลผลคำสั่งหรืออินพุตอีกต่อไป โปรดทราบว่าพื้นที่รูปแบบปัจจุบันจะถูกพิมพ์หากไม่ได้ปิดใช้งานการพิมพ์อัตโนมัติด้วยตัวเลือก -n

และ

ที่อยู่ในสคริปต์ sed สามารถอยู่ในรูปแบบใดรูปแบบหนึ่งต่อไปนี้:

หมายเลข การระบุหมายเลขบรรทัดจะจับคู่เฉพาะบรรทัดนั้นในอินพุต

สามารถระบุช่วงของที่อยู่ได้โดยการระบุที่อยู่สองที่คั่นด้วยเครื่องหมายจุลภาค (,) ช่วงที่อยู่ตรงกับบรรทัดที่เริ่มต้นจากที่ที่อยู่แรกตรงกับและดำเนินการต่อไปจนกระทั่งที่อยู่ที่สองตรงกัน (รวมอยู่ด้วย)


3
ฉันอยากรู้ว่าสิ่งนี้จะแก้ไขไฟล์ต้นฉบับหรือไม่ ฉันสำรองข้อมูลไว้ในกรณีและปรากฏว่านี่ไม่ได้แก้ไขต้นฉบับตามที่คาดไว้
Andy Groff

@AndyGroff ในการแก้ไขไฟล์ให้ใช้พารามิเตอร์ "-i" มิฉะนั้นจะไม่แก้ไขไฟล์
youri

175
ถ้าเช่นฉันคุณต้องทำสิ่งนี้ในไฟล์ขนาดใหญ่มากมันจะช่วยถ้าคุณเพิ่มคำสั่ง quit ในบรรทัดถัดไป sed -n '16224,16482p;16483q' filenameจากนั้นก็จะเป็น มิฉะนั้นจะทำการสแกนต่อไปจนกว่าจะสิ้นสุด (หรืออย่างน้อยเวอร์ชั่นของฉันก็ทำ)
wds

7
@MilesRout คนดูเหมือนจะถามว่า "ทำไม downvote?" บ่อยครั้งที่คุณอาจหมายถึง "ฉันไม่สนใจ" แทนที่จะเป็น "ไม่มีใครสนใจ"
Mark

1
@wds - ความคิดเห็นของคุณสมควรได้รับคำตอบที่ปีนขึ้นไปด้านบน มันสามารถสร้างความแตกต่างระหว่างกลางวันและกลางคืน
sancho.s ReinstateMonicaCellio

203
sed -n '16224,16482 p' orig-data-file > new-file

โดยที่ 16224,16482 คือหมายเลขบรรทัดเริ่มต้นและหมายเลขบรรทัดสิ้นสุดรวมอยู่ด้วย นี่คือดัชนี 1 -nยับยั้งการสะท้อนอินพุตเป็นเอาต์พุตซึ่งคุณไม่ต้องการอย่างชัดเจน ตัวเลขบ่งชี้ช่วงของบรรทัดเพื่อให้คำสั่งต่อไปนี้ทำงาน คำสั่งpพิมพ์บรรทัดที่เกี่ยวข้อง


7
สำหรับไฟล์ขนาดใหญ่คำสั่งด้านบนจะเดินต่อไปทั้งไฟล์หลังจากพบช่วงที่ต้องการ มีวิธีที่จะหยุดการประมวลผลไฟล์เมื่อมีการส่งออกช่วงหรือไม่?
Gary

39
ดีจากคำตอบที่นี่sed -n '16224,16482p;16482q' orig-data-file > new-fileมันก็ดูเหมือนว่าหยุดที่จุดสิ้นสุดของช่วงที่สามารถทำได้ด้วย:
Gary

5
ทำไมคุณต้องใส่ในพื้นที่ที่ไม่จำเป็นแล้วต้องพูด? (แน่นอนการสร้างปัญหาที่ไม่จำเป็นและการแก้ปัญหาเหล่านี้เป็นสาระสำคัญของครึ่งหนึ่งของวิทยาการคอมพิวเตอร์ แต่ฉันหมายถึงเหตุผลนั้น ... )
Kaz

92

ค่อนข้างง่ายโดยใช้หัว / หาง:

head -16482 in.sql | tail -258 > out.sql

ใช้ sed:

sed -n '16482,16482p' in.sql > out.sql

ใช้ awk:

awk 'NR>=10&&NR<=20' in.sql > out.sql

1
ตัวเลือกที่สองและที่สามก็โอเค แต่ตัวเลือกแรกนั้นช้ากว่าตัวเลือกอื่น ๆ เนื่องจากมันใช้ 2 คำสั่งโดยที่ 1 เพียงพอ tailนอกจากนี้ยังต้องคำนวณที่จะได้รับการโต้แย้งสิทธิในการ
Jonathan Leffler

3
ที่น่าสังเกตว่าเพื่อให้หมายเลขบรรทัดเดียวกับคำถามที่ว่าคำสั่ง sed ที่ควรจะเป็นsed -n 16224,16482p' in.sql >out.sqlและคำสั่ง awk ที่ควรจะเป็นawk 'NR>=16224&&NR<=16482' in.sql > out.sql
sibaz

3
นอกจากนี้ควรทราบด้วยว่าในกรณีของตัวอย่างแรกhead -16482 in.sql | tail -$((16482-16224)) >out.sqlทำให้การคำนวณลงไป bash
sibaz

1
คนแรกที่มีหัวและหาง WAYYYY เร็วขึ้นในไฟล์ขนาดใหญ่กว่ารุ่น sed แม้จะมีการเพิ่มตัวเลือก q รุ่นหัวทันทีและรุ่นที่ฉันกดปุ่ม Ctrl-C หลังจากผ่านไปหนึ่งนาที ... ขอบคุณ
Miyagi

2
สามารถใช้tail -n +16224เพื่อลดการคำนวณ
SOFe

35

คุณสามารถใช้ 'vi' และจากนั้นคำสั่งต่อไปนี้:

:16224,16482w!/tmp/some-file

อีกวิธีหนึ่งคือ:

cat file | head -n 16482 | tail -n 258

แก้ไข: - เพียงแค่เพิ่มคำอธิบายคุณใช้head -n 16482เพื่อแสดง 16482 บรรทัดแรกจากนั้นใช้tail -n 258เพื่อรับ 258 บรรทัดสุดท้ายจากเอาต์พุตแรก


2
และแทนที่จะเป็น vi คุณสามารถใช้ ex ได้นั่นคือ vi ลบเนื้อหาคอนโซลแบบโต้ตอบ
Tadeusz A. Kadłubowski

1
คุณไม่ต้องการcatคำสั่ง headสามารถอ่านไฟล์ได้โดยตรง สิ่งนี้ช้ากว่าทางเลือกมากมายเนื่องจากใช้คำสั่ง 2 (3 ดังที่แสดง) โดยที่ 1 เพียงพอ
Jonathan Leffler

1
@JanathanLeffler คุณผิดไปมาก มันเร็วมาก ฉันแยกสาย 200k ประมาณ 1G จากไฟล์ 2G ที่มี 500k บรรทัดในไม่กี่วินาที (โดยไม่มีcat) โซลูชันอื่น ๆ ต้องการเวลาอย่างน้อยสองสามนาที นอกจากนี้รูปแบบที่เร็วที่สุดของ GNU ก็น่าจะเป็นtail -n +XXX filename | head XXXเช่นกัน
Antonis Christofides

28

มีวิธีอื่นด้วยawk:

awk 'NR==16224, NR==16482' file

หากไฟล์มีขนาดใหญ่ก็สามารถดีexitหลังจากอ่านบรรทัดที่ต้องการ ด้วยวิธีนี้มันจะไม่อ่านบรรทัดต่อไปนี้โดยไม่จำเป็น:

awk 'NR==16224, NR==16482-1; NR==16482 {print; exit}' file

awk 'NR==16224, NR==16482; NR==16482 {exit}' file

2
1+ print; exitสำหรับการบันทึกรันไทม์และทรัพยากรโดยใช้ ขอบคุณมาก!
เบอร์นีไรเตอร์

การทำให้เข้าใจตัวอย่างเล็กน้อยของตัวอย่างที่ 2:awk 'NR==16224, NR==16482; NR==16482 {exit}' file
Robin A. Meade

สดใสขอบคุณ @ RobinA.Meade! ฉันแก้ไขความคิดของคุณใน post
fedorqui 'ดังนั้นหยุดทำอันตราย'



6
cat dump.txt | head -16224 | tail -258

ควรทำเคล็ดลับ ข้อเสียของวิธีการนี้คือคุณต้องทำเลขคณิตเพื่อกำหนดอาร์กิวเมนต์สำหรับ tail และพิจารณาว่าคุณต้องการให้ 'ระหว่าง' เพื่อรวมบรรทัดสุดท้ายหรือไม่


4
คุณไม่ต้องการcatคำสั่ง headสามารถอ่านไฟล์ได้โดยตรง สิ่งนี้ช้ากว่าทางเลือกมากมายเนื่องจากใช้คำสั่ง 2 (3 ดังที่แสดง) โดยที่ 1 เพียงพอ
Jonathan Leffler

@JonathanLeffler คำตอบนี้ง่ายที่สุดในการอ่านและจดจำ ถ้าคุณสนใจเรื่องการแสดงจริงๆคุณจะไม่ได้ใช้เชลล์ในตอนแรก เป็นวิธีปฏิบัติที่ดีที่จะให้เครื่องมือเฉพาะอุทิศตนให้กับงานบางอย่าง นอกจากนี้ "คณิตศาสตร์" | tail -$((16482 - 16224))สามารถแก้ไขได้โดยใช้
Yeti

6

ยืนอยู่บนไหล่ของ boxxar ฉันชอบสิ่งนี้:

sed -n '<first line>,$p;<last line>q' input

เช่น

sed -n '16224,$p;16482q' input

$หมายถึง "บรรทัดสุดท้าย" เพื่อให้คำสั่งแรกทำให้sedพิมพ์ทุกสายที่เริ่มต้นด้วยเส้น16224และทำให้คำสั่งที่สองsedเลิกหลังจาก16428พิมพ์บรรทัด (การเพิ่ม1สำหรับqโซลูชันช่วง boxxar ดูเหมือนจะไม่จำเป็น)

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



3

รวดเร็วและสกปรก:

head -16428 < file.in | tail -259 > file.out

อาจไม่ใช่วิธีที่ดีที่สุดที่จะทำ แต่ควรใช้งานได้

BTW: 259 = 16482-16224 + 1


สิ่งนี้ช้ากว่าทางเลือกอื่น ๆ เนื่องจากใช้ 2 คำสั่งโดยที่ 1 เพียงพอ
Jonathan Leffler

3

ผมเขียนโปรแกรม Haskell เรียกว่าแยกที่ไม่ตรงนี้มีการอ่านผ่านการโพสต์บล็อกของฉันปล่อย

คุณสามารถใช้โปรแกรมดังต่อไปนี้:

$ cat somefile | splitter 16224-16482

และนั่นคือทั้งหมดที่มีไป คุณจะต้องติดตั้ง Haskell แค่:

$ cabal install splitter

และคุณทำเสร็จแล้ว ฉันหวังว่าคุณจะพบว่าโปรแกรมนี้มีประโยชน์


ไม่splitterเพียง แต่อ่านจากอินพุตมาตรฐาน? ในแง่หนึ่งมันไม่สำคัญ catคำสั่งเป็นฟุ่มเฟือยไม่ว่าจะเกิดขึ้นหรือไม่ ทั้งใช้splitter 16224-16482 < somefileหรือ splitter 16224-16482 somefile(ถ้าใช้อาร์กิวเมนต์ชื่อไฟล์)
Jonathan Leffler

3

แม้เราสามารถทำสิ่งนี้เพื่อตรวจสอบที่บรรทัดคำสั่ง:

cat filename|sed 'n1,n2!d' > abc.txt

ตัวอย่างเช่น:

cat foo.pl|sed '100,200!d' > abc.txt

6
คุณไม่ต้องการcatคำสั่งในสิ่งใดสิ่งหนึ่ง sedมีความสามารถในการอ่านไฟล์ได้อย่างสมบูรณ์แบบด้วยตัวเองหรือคุณสามารถเปลี่ยนเส้นทางอินพุตมาตรฐานจากไฟล์
Jonathan Leffler


2

ฉันกำลังจะโพสต์เคล็ดลับหัว / หาง แต่จริง ๆ แล้วฉันอาจจะแค่ไฟ emacs ;-)

  1. esc- xgoto-lineret 16224
  2. เครื่องหมาย ( ctrl- space)
  3. esc- xgoto-line ret16482
  4. esc-w

เปิดไฟล์เอาต์พุตใหม่บันทึก ctl-y

ให้ฉันดูสิ่งที่เกิดขึ้น


4
Emacs ทำงานได้ไม่ดีกับไฟล์ที่มีขนาดใหญ่มากในประสบการณ์ของฉัน
Greg Mattes

คุณสามารถเรียกใช้สิ่งนั้นว่าเป็นการกระทำแบบสคริปต์หรือเป็นเพียงตัวเลือกแบบโต้ตอบได้หรือไม่?
Jonathan Leffler

2

ฉันจะใช้:

awk 'FNR >= 16224 && FNR <= 16482' my_file > extracted.txt

FNR มีหมายเลขระเบียน (บรรทัด) ของบรรทัดที่อ่านจากไฟล์


2

ฉันต้องการทำสิ่งเดียวกันจากสคริปต์โดยใช้ตัวแปรและบรรลุผลโดยใส่เครื่องหมายคำพูดรอบตัวแปร $ เพื่อแยกชื่อตัวแปรออกจาก p:

sed -n "$first","$count"p imagelist.txt >"$imageblock"

ฉันต้องการแยกรายการเป็นโฟลเดอร์แยกและพบคำถามเริ่มต้นและตอบขั้นตอนที่มีประโยชน์ (คำสั่งแยกไม่ใช่ตัวเลือกในระบบปฏิบัติการเก่าฉันต้องพอร์ตรหัสไป)


1

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

การใช้งาน: $ pinch ชื่อไฟล์เริ่มต้นบรรทัดสุดท้าย

#!/bin/bash
# Display line number ranges of a file to the terminal.
# Usage: $ pinch filename start-line end-line
# By Evan J. Coon

FILENAME=$1
START=$2
END=$3

ERROR="[PINCH ERROR]"

# Check that the number of arguments is 3
if [ $# -lt 3 ]; then
    echo "$ERROR Need three arguments: Filename Start-line End-line"
    exit 1
fi

# Check that the file exists.
if [ ! -f "$FILENAME" ]; then
    echo -e "$ERROR File does not exist. \n\t$FILENAME"
    exit 1
fi

# Check that start-line is not greater than end-line
if [ "$START" -gt "$END" ]; then
    echo -e "$ERROR Start line is greater than End line."
    exit 1
fi

# Check that start-line is positive.
if [ "$START" -lt 0 ]; then
    echo -e "$ERROR Start line is less than 0."
    exit 1
fi

# Check that end-line is positive.
if [ "$END" -lt 0 ]; then
    echo -e "$ERROR End line is less than 0."
    exit 1
fi

NUMOFLINES=$(wc -l < "$FILENAME")

# Check that end-line is not greater than the number of lines in the file.
if [ "$END" -gt "$NUMOFLINES" ]; then
    echo -e "$ERROR End line is greater than number of lines in file."
    exit 1
fi

# The distance from the end of the file to end-line
ENDDIFF=$(( NUMOFLINES - END ))

# For larger files, this will run more quickly. If the distance from the
# end of the file to the end-line is less than the distance from the
# start of the file to the start-line, then start pinching from the
# bottom as opposed to the top.
if [ "$START" -lt "$ENDDIFF" ]; then
    < "$FILENAME" head -n $END | tail -n +$START
else
    < "$FILENAME" tail -n +$START | head -n $(( END-START+1 ))
fi

# Success
exit 0

1
สิ่งนี้ช้ากว่าทางเลือกอื่น ๆ เนื่องจากใช้ 2 คำสั่งโดยที่ 1 เพียงพอ ในความเป็นจริงมันอ่านไฟล์สองครั้งเพราะwcคำสั่งซึ่งทำให้เปลืองแบนด์วิดท์ดิสก์โดยเฉพาะในไฟล์กิกะไบต์ ในทุกประเภทวิธีนี้เป็นเอกสารที่ดี แต่มันก็เป็นงานวิศวกรรมที่มากเกินไป
Jonathan Leffler

1

สิ่งนี้อาจใช้ได้กับคุณ (GNU sed):

sed -ne '16224,16482w newfile' -e '16482q' file

หรือการใช้ประโยชน์จากการทุบตี:

sed -n $'16224,16482w newfile\n16482q' file

1

ใช้ ed:

ed -s infile <<<'16224,16482p'

-sไม่แสดงเอาต์พุตการวินิจฉัย คำสั่งที่แท้จริงอยู่ในที่นี่สตริง โดยเฉพาะให้16224,16482pรันpคำสั่ง (พิมพ์) ในช่วงที่อยู่บรรทัดที่ต้องการ


0

-n ในคำตอบที่ยอมรับได้ทำงาน นี่เป็นอีกวิธีในกรณีที่คุณมีแนวโน้ม

cat $filename | sed "${linenum}p;d";

นี่ทำสิ่งต่อไปนี้:

  1. ไปป์ในเนื้อหาของไฟล์ (หรือฟีดในข้อความตามที่คุณต้องการ)
  2. sed เลือกบรรทัดที่กำหนดพิมพ์มัน
  3. d จำเป็นต้องลบบรรทัดมิฉะนั้น sed จะถือว่าบรรทัดทั้งหมดจะถูกพิมพ์ในที่สุด กล่าวคือหากไม่มี d คุณจะได้รับการพิมพ์ทุกบรรทัดโดยการเลือกบรรทัดที่พิมพ์สองครั้งเนื่องจากคุณมีส่วน $ {linenum} p ที่ขอให้พิมพ์ ฉันค่อนข้างแน่ใจว่า -n กำลังทำสิ่งเดียวกันกับ d ที่นี่

3
หมายเหตุcat file | sedเขียนได้ดีกว่าว่าsed file
fedorqui 'ดังนั้นหยุดการทำร้าย'

นี่ก็แค่พิมพ์เส้นตรงในขณะที่คำถามนั้นเกี่ยวกับช่วงของพวกมัน
fedorqui 'ดังนั้นหยุดการทำร้าย'

0

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

myfile content:
=====================
line1 not needed
line2 also discarded
[Data]
first data line
second data line
=====================
sed -n '/Data/,$p' myfile

จะพิมพ์บรรทัด [Data] และส่วนที่เหลือ หากคุณต้องการข้อความจาก line1 เป็นรูปแบบให้พิมพ์: sed -n '1, / Data / p' myfile นอกจากนี้หากคุณรู้ว่ามีสองรูปแบบ (ควรมีความโดดเด่นในข้อความของคุณ) ทั้งจุดเริ่มต้นและจุดสิ้นสุดของช่วงสามารถระบุด้วยการจับคู่ได้

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