วิธีค้นหาบรรทัดที่มีอักขระน้อยที่สุด


22

ฉันกำลังเขียนเชลล์สคริปต์โดยใช้คำสั่ง UNIX ทั่วไป ฉันต้องดึงสายที่มีตัวอักษรน้อยที่สุด (รวมช่องว่าง) สามารถมีได้สูงสุดประมาณ 20 บรรทัด

ฉันรู้ว่าฉันสามารถใช้head -$L | tail -1 | wc -mเพื่อค้นหาจำนวนตัวอักษรของบรรทัด L ปัญหาคือวิธีเดียวที่ฉันสามารถคิดได้โดยใช้สิ่งนั้นคือการเขียนคำสั่งที่ยุ่งเหยิงหากเปรียบเทียบกับค่าต่างๆ

ข้อมูลตัวอย่าง:

seven/7
4for
8 eight?
five!

จะกลับมา4forเนื่องจากบรรทัดนั้นมีอักขระน้อยที่สุด

ในกรณีของฉันหากหลายบรรทัดมีความยาวสั้นที่สุดควรจะคืนค่าหนึ่งบรรทัด ไม่สำคัญว่าจะเลือกรายการใดตราบใดที่มีความยาวต่ำสุด แต่ฉันไม่เห็นอันตรายในการแสดงทั้งสองวิธีสำหรับผู้ใช้รายอื่นที่มีสถานการณ์อื่น


5
เกิดอะไรขึ้นถ้ามีหลายบรรทัดที่มีความยาวเป็น 4 พวกเขาควรจะพิมพ์ด้วยหรือไม่
ความโกลาหล

ในกรณีของฉันหากหลายบรรทัดมีความยาวสั้นที่สุดควรจะคืนค่าหนึ่งบรรทัด ไม่สำคัญว่าจะเลือกรายการใดตราบใดที่มีความยาวต่ำสุด แต่ฉันไม่เห็นอันตรายในการแสดงทั้งสองวิธีสำหรับผู้ใช้รายอื่นที่มีสถานการณ์อื่น
Matthew D. Scholefield

คำตอบ:


13

วิธี Perl โปรดทราบว่าหากมีหลายบรรทัดที่มีความยาวสั้นที่สุดเท่ากันวิธีการนี้จะพิมพ์หนึ่งบรรทัดเท่านั้น:

perl -lne '$m//=$_; $m=$_ if length()<length($m); END{print $m if $.}' file 

คำอธิบาย

  • perl -lne: -nหมายถึง "อ่านไฟล์อินพุตบรรทัดต่อบรรทัด" -lทำให้บรรทัดใหม่ต่อท้ายถูกลบออกจากแต่ละบรรทัดอินพุตและขึ้นบรรทัดใหม่เพื่อเพิ่มในแต่ละการprintโทร และ-eเป็นสคริปต์ที่จะใช้กับแต่ละบรรทัด
  • $m//=$_: ตั้ง$mเป็นบรรทัดปัจจุบัน ( $_) เว้นแต่$mจะมีการกำหนดไว้ //=ผู้ประกอบการสามารถใช้ได้ตั้งแต่ Perl 5.10.0
  • $m=$_ if length()<length($m)ถ้าความยาวของมูลค่าปัจจุบันของ$mมีค่ามากกว่าความยาวของบรรทัดปัจจุบันบันทึกบรรทัดปัจจุบัน ( $_) $mในฐานะ
  • END{print $m if $.}: เมื่อประมวลผลทุกบรรทัดแล้วให้พิมพ์ค่าปัจจุบันของ$mบรรทัดที่สั้นที่สุด การif $.ตรวจสอบให้แน่ใจว่าสิ่งนี้จะเกิดขึ้นเฉพาะเมื่อมีการกำหนดหมายเลขบรรทัด ( $.) เพื่อหลีกเลี่ยงการพิมพ์บรรทัดว่างสำหรับอินพุตว่าง

อีกทางหนึ่งเนื่องจากไฟล์ของคุณมีขนาดเล็กพอที่จะใส่ในหน่วยความจำคุณสามารถทำได้:

perl -e '@K=sort{length($a) <=> length($b)}<>; print "$K[0]"' file 

คำอธิบาย

  • @K=sort{length($a) <=> length($b)}<>: <>นี่คืออาเรย์ที่องค์ประกอบเป็นเส้นของไฟล์ จะจัดเรียงพวกเขาตามความยาวของพวกเขาและเส้นเรียงจะถูกบันทึกไว้เป็นแถวsort@K
  • print "$K[0]": พิมพ์องค์ประกอบแรกของอาร์เรย์@K: บรรทัดที่สั้นที่สุด

หากคุณต้องการพิมพ์บรรทัดที่สั้นที่สุดทั้งหมดคุณสามารถใช้

perl -e '@K=sort{length($a) <=> length($b)}<>; 
         print grep {length($_)==length($K[0])}@K; ' file 

1
เพิ่ม-Cเพื่อวัดความยาวในรูปของจำนวนอักขระแทนที่จะเป็นจำนวนไบต์ ในโลแคล UTF-8 $$มีจำนวนไบต์น้อยกว่า(2 vs 3) แต่มีอักขระมากกว่า (2 vs 1)
Stéphane Chazelas

17

ด้วยsqlite3:

sqlite3 <<EOT
CREATE TABLE file(line);
.import "data.txt" file
SELECT line FROM file ORDER BY length(line) LIMIT 1;
EOT

ที่หนึ่งที่ชื่นชอบที่นี่ไม่เคยคิดว่าของ SQL ...
ความวุ่นวาย

2
นี่คือรหัสสถานะกอล์ฟที่ชาญฉลาด
shadowtalker

2
สิ่งนี้จะอ่านไฟล์ทั้งหมดในหน่วยความจำและ / หรือสร้างสำเนาบนดิสก์ตัวที่สองหรือไม่ ถ้าเป็นเช่นนั้นมันฉลาด แต่ไม่มีประสิทธิภาพ
John Kugelman สนับสนุน Monica

1
@JohnKugelman ซึ่งอาจดูดซับทั้ง 4 บรรทัดลงในฐานข้อมูลหน่วยความจำชั่วคราวเท่านั้น (นั่นคือสิ่งที่straceบ่งชี้) หากคุณต้องการทำงานกับไฟล์ที่มีขนาดใหญ่มาก ๆ (และระบบของคุณไม่ได้ทำการแลกเปลี่ยน) คุณสามารถบังคับได้โดยการต่อท้ายชื่อไฟล์เช่นเดียวกับsqlite3 $(mktemp)ข้อมูลทั้งหมดจะถูกเขียนลงดิสก์
FloHimself

ฉันได้รับข้อผิดพลาดดังต่อไปนี้: "" "xaa: 8146: ตัวอักษร" "" "และ" "" xaa: 8825: คาดว่าจะมี 1 คอลัมน์ แต่พบ 2 คอลัมน์ - ละเว้นพิเศษ "" "ไฟล์ประกอบด้วยเอกสาร json 1 ต่อแต่ละบรรทัด .
Ahmedov

17

ต่อไปนี้เป็นawkวิธีแก้ไขปัญหาสำหรับการพิมพ์บรรทัดต่ำสุดที่พบครั้งแรก:

awk '
  NR==1 || length<len {len=length; line=$0}
  END {print line}
'

ซึ่งสามารถขยายได้ด้วยเงื่อนไขเดียวเพื่อพิมพ์บรรทัดขั้นต่ำทั้งหมด:

awk '
  length==len {line=line ORS $0}
  NR==1 || length<len {len=length; line=$0}
  END {print line}'
'

12

Python ออกมาค่อนข้างรัดกุมและรหัสทำในสิ่งที่พูดกับดีบุก:

python -c "import sys; print min(sys.stdin, key=len),"

เครื่องหมายจุลภาคสุดท้ายคือปิดบังฉันยอมรับ ช่วยป้องกันคำสั่งการพิมพ์เพิ่ม linebreak เพิ่มเติม นอกจากนี้คุณสามารถเขียนสิ่งนี้ใน Python 3 ที่รองรับ 0 บรรทัดเช่น:

python3 -c "import sys; print(min(sys.stdin, key=len, default='').strip('\n'))"


กระป๋องพูดว่าอะไร?
mikeserv

@mikeserve: มันบอกว่า "พิมพ์ต่ำสุดของ sys.stdin ใช้ len เป็นกุญแจสำคัญใน" ;-)
สตีฟเจสซอพ

1
อ่า ไม่มีอะไรเกี่ยวกับขนาดไบนารี่, การพึ่งพาการคืบหรือเวลาในการประมวลผล?
mikeserv

2
@mikeserv: ไม่พิมพ์เล็ก ๆ ไม่ได้อยู่ในกระป๋อง มันอยู่ในเอกสารแนะนำในตู้เก็บเอกสารที่ถูกล็อกในห้องใต้ดินด้านหลังประตูที่ระบุว่า "ระวังเสือดาว"
Steve Jessop

Gotcha - บนจอแสดงผล
mikeserv

10

ฉันมักจะชอบวิธีการแก้ปัญหาด้วยสคริปต์เปลือกบริสุทธิ์ (ไม่มี exec!)

#!/bin/bash
min=
is_empty_input="yes"

while IFS= read -r a; do
    if [ -z "$min" -a "$is_empty_input" = "yes" ] || [ "${#a}" -lt "${#min}" ]; then
        min="$a"
    fi
    is_empty_input="no"
done

if [ -n "$a" ]; then
    if [ "$is_empty_input" = "yes" ]; then
        min="$a"
        is_empty_input="no"
    else
        [ "${#a}" -lt "${#min}" ] && min="$a"
    fi
fi

[ "$is_empty_input" = "no" ] && printf '%s\n' "$min"

หมายเหตุ :

มีปัญหากับ NUL ไบต์ในอินพุต ดังนั้นprintf "ab\0\0\ncd\n" | bash this_scriptพิมพ์แทนabcd


นี่คือสิ่งที่บริสุทธิ์ที่สุด แม้ว่าการทดสอบในความงุ่มง่ามbashจะโน้มน้าวให้ฉันไปป์ไลน์ระดับกลางsortแทน
orion

2
คุณได้ลองผู้บริหารไม่มีผู้บริหาร! วิธีการแก้ปัญหาเมื่อเทียบกับคนอื่น ๆ ที่ทำ? นี่เป็นการเปรียบเทียบความแตกต่างของประสิทธิภาพระหว่างexec! และไม่มีผู้บริหาร! วิธีแก้ไขปัญหาที่คล้ายกัน การดำเนินกระบวนการแยกต่างหากไม่ค่อยมีประโยชน์มากเมื่อมันไปเดอร์ - ในรูปแบบเช่นvar=$(get data)เพราะมัน จำกัด การไหลของข้อมูลไปยังบริบทเดียว - แต่เมื่อคุณย้ายข้อมูลผ่านไปป์ไลน์ - ในสตรีม - โดยทั่วไป exec ที่ใช้แต่ละอันมีประโยชน์ แอปพลิเคชันของโมดูลาร์เฉพาะที่จำเป็นเท่านั้น
mikeserv

1
@DigitalTrauma - สตริงตัวเลขที่ต่อเนื่องกันของส่วนขยายไม่ได้รับการยกเว้นจากเงื่อนไขใด ๆ ที่ทำให้การอ้างถึงเชลล์จำเป็นกว่าสตริงส่วนขยายอื่น ๆ $IFSไม่ใช่ตัวเลือกดิจิตัล - แม้ว่าจะไม่มีค่าเริ่มต้น$IFSแม้ว่าเชลล์จำนวนมากจะยอมรับการกำหนดค่าสภาพแวดล้อมที่กำหนดไว้ล่วงหน้า$IFS- และนั่นไม่ใช่ค่าเริ่มต้นที่เชื่อถือได้เป็นพิเศษ
mikeserv


1
ขอบคุณสำหรับความคิดเห็นและ upvotes (ตัวแทนบางคนควรไปที่ @cuonglm เพื่อแก้ไขคำตอบของฉัน) โดยทั่วไปผมไม่แนะนำให้คนอื่นปฏิบัติประจำวันสคริปต์เปลือกบริสุทธิ์ แต่ทักษะที่สามารถพบได้ประโยชน์อย่างมากในสภาพอากาศที่รุนแรงบางอย่างที่ไม่มีอะไรอื่นนอกเหนือจากการเชื่อมโยงแบบคงที่/bin/shสามารถใช้ได้ มันเกิดขึ้นกับฉันหลายครั้งกับ SunOS4 โฮสต์ที่/usrสูญหายหรือ.soเสียหายและในยุค Linux ปัจจุบันฉันยังคงพบกับสถานการณ์ที่คล้ายกันในบางครั้งด้วยระบบฝังตัวหรือเริ่มระบบบูตที่ล้มเหลว BusyBox เป็นหนึ่งในสิ่งที่ยอดเยี่ยมที่เราเพิ่งได้รับ
yaegashi

9

นี่คือzshทางออกที่แท้จริง(มันพิมพ์ทุกบรรทัดด้วยความยาวน้อยที่สุดจากfile):

IFS=$'\n'; print -l ${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}

อินพุตตัวอย่าง:

seven/7
4for
8 eight?
five!
four

ผลลัพธ์คือ:

4for
four

ฉันคิดว่ามันต้องการคำอธิบายสั้น ๆ :-)


ก่อนอื่นเราตั้งตัวคั่นฟิลด์ภายในเป็นบรรทัดใหม่:

IFS=$'\n';

จนถึงตอนนี้ส่วนที่ยาก printใช้-lแฟล็กเพื่อพิมพ์ผลลัพธ์ที่คั่นด้วยการขึ้นบรรทัดใหม่แทนช่องว่าง

ตอนนี้เราเริ่มที่ด้านใน:

$(<file)

ไฟล์ถูกอ่านทีละบรรทัดและถือว่าเป็นอาร์เรย์ แล้ว:

${(o@)...//?/?}

oธงบอกว่าผลที่ควรจะได้รับคำสั่งในลำดับที่@หมายถึงการรักษาผลเป็นอาร์เรย์เกินไป ส่วนที่อยู่เบื้องหลัง ( //?/?) ?คือแทนที่มีแทนที่ตัวอักษรทั้งหมดที่มี ขณะนี้:

${~...[1]}

เราจะใช้องค์ประกอบอาร์เรย์แรกซึ่งเป็นที่สั้นที่สุดคือในกรณีของคุณในขณะนี้[1]????

${(M)$(<file):#...}

การจับคู่จะดำเนินการในแต่ละองค์ประกอบอาร์เรย์แยกกันและองค์ประกอบอาร์เรย์ที่ไม่ตรงกันจะถูกลบออก ( M) แต่ละองค์ประกอบที่ตรงกับ????(4 ตัวอักษร) จะยังคงอยู่ในอาร์เรย์ ดังนั้นองค์ประกอบที่เหลือคือองค์ประกอบที่มี 4 ตัวอักษร (องค์ประกอบที่สั้นที่สุด)

แก้ไข:หากคุณต้องการเพียงหนึ่งบรรทัดที่สั้นที่สุดเวอร์ชันที่แก้ไขนี้จะพิมพ์บรรทัดแรก:

IFS=$'\n'; print -l ${${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}[1]}

8
tr -c \\n 1 <testfile |   #first transform every [^\n] char to a 1
grep -nF ''           |   #next get line numbers
paste -d: - testfile  |   #then paste it together with itself
sort  -t: -nk2,2          #then sort on second field

... และผู้ชนะคือ ... บรรทัดที่ 2 มันจะดูเหมือน

2:1111:4for
4:11111:five!
1:1111111:seven/7
3:11111111:8 eight?

แต่ปัญหาที่เกิดขึ้นก็คือทุกบรรทัดต้องมีความยาวมากกว่าสองเท่าเพื่อให้มันทำงานได้ดังนั้นLINE_MAXจึงลดลงครึ่งหนึ่งอย่างมีประสิทธิภาพ สาเหตุก็คือมันใช้งานอยู่ - อะไรคือฐาน 1? - เพื่อแสดงความยาวของเส้น แนวทางที่คล้ายกันและเป็นระเบียบมากขึ้นอาจบีบอัดข้อมูลนั้นในสตรีม ความคิดแรกตามแนวที่เกิดขึ้นกับฉันคือฉันควรจะunexpand:

tr -c \\n \  <testfile    |   #transform all [^\n] to <space>
unexpand -t10             |   #squeeze every series of 10 to one tab
grep -nF ''               |   #and get the line numbers
sed    's/:/!d;=;:/;h;:big    #sed compares sequential lines
$P;$!N; /\(:[^ ]*\)\( *\)\n.*\1.*\2/!D     #newest line is shorter or...
        g;/:./!q;b big'   |   #not; quit input entirely for blank line
sed -f - -e q testfile        #print only first occurrence of shortest line

พิมพ์ออกมา ...

2
4for

อีกหนึ่งเพียงsed:

sed -n '/^\n/D;s/\(.\)\(\n.*\)*/\1/g
$p;h;   s// /g;G;x;n;//!g;H;s// /g
G;      s/^\( *\)\(\n \1 *\)\{0,1\}\n//
D'      <infile >outfile

ไวยากรณ์เป็นไปตามมาตรฐาน - แต่นั่นไม่รับประกันว่าsedจะมีคนเก่าจัดการ\(reference-group\)\{counts\}อย่างถูกต้อง - หลายคนไม่ได้

โดยทั่วไปแล้วจะใช้ regexp เดียวกันกับการป้อนข้อมูลซ้ำ ๆ - ซึ่งจะเป็นประโยชน์มากเมื่อถึงเวลาที่จะรวบรวมพวกเขา รูปแบบนั้นคือ:

\(.\)\(\n.*\)*

ซึ่งจับคู่สตริงที่แตกต่างกันในวิธีต่างๆ ตัวอย่างเช่น:

string1\nstring2\nstring3

... จะถูกจับคู่กับsใน\1และสตริงใน''\2

1\nstring2\nstring3

... ถูกจับคู่กับ1ใน\1และ\nstring2\nstring3ใน\2

\nstring2\nstring3

... จะถูกจับคู่กับ\nใน\1และสตริงใน'' \2นี่จะเป็นปัญหาหากมีโอกาสที่\newline เกิดขึ้นที่ส่วนหัวของพื้นที่รูปแบบ - แต่ใช้คำสั่ง/^\n/Dและและ//!gเพื่อป้องกันสิ่งนี้ ฉันใช้ไปแล้ว[^\n]แต่ความต้องการอื่น ๆ สำหรับสคริปต์ตัวเล็กนี้ทำให้การพกพาเป็นเรื่องที่น่ากังวลและฉันก็ไม่พอใจกับหลาย ๆ วิธีที่มักถูกตีความผิด บวก.เร็วกว่า

\nstring2
string1

... จับคู่\nและsอีกครั้ง\1ทั้งคู่รับ''ค่าสตริง\2ว่าง บรรทัดว่างไม่ตรงกันเลย

เมื่อรูปแบบจะถูกนำไปใช้globallyสองอคติ - ทั้งอคติซ้ายสุดมาตรฐานและเลสเบี้ยนด้านขวา\newline อคติ - มีการทวนความสมดุลจะทำให้เกิดการข้าม ตัวอย่างบางส่วน:

s/\(.\)\(\n.*\)*/\1:\2/g
s/\(.\)\(\n.*\)*/\2\1:/g
s/\(.\)\(\n.*\)*/\1: /g
s/\(.\)\(\n.*\)*/ :\2/g

... ถ้าใช้ทั้งหมด(ไม่ต่อเนื่อง)กับสตริงต่อไปนี้ ...

string1\nstring2

... จะแปลงเป็น ...

s:t:r:i:n:g:1:\nstring2
s:t:r:i:n:g:\nstring21:
s:t:r:i:n:g:1: 
 : : : : : : :\nstring2

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

รุ่นที่แตกต่างกันมีความจำเป็นสำหรับการเปรียบเทียบสตริง / สตริงตามตัวอักษร - ดังนั้นจะต้องมีเวอร์ชันของแต่ละบรรทัดที่อักขระทั้งหมดรับประกันว่าจะเท่ากัน แต่แน่นอนถ้าหนึ่งหรืออื่น ๆ ควรจบลงจริง ๆ แล้วเป็นบรรทัดที่สั้นที่สุดที่เกิดขึ้นเร็วที่สุดในบรรทัดแล้วพิมพ์บรรทัดที่ส่งออกไปอาจจะเป็นรุ่นดั้งเดิมของบรรทัด - ไม่ใช่คนที่ฉันได้ sanitized / homogenized เพื่อประโยชน์ของการเปรียบเทียบ ดังนั้นฉันต้องการสองรุ่นแต่ละรุ่น

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

อย่างไรก็ตามสำหรับแต่ละรอบสิ่งแรกที่เกิดขึ้นคือการเปลี่ยนแปลงในบรรทัดที่จดจำ - เนื่องจากสำเนาที่บันทึกจริงเท่านั้นคือต้นฉบับดั้งเดิม - เป็น ...

^               \nremembered line$

... และหลังจากนั้นnบรรทัดอินพุตอินพุตจะเขียนทับบัฟเฟอร์เก่าใด ๆ หากไม่มีอย่างน้อยหนึ่งตัวอักษรก็จะถูกละเว้นอย่างมีประสิทธิภาพ มันจะง่ายกว่าที่จะqใช้บรรทัดแรกที่ว่างเปล่า แต่ข้อมูลการทดสอบของฉันมีจำนวนมากและฉันต้องการจัดการหลายย่อหน้า

ดังนั้นถ้ามันมีตัวอักษรเวอร์ชันที่เป็นตัวอักษรจะถูกต่อท้ายบรรทัดที่จดจำและเวอร์ชันเปรียบเทียบที่เว้นระยะจะอยู่ในตำแหน่งที่ส่วนหัวของรูปแบบพื้นที่เช่นนี้:

^   \n               \nremembered line\nnew$

การทดแทนครั้งสุดท้ายจะใช้กับพื้นที่รูปแบบนั้น:

s/^\( *\)\(\n \1 *\)\{0,1\}\n//

ดังนั้นหากบรรทัดใหม่สามารถพอดีกับพื้นที่ที่ต้องการเพื่อให้มีบรรทัดที่ถูกจดจำด้วยอักขระอย่างน้อยหนึ่งตัวเพื่อสำรองดังนั้นสองบรรทัดแรกจะถูกแทนที่ออกไปมิฉะนั้นจะเป็นบรรทัดแรกเท่านั้น

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

new

... ถูกส่งกลับไปที่การทดแทนครั้งแรกในรอบซึ่งจะตัดเฉพาะจากตัวอักษรขึ้นบรรทัดใหม่ตัวแรก - และดังนั้นจึงยังคงอยู่ทั้งหมด แต่ถ้ามันไม่ได้เป็นสตริง ...

remembered line\nnew

... จะเริ่มรอบถัดไปแทนและการแทนที่ครั้งแรกจะตัดจากสตริงนั้น ...

\nnew

...ทุกเวลา.

ในบรรทัดสุดท้ายบรรทัดที่จดจำจะถูกพิมพ์ไปที่มาตรฐานและดังนั้นสำหรับข้อมูลตัวอย่างที่ให้มามันจะพิมพ์:

4for

trแต่อย่างจริงจังใช้



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

@ DigitalTrauma - ไม่น่าจะเป็นไปได้ แต่มันไม่ค่อยมีประโยชน์มากหากไม่มีพวกเขา - และพวกมันก็มาถูกอย่างมาก เมื่อทำงานกระแสฉันมักจะต้องการรวมถึงวิธีการทำซ้ำของต้นฉบับที่เหมือนกันในการส่งออก - หมายเลขบรรทัดทำให้เป็นไปได้ที่นี่ REINPUT | sort -t: -nk1,1 | cut -d: -f3-ตัวอย่างเช่นการเปิดผลของท่อรอบแรก: และอย่างที่สองก็เป็นเรื่องง่าย ๆ ที่รวมเอาsed --expressionสคริปต์อื่นไว้ที่ท้าย
mikeserv

@DigitalTrauma - โอ้และในตัวอย่างแรกหมายเลขบรรทัดไม่ส่งผลกระทบต่อsortพฤติกรรมเป็นผูกเบรกเมื่อสายเดียวกันที่มีความยาวที่เกิดขึ้นในการป้อนข้อมูล - เพื่อให้เส้นที่เกิดขึ้นเร็วที่สุดเท่าที่มักจะลอยไปด้านบนในกรณีที่
mikeserv

7

ลอง:

awk '{ print length, $0 }' testfile | sort -n | cut -d" " -f2- | head -1

แนวคิดคือการใช้awkพิมพ์ความยาวของแต่ละบรรทัดก่อน สิ่งนี้จะปรากฏเป็น:

echo "This is a line of text" | awk '{print length, $0}'
22 This is a line of text

จากนั้นใช้นับจำนวนตัวอักษรในการจัดเรียงสายโดยsort, cutการกำจัดของการนับและheadเพื่อให้บรรทัดแรก (หนึ่งกับตัวละครน้อยที่) แน่นอนคุณสามารถใช้tailเพื่อให้สอดคล้องกับตัวละครมากที่สุดในกรณีนี้

(นี่เป็นลูกบุญธรรมจากคำตอบนี้ )


+1 สำหรับตรรกะ แต่มันจะไม่ทำงานในทุกกรณี หากสองบรรทัดมีจำนวนอักขระเท่ากันและต่ำสุด มันจะให้คุณบรรทัดแรกเท่านั้นที่พบเพราะhead -1
Thushi

เพื่อให้ได้บรรทัดที่ยาวที่สุดมีประสิทธิภาพกว่าเล็กน้อยในการย้อนกลับการเรียงลำดับกว่าที่จะใช้tail( headสามารถออกได้ทันทีที่งานเสร็จสิ้นโดยไม่อ่านอินพุตที่เหลือ)
Toby Speight

@Thushi โดยใช้ regex เล็กน้อยหลังจากพิมพ์หมายเลขบรรทัดทุกอย่างยกเว้นบรรทัดที่มีหมายเลขเดียวกับบรรทัดที่ 1 สามารถลบออกได้ดังนั้นจึงแสดงผลลัพธ์ของบรรทัดที่สั้นที่สุดทั้งหมด
Matthew D. Scholefield

5

ด้วย POSIX awk:

awk 'FNR==1{l=$0;next};length<length(l){l=$0};END{print l}' file

มันจะไม่ทำงานหากมีมากกว่าหนึ่งบรรทัดมีจำนวนตัวอักษรเท่ากันและขั้นต่ำสุดด้วย
Thushi

@Thushi: มันจะรายงานบรรทัดขั้นต่ำแรก
cuonglm

ใช่ แต่นั่นไม่ใช่เอาท์พุทที่ถูกต้องใช่มั้ย แม้แต่บรรทัดอื่น ๆ ก็มีจำนวนอักขระขั้นต่ำ
Thushi

1
@Thushi: นั่นไม่ได้กล่าวถึงความต้องการ OP รอการปรับปรุงจาก OP
cuonglm

3
ฉันไม่คิดว่าLเป็นตัวอักษรที่ดีที่สุดในการเลือกชื่อตัวแปร: D สิ่งที่ต้องการminจะทำในสิ่งที่ชัดเจนมากขึ้น
fedorqui

3

การยืมความคิดของ @ mikeserv:

< testfile sed 'h;s/./:/g;s/.*/expr length "&"/e;G;s/\n/\t/' | \
sort -n | \
sed -n '1s/^[0-9]+*\t//p'

คนแรกsedทำสิ่งต่อไปนี้:

  • h บันทึกบรรทัดต้นฉบับลงในบัฟเฟอร์พัก
  • แทนที่ตัวละครทุกตัวในบรรทัดด้วย : - นี่คือการกำจัดอันตรายจากการฉีดรหัส
  • แทนที่ทั้งบรรทัดด้วย expr length "whole line" - นี่คือนิพจน์เชลล์ซึ่งอาจถูกประเมิน
  • คำสั่ง esเป็นส่วนขยาย sed ของ GNUเพื่อประเมินพื้นที่รูปแบบและนำผลลัพธ์กลับมาในพื้นที่รูปแบบ
  • G เพิ่มบรรทัดใหม่และเนื้อหาของพื้นที่พัก (บรรทัดเดิม) ไปยังพื้นที่รูปแบบ
  • สุดท้ายsแทนที่ขึ้นบรรทัดใหม่ด้วยแท็บ

จำนวนตัวอักษรตอนนี้เป็นตัวเลขที่จุดเริ่มต้นของแต่ละบรรทัดดังนั้นsort -nเรียงตามความยาวบรรทัด

สุดท้ายsedจะลบทั้งหมดยกเว้นบรรทัดแรก (สั้นที่สุด) และความยาวบรรทัดและพิมพ์ผลลัพธ์


1
@mikeserv ใช่ฉันคิดว่าexprดีกว่าที่นี่ ใช่eจะวางไข่เปลือกสำหรับแต่ละบรรทัด ฉันแก้ไขนิพจน์ sed เพื่อให้แทนที่อักขระแต่ละตัวในสตริงด้วย:ก่อนหน้า eval ซึ่งฉันคิดว่าควรลบความเป็นไปได้ของการฉีดโค้ด
บาดเจ็บทางดิจิตอล

ฉันมักจะเลือกxargs exprเป็นการส่วนตัว - แต่นอกเหนือจากการหลีกเลี่ยงเชลล์ระดับกลางนั่นอาจเป็นเรื่องโวหารมากกว่า ฉันชอบมันอยู่แล้ว
mikeserv

3

มันเกิดขึ้นกับฉันว่าทุกสิ่งเป็นไปได้ในsedการแสดงออกครั้งเดียว มันไม่สวยเลย:

$ sed '1h;s/.*/&\n&/;G;:l;s/\n[^\n]\([^\n]*\)\n[^\n]/\n\1\n/;tl;/\n\n/{s/\n.*//;x};${x;p};d' testfile
4for
$ 

ทำลายมันลง:

1h            # save line 1 in the hold buffer (shortest line so far)
s/.*/&\n&/    # duplicate the line with a newline in between
G             # append newline+hold buffer to current line
:l            # loop start
s/\n[^\n]\([^\n]*\)\n[^\n]/\n\1\n/
              # attempt to remove 1 char both from current line and shortest line
tl            # jump back to l if the above substitution succeeded
/\n\n/{       # matches if current line is shorter
  s/\n.*//    # remove all but original line
  x           # save new shortest line in hold buffer
}
${            # at last line
  x           # get shortest line from hold buffer
  p           # print it
}
d             # don't print any other lines

BSD sed ใน OS X ค่อนข้างจะพิถีพิถันยิ่งขึ้นกับการขึ้นบรรทัดใหม่ รุ่นนี้ใช้งานได้ทั้งรุ่น BSD และ GNU:

$ sed -e '1h;G;s/\([^\n]*\)\(\n\)\(.*\)/\1\2\1\2\3/;:l' -e 's/\(\n\)[^\n]\([^\n]*\n\)[^\n]/\1\2/;tl' -e '/\n\n/{s/\n.*//;x;};${x;p;};d' testfile
4for
$

โปรดทราบว่านี่เป็นคำตอบที่ "เป็นไปได้" มากกว่าความพยายามอย่างจริงจังที่จะให้คำตอบที่ดีที่สุด ฉันคิดว่ามันหมายความว่าฉันเล่นโค - โคฟมากเกินไป


@mikeserv จากman sedบน OS X: "หนีลำดับ \ n ตรงกับอักขระ newline ฝังตัวอยู่ในพื้นที่รูปแบบ" ดังนั้นฉันคิดว่า GNU sed อนุญาต\nใน regex และในการแทนที่ในขณะที่ BSD อนุญาตเฉพาะ\nใน regex ไม่ใช่ในการแทนที่
Digital Trauma

การยืม\nจาก pattern space เป็นความคิดที่ดีและสามารถใช้งานในs///นิพจน์ที่สองได้แต่s/.*/&\n&/นิพจน์นั้นแทรก\nเข้าไปใน space pattern ซึ่งไม่เคยมีมาก่อน นอกจากนี้ BSD sed ยังต้องการบรรทัดใหม่ตามตัวอักษรหลังจากคำจำกัดความของฉลากและสาขา
Digital Trauma

1
บรรทัดใหม่เหล่านั้นเป็นตัวคั่นพารามิเตอร์ - คุณต้องการให้คั่นคำสั่งใด ๆ ที่อาจยอมรับพารามิเตอร์โดยพลการ - อย่างน้อยนั่นคือสิ่งที่ spec ระบุ ข้อมูลจำเพาะยังบอกว่าsedสคริปต์จะต้องเป็นไฟล์ข้อความยกเว้นว่ามันไม่จำเป็นต้องจบลงด้วยการขึ้นบรรทัดใหม่ ดังนั้นคุณสามารถแบ่งพวกมันออกเป็นส่วน ๆ ได้เช่นกัน - sed -e :\ label -e :\ label2และอื่น ๆ ตั้งแต่ที่คุณกำลังทำ1hอยู่แล้วคุณก็สามารถเปลี่ยนไปใช้ตรรกะบางขึ้นอยู่กับการx;Hที่จะได้รับการขึ้นบรรทัดใหม่ของคุณ - และคุณสามารถตัดขึ้นบรรทัดใหม่ชั้นนำจากพื้นที่รูปแบบในตอนท้ายของวงจรโดยไม่ต้องดึงขึ้นบรรทัดใหม่ใน W A D/
mikeserv

@mikeserv ดี ใช่ฉันแทรกขึ้นบรรทัดใหม่ที่ฉันต้องการโดยทำGครั้งแรกและเปลี่ยนการs///แสดงออก การแยกโดยใช้-eช่วยให้ทุกอย่างขึ้นไปบนหนึ่งบรรทัด (ยาว) โดยไม่ขึ้นบรรทัดใหม่ตามตัวอักษร
Digital Trauma

ยกเว้น\nสำหรับsedspec ของ LHS ก็เช่นกันและฉันคิดว่านั่นเป็นคำสั่ง verbatim ยกเว้นว่านิพจน์วงเล็บเหลี่ยม POSIX นั้นจะมีสเป็คในลักษณะที่อักขระทุกตัวสูญเสียความหมายพิเศษของพวกเขา - (อย่างชัดเจนรวมถึง\\) - ภายในหนึ่งยกเว้นวงเล็บเครื่องหมายขีดคั่นเป็นตัวคั่นช่วงและจุดเท่ากับเครื่องหมายคาเร็ตเครื่องหมายโคลอนสำหรับการเปรียบเทียบความเท่าเทียมการคัดค้านและคลาส
mikeserv

2

อีกโซลูชันที่ perl: จัดเก็บบรรทัดใน hash-of-arrays คีย์ hash เป็นความยาวบรรทัด จากนั้นพิมพ์บรรทัดด้วยคีย์ต่ำสุด

perl -MList::Util=min -ne '
    push @{$lines{ length() }}, $_;
} END {
    print @{$lines{ min keys %lines }};
' sample 
4for

คุณสามารถใช้push @{$lines{+length}};และprint @{$lines{+min keys %lines}};สำหรับการพิมพ์น้อยลง :)
cuonglm

ถ้าฉันเล่นกอล์ฟฉันจะไม่ใช้ชื่อตัวแปร "lines":perl -MList::Util=min -nE'push @{$l{+length}},$_}END{say@{$l{min keys%l}}' sample
glenn jackman

+1 สำหรับเวอร์ชันที่ไม่ใช่กอล์ฟ (ซึ่งใช้งานได้!) แต่สำหรับพิมพ์ตัวแปรทั้งหมดเท่านั้น - perlรับเล็กน้อย gnarly สำหรับพวกเราที่ไม่ได้ขึ้นอยู่กับperlธรรมชาติที่ซ่อนเร้นของpar.with BTW กอล์ฟsayพิมพ์บรรทัดว่างปลอมที่ส่วนท้ายของเอาต์พุต
Peter.O

2

วิธีรับบรรทัดแรกที่สั้นที่สุด:

f=file; sed -n "/^$(sed 's/./1/g' $f | sort -ns | sed 's/././g;q')$/{p;q}" $f

เพื่อให้ได้ผ้าสำลีที่สั้นที่สุดเพียงเปลี่ยน{p;q}เป็นp


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

มันทำงานอย่างไร:
เรียงตามความยาวแปรผันของคีย์เดียวกัน - key 1ซึ่งครอบคลุมทั้งบรรทัด
ตัวแปรแต่ละตัวที่ต่อเนื่องจะเพิ่มความยาวของคีย์ด้วยอักขระหนึ่งตัวจนถึงความยาวของบรรทัดที่ยาวที่สุดของไฟล์ (พิจารณาโดยwc -L)

วิธีรับบรรทัดที่สั้นที่สุดบรรทัดแรก (เรียงลำดับ):

f=file; sort -t'\0' $(seq -f "-k1.%0.0f" $(<"$f" wc -L) -1 1) "$f" | head -n1

ซึ่งเหมือนกับ:

f=file.in; 
l=$(<"$f" wc -L)
k=$(seq -f "-k1.%0.0f" $l -1 1) 
sort -st'\0' $k "$f" | head -n1

2

สมมติว่าบรรทัดว่างไม่ถือว่าเป็นบรรทัดที่สั้นที่สุดและอาจมีบรรทัดว่างนั้น AWK แท้ต่อไปนี้จะทำงาน:

awk '
    {
        len   = length;
        a[$0] = len
    }
    !len { next }
    !min { min = len }
    len < min { min = len }
    END {
        for (i in a)
            if (min == a[i])
                print i
    }
' infile.txt


1

ด้วย GNU awk

gawk '
    {
         a[length]=$0
    };
    END
    {
        PROCINFO["sorted_in"]="@ind_num_asc";
        for (i in a)
        {
            print a[i]; 
            exit
        }
    }
    ' file
  • อ่านแต่ละบรรทัดในอาร์เรย์ที่ทำดัชนีโดยความยาวบรรทัด

  • ตั้งค่าPROCINFO["sorted_in"]เป็น@ind_num_ascบังคับให้สแกนอาเรย์จะถูกเรียงลำดับโดยดัชนีอาร์เรย์เรียงลำดับตัวเลข

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

นี่เป็นข้อเสียของการเป็นอยู่พักหนึ่งnlognในขณะที่อีกวิธีหนึ่งอยู่nในเวลา


1

วิธีการเครื่องมือเชลล์ระดับกลางโดยไม่มีsedหรือawk:

f=inputfile
head -n $(xargs -d '\n' -L 1 -I % sh -c 'exec echo "%" | wc -c' < $f | 
          cat -n | sort -n -k 2 | head -1 | cut -f 1)  $f | tail -1

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