จะพิมพ์บรรทัดที่ยาวที่สุดในไฟล์ได้อย่างไร?


34

ฉันกำลังมองหาวิธีที่ง่ายที่สุดในการพิมพ์บรรทัดที่ยาวที่สุดในไฟล์ ฉันทำ googling แล้วก็แปลกใจที่ไม่สามารถหาคำตอบได้ ฉันมักจะพิมพ์ความยาวของบรรทัดที่ยาวที่สุดในไฟล์ แต่ฉันไม่ทราบวิธีพิมพ์บรรทัดที่ยาวที่สุดจริง ๆ ทุกคนสามารถให้บริการโซลูชั่นในการพิมพ์บรรทัดที่ยาวที่สุดในไฟล์ได้หรือไม่? ขอบคุณล่วงหน้า.


1
จะเกิดอะไรขึ้นเมื่อมีเส้น "ยาวที่สุด" หลายเส้น เนื่องจากคุณต้องการมากกว่าความยาวสูงสุดธรรมดาคุณต้องการดูอินสแตนซ์ทั้งหมดของบรรทัดที่ยาวที่สุดเท่ากันหรือไม่
Peter.O

คำตอบ:


38
cat ./text | awk ' { if ( length > x ) { x = length; y = $0 } }END{ print y }'

UPD : สรุปคำแนะนำทั้งหมดในความคิดเห็น

awk 'length > max_length { max_length = length; longest_line = $0 } END { print longest_line }' ./text 

3
ทั้งการเรียกคำสั่งอื่น ( cat) และการใช้ไพพ์เป็นการดำเนินการที่มีราคาแพงไม่ต้องพูดถึงว่ามันมีประสิทธิภาพมากกว่าสำหรับ awk ที่จะอ่านไฟล์ ผลกระทบประสิทธิภาพการทำงานที่เห็นได้ชัดเจนแน่นอนหากทำบ่อย ๆ catและยังให้คุณทางที่ผิดอย่างสมบูรณ์
Chris Down

7
@laebshade มีเหตุผลอย่างหนึ่งคือ - คุณไม่จำเป็นต้องจำว่าคำสั่งใดใช้ชื่อไฟล์และคำสั่งใดหรือไม่สนใจว่าคำสั่งใดจะทำงานเป็นอันดับแรกในไพพ์ไลน์ หากคุณกำลังจะเขียนสคริปต์ที่ทำงานเป็นประจำโดยทั้งหมดหมายความว่ากังวลเกี่ยวกับสิ่งนี้ หากคุณกำลังเขียนสิ่งหนึ่งครั้งเพื่อค้นหาบรรทัดที่ยาวที่สุดในไฟล์กระบวนการพิเศษและจำนวนเวลาที่ใช้จะไม่เกี่ยวข้องอย่างสมบูรณ์ มันโง่ที่ผู้คนหมกมุ่นอยู่กับมันที่นี่มันช่างน่าเหลือเชื่อ
Michael Mrozek

4
@ Keith Thompson: catไม่มีประโยชน์ที่นี่ มันอาจจะไร้ประโยชน์กับคอมพิวเตอร์ แต่สำหรับผู้อ่านที่เป็นมนุษย์ก็สามารถให้คุณค่า ตัวแปรแรกแสดงอินพุตที่ชัดเจน การไหลเป็นธรรมชาติมากขึ้น (จากซ้ายไปขวา) ในกรณีที่สองคุณไม่ทราบว่าอินพุตคืออะไรเว้นแต่คุณจะเลื่อนหน้าต่าง
jfs

1
@JFSebastian catแม้ว่าคุณต้องการที่ด้านซ้ายที่คุณไม่จำเป็นต้อง < file commandทำงานได้ดี
Chris Down

3
@JFSebastian: ความจริงที่ว่าการเปลี่ยนเส้นทางสามารถเขียนได้ที่จุดเริ่มต้นของคำสั่งนั้นค่อนข้างคลุมเครือ < filename commandเทียบเท่ากับfilename < commandทุกเชลล์ที่ฉันได้ลอง แต่เมื่อคุณทราบแล้วคุณสามารถใช้ประโยชน์จากมันเมื่อเขียนท่อยาว ๆ ที่แสดงทิศทางการไหลของข้อมูลอย่างชัดเจน (โดยไม่ต้องเรียกใช้คำสั่งพิเศษ):< input-file command1 | command2 | command3 > output-file
Keith Thompson

6
cat filename | awk '{ print length }' | sort -n | tail -1

+1 มีวิธีแก้ไขปัญหาที่น่าสนใจมากมาย แต่นี่เป็นวิธีที่ง่ายที่สุด (มันจะง่ายกว่าถ้าไม่มีแมวโดยให้ awk อ่านไฟล์ แต่ทำไมต้องเล่นลิ้น?)
user1683793

5
sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file

นี่เป็นครั้งแรกที่อ่านไฟล์ภายในการทดแทนคำสั่งและส่งออกความยาวของบรรทัดที่ยาวที่สุด (ก่อนหน้านี้expandแปลงแท็บไปที่ช่องว่างเพื่อเอาชนะความหมายของwc -L- แต่ละแท็บในบรรทัดจะเพิ่ม8 แทน 1ถึงความยาวบรรทัด) ความยาวนี้จะถูกใช้ในsedนิพจน์หมายถึง "ค้นหาบรรทัดจำนวนอักขระที่มีความยาวพิมพ์แล้วออกจาก" ดังนั้นนี่อาจเป็นสิ่งที่ดีที่สุดเนื่องจากเส้นที่ยาวที่สุดอยู่ใกล้กับด้านบนของไฟล์ heheh (ขอบคุณที่ fered สำหรับความคิดเห็นที่ยอดเยี่ยมและสร้างสรรค์)

อีกอย่างหนึ่งที่ฉันคิดไว้ก่อนหน้าหนึ่งคือ:

#!/bin/bash
while read -r line; do
    (( ${#line} > max )) && max=${#line} && longest="$line"
done
echo "$longest"

2
วิธีนี้แพงและช้ามาก
Chris Down

2
@Chris Down: โอ้ใช่มันเป็น แต่คำถามก็เกี่ยวกับวิธีการเรียงลำดับไม่ได้มีประสิทธิภาพมากที่สุด WOrks ประณีตสำหรับไฟล์ขนาดเล็กถึงขนาดกลางหรืองานที่ไม่สำคัญ
ata

3
คำเตือน : ตัวเลือกของ wc -L, --max-line-lengthพิมพ์ความยาวของบรรทัดที่ยาวที่สุดตามหน้า man แต่ถ้าคุณขุดลึกลงไป (เช่นเมื่อคุณได้ผลลัพธ์ที่ผิด / ไม่คาดคิด ) คุณพบว่าตัวเลือกนี้เพิ่มความยาว8สำหรับแต่ละแท็บ char 1\x09 ดูUnix & Linux Q / A นี้
Peter.O

PS คำตอบของคุณจะพิมพ์บรรทัด "ยาวที่สุดเท่ากัน" ซึ่งอาจเป็นสิ่งที่ดี ... หากต้องการบังคับให้wcนับเพียง 1 อักขระต่อแท็บการทำงานนี้ sed -rn "/.{$(<file expand -t1 |wc -L)}/p" file
Peter.O

1
read lineจะตีความตัวอักษรทับขวาหนีเป็นถ่านที่แท้จริงเช่น\Aresloves ไปAซึ่งแน่นอนอย่างมีประสิทธิภาพรายงานสั้นกว่าที่เกิดขึ้นจริงไบต์การใช้งาน ... เพื่อป้องกันไม่ให้หลบหนีread -r lineการตีความใช้: . . . นอกจากนี้จะทำให้sed + wcรุ่นเลิกหลังจากที่ครั้งแรก "สายที่ยาวที่สุด" การเปลี่ยนแปลงpที่จะ{p;q}..sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file
Peter.O

4

นี่คือทางออกของ Perl:

perl -e 'while(<>){
           $l=length;  
           $l>$m && do {$c=$_; $m=$l}  
         } print $c' file.txt 

หรือถ้าคุณต้องการพิมพ์บรรทัดที่ยาวที่สุดทั้งหมด

perl -e 'while(<>){
           $l=length;
           push @{$k{$l}},$_;
           $m=$l if $l>$m;
         } print @{$k{$m}}' file.txt 

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

พิมพ์บรรทัดที่ยาวที่สุดเพียงบรรทัดเดียว:

$ time perl -e 'while(<>){
           $l=length;  
           $l>$m && do {$c=$_; $m=$l}  
         } print $c' file.txt 
real    0m3.837s
user    0m3.724s
sys     0m0.096s



$ time awk 'length > max_length { max_length = length; longest_line = $0 }
 END { print longest_line }' file.txt
real    0m5.835s
user    0m5.604s
sys     0m0.204s



$ time sed -rn "/.{$(<file.txt expand -t1 |wc -L)}/{p;q}" file.txt 
real    2m37.348s
user    2m39.990s
sys     0m1.868s

พิมพ์บรรทัดที่ยาวที่สุดทั้งหมด:

$ time perl -e 'while(<>){
           $l=length;
           push @{$k{$l}},$_;
           $m=$l if $l>$m;
         } print @{$k{$m}}' file.txt 
real    0m9.263s
user    0m8.417s
sys     0m0.760s


$ time awk 'length >x { delete y; x=length }
     length==x { y[NR]=$0 } END{ for (z in y) print y[z] }' file.txt
real    0m10.220s
user    0m9.925s
sys     0m0.252s


## This is Chris Down's bash solution
$ time ./a.sh < file.txt 
Max line length: 254
Lines matched with that length: 2
real    8m36.975s
user    8m17.495s
sys     0m17.153s

3

grep บรรทัดแรกยาวที่สุด

grep -Em1 "^.{$(wc -L <file.txt)}\$" file.txt 

คำสั่งนั้นอ่านยากผิดปกติโดยไม่มีการฝึกฝนเพราะมันผสมไวยากรณ์ของเชลล์และ regexp
สำหรับคำอธิบายฉันจะใช้รหัสเทียมแบบง่ายก่อน บรรทัดที่ขึ้นต้นด้วย##อย่ารันในเชลล์
รหัสที่ใช้ง่ายนี้ใช้ชื่อไฟล์ F และทำให้การอ้างอิงและส่วนต่าง ๆ ของ regexps อ่านได้ง่าย

มันทำงานอย่างไร

คำสั่งมีสองส่วนคือgrep- และการwcร้องขอ:

## grep "^.{$( wc -L F )}$" F

wcถูกนำมาใช้ในการขยายกระบวนการดังนั้นจึงมีการเรียกใช้ก่อน$( ... ) grepมันจะคำนวณความยาวของเส้นที่ยาวที่สุด ไวยากรณ์การขยายเชลล์ผสมกับไวยากรณ์รูปแบบการแสดงออกปกติในทางที่สับสนดังนั้นฉันจะสลายการขยายกระบวนการ:

## wc -L F
42
## grep "^.{42}$" F

ที่นี่การขยายกระบวนการถูกแทนที่ด้วยค่าที่จะกลับมาสร้างgrepcommandline ที่ใช้ ตอนนี้เราสามารถอ่านนิพจน์ทั่วไปได้ง่ายขึ้น: มันจับคู่กันตั้งแต่เริ่มต้น ( ^) ถึงท้าย ( $) ของบรรทัด การแสดงออกระหว่างพวกเขาตรงกับตัวละครใด ๆ ยกเว้นการขึ้นบรรทัดใหม่ซ้ำแล้วซ้ำอีก 42 ครั้ง รวมกันนั่นคือบรรทัดที่ประกอบด้วยอักขระ 42 ตัว


ตอนนี้กลับไปที่คำสั่งเชลล์จริง: grepตัวเลือก-E( --extended-regexp) ช่วยให้ไม่สามารถหลบหนี{}เพื่อความสะดวกในการอ่าน ตัวเลือก-m 1( --max-count=1) ทำให้หยุดหลังจากพบบรรทัดแรก <ในwcคำสั่งเขียนไฟล์เพื่อ stdin ของตนเพื่อป้องกันwcจากการพิมพ์ชื่อไฟล์พร้อมกับความยาว

สายไหนยาวที่สุด?

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

f="file.txt"

แสดงบรรทัดที่ยาวที่สุดแรก - บรรทัดแรกที่ยาวที่สุดเท่าที่ยาวที่สุด:

grep -E -m1 "^.{$(wc -L <"$f")}\$" "$f"

แสดงบรรทัดที่ยาวที่สุดทั้งหมด - ทุกบรรทัดที่มีความยาวเป็นบรรทัดที่ยาวที่สุด:

grep -E "^.{$(wc -L <"$f")}\$" "$f" 

แสดงบรรทัดที่ยาวที่สุดสุดท้าย - บรรทัดสุดท้ายที่ยาวเท่ากับบรรทัดที่ยาวที่สุด:

tac "$f" | grep -E -m1 "^.{$(wc -L <"$f")}\$"

แสดงบรรทัดที่ยาวที่สุดเดียว - บรรทัดที่ยาวที่สุดยาวกว่าบรรทัดอื่นทั้งหมดหรือล้มเหลว:

[ $(grep -E "^.{$(wc -L <"$f")}\$" "$f" | wc -l) = 1 ] && grep -E "^.{$(wc -L <"$f")}\$" "$f" 

(คำสั่งสุดท้ายนั้นไม่มีประสิทธิภาพมากกว่าคำสั่งอื่น ๆ เนื่องจากทำซ้ำคำสั่ง grep ที่สมบูรณ์มันควรจะถูกย่อยสลายอย่างชัดเจนเพื่อให้ผลลัพธ์ของwcและบรรทัดที่เขียนโดยgrepถูกบันทึกลงในตัวแปร
โปรดทราบว่าบรรทัดที่ยาวที่สุดอาจเป็นบรรทัดทั้งหมด . สำหรับการบันทึกในตัวแปรเพียงสองบรรทัดแรกเท่านั้นที่จะถูกเก็บไว้)


ว้าวคำตอบที่ดีเรียนรู้มากมายจากมัน ขอบคุณ
somethingSomething

2

ตัวอย่างต่อไปนี้จะเป็นและควรได้รับความคิดเห็นต่อคำตอบของ dmitry.malikovแต่เนื่องจากการใช้พื้นที่แสดงความคิดเห็นที่ไร้ประโยชน์ นั้นฉันจึงเลือกที่จะนำเสนอที่นี่ซึ่งอย่างน้อยที่สุดก็จะเห็น ..

นี่เป็นรูปแบบที่เรียบง่ายของวิธีการ single-pass awk ของdmitry
มันพิมพ์บรรทัด "ยาวที่สุด" เท่ากันทั้งหมด (หมายเหตุdelete arrayคือส่วนขยายเพ่งพิศ)

awk 'length >x { delete y; x=length }
     length==x { y[NR]=$0 } END{ for (z in y) print y[z] }' file

1

ในทุบตีบริสุทธิ์:

#!/bin/bash

_max_length=0
while IFS= read -r _line; do
    _length="${#_line}"
    if (( _length > _max_length )); then
        _max_length=${_length}
        _max_line=( "${_line}" )
    elif (( _length == _max_length )); then
        _max_line+=( "${_line}" )
    fi
done

printf 'Max line length: %d\n' "${_max_length}"
printf 'Lines matched with that length: %d\n' "${#_max_line[@]}"
(( ${#_max_line[@]} )) && printf '%s\n' '----------------' "${_max_line[@]}"

ตามที่เป็นอยู่รหัสสามารถส่งคืนผลลัพธ์ที่ไม่ถูกต้อง การตั้งค่า_max_line[0]=${_line}จะไม่ลบส่วนที่เหลือของ "บรรทัดที่ยาวที่สุด" ที่สะสมไว้ก่อนหน้านี้ใด ๆ ... unset _max_lineจะล้างอาร์เรย์ทั้งหมด ...
Peter.O

@fered ขอบคุณสำหรับสิ่งที่เขียนค่อนข้างเร็ว คงที่
Chris Down

0

ฉันได้พัฒนาเชลล์สคริปต์ขนาดเล็กสำหรับสิ่งนี้ มันแสดงความยาวบรรทัด # และบรรทัดตัวเองตามความยาวที่เกินขนาดเฉพาะเช่น 80 ตัวอักษร:

#!/bin/sh

# Author: Surinder

if test $# -lt 2
then
   echo "usage: $0 length file1 file2 ..."
   echo "usage: $0 80 hello.c"
   exit 1
fi

length=$1

shift

LONGLINE=/tmp/longest-line-$$.awk

cat << EOF > $LONGLINE
  BEGIN {
  }

  /.*/ {
    current_length=length(\$0);
    if (current_length >= expected_length) {
       printf("%d at line # %d %s\n", current_length, NR, \$0);
    }
  }

  END {
  }
EOF

for file in $*
do
  echo "$file"
  cat $file | awk -v expected_length=$length -f $LONGLINE |sort -nr
done

rm $LONGLINE

https://github.com/lordofrain/tools/blob/master/longest-line/longest-line.sh


1
มีการปรับปรุงเล็กน้อยที่คุณสามารถทำได้ พูดตัวแปรของคุณ สิ่งนี้จะแตกชื่อไฟล์ใด ๆ ที่มีช่องว่างหรือตัวอักษรแปลก ๆ ใช้$*เป็นไม่ค่อยมีความคิดที่ดีที่คุณต้องการ "$@"/.*/ในของคุณawkไม่ได้ทำอะไรตั้งแต่ที่ตรงกับสายที่ว่างเปล่าเช่นกัน คุณสามารถหลีกเลี่ยงการหลบหนีถ้าคุณอ้างเดียว\$0 'EOF'เหตุใดจึงต้องใช้BEGIN{}บล็อกที่ว่างเปล่า ในที่สุดคุณไม่ต้องการcatเพียงแค่awk . . . "$file" | . . .
terdon

1
คุณยังสามารถทำสิ่งทั้งหมดใน awk ได้โดยตรง:awk -vmax=15 '{len=length($0); if(len>=max){printf("%s, %d at line # %d %s\n", FILENAME, len, NR, $0);}}' file*
terdon

-3

คุณสามารถใช้wc:

wc -L fileName

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