Bagaimana cara mencetak baris terpanjang dalam suatu file?

35

Saya mencari metode paling sederhana untuk mencetak baris terpanjang dalam file. Saya melakukan beberapa pencarian di Google dan secara mengejutkan tidak menemukan jawaban. Saya sering mencetak panjang garis terpanjang dalam sebuah file, tetapi saya tidak tahu bagaimana cara mencetak garis terpanjang. Adakah yang bisa memberikan solusi untuk mencetak baris terpanjang dalam file? Terima kasih sebelumnya.

dr.bunsen
sumber
1
Bagaimana bila ada beberapa garis "terpanjang"? Karena Anda ingin lebih dari panjang maksimum sederhana, apakah Anda ingin melihat semua contoh garis yang sama panjangnya?
Peter.O

Jawaban:

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

UPD : merangkum semua saran dalam komentar

awk 'length > max_length { max_length = length; longest_line = $0 } END { print longest_line }' ./text 
ДМИТРИЙ МАЛИКОВ
sumber
3
Yaitu, keduanya memanggil perintah lain ( cat), dan menggunakan pipa adalah operasi yang mahal, belum lagi bahwa awk lebih efisien untuk hanya membaca file. Implikasi kinerja jelas terlihat jika hal ini sering dilakukan, dan meskipun demikian, Anda sepenuhnya menyalahgunakan cat.
Chris Down
7
@laebshade Benar-benar ada alasan - karena itu Anda tidak perlu mengingat perintah mana yang mengambil nama file dan mana yang tidak, atau peduli tentang perintah mana yang akan dieksekusi pertama kali dalam pipa. Jika Anda akan menulis skrip yang sering dijalankan, jangan khawatir tentang hal seperti ini. Jika Anda menulis satu hal untuk menemukan baris terpanjang dalam file, proses ekstra dan jumlah waktu yang dihabiskan sama sekali tidak relevan. Konyol kalau orang begitu terobsesi di sini, luar biasa kecil
Michael Mrozek
4
@Keith Thompson: cattidak ada gunanya di sini. Mungkin tidak berguna untuk komputer tetapi bagi pembaca manusia itu bisa memberikan nilai. Varian pertama dengan jelas menunjukkan input. Alurnya lebih alami (dari kiri ke kanan). Dalam kasus kedua Anda tidak tahu apa inputnya kecuali Anda menggulir jendela.
jfs
1
@ JSFSebastian Sekalipun Anda menginginkannya di sebelah kiri, Anda tidak perlu cat. < file commandberfungsi dengan baik.
Chris Down
3
@ JFSebastian: Fakta bahwa pengalihan dapat ditulis pada awal perintah agak tidak jelas; < filename commandsetara dengan filename < commanddi setiap shell yang saya coba. Tetapi begitu Anda menyadarinya, Anda dapat memanfaatkannya saat menulis pipa panjang yang dengan jelas menunjukkan arah aliran data (tanpa menggunakan perintah tambahan):< input-file command1 | command2 | command3 > output-file
Keith Thompson
6
cat filename | awk '{ print length }' | sort -n | tail -1
aspinalln
sumber
+1 Ada banyak solusi menarik untuk ini tetapi ini adalah yang paling sederhana. (Akan lebih mudah tanpa kucing dengan membiarkan awk membaca file tetapi mengapa berdalih?)
user1683793
5
sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file

Ini pertama kali membaca file di dalam substitusi perintah dan menampilkan panjang baris terpanjang, (sebelumnya, expandmengkonversi tab menjadi spasi, untuk mengatasi semantik wc -L- setiap tab di baris akan menambah 8 bukannya 1 ke panjang baris). Panjang ini kemudian digunakan dalam sedekspresi yang berarti "temukan garis yang panjangnya karakter ini, cetak, lalu keluar". Jadi ini sebenarnya bisa seoptimal garis terpanjang dekat dengan bagian atas file, heheh (terima kasih untuk komentar yang luar biasa dan konstruktif).

Lain, saya telah berpikir lebih awal daripada yang sed (dalam bash):

#!/bin/bash
while read -r line; do
    (( ${#line} > max )) && max=${#line} && longest="$line"
done
echo "$longest"
ata
sumber
2
Metode ini sangat mahal dan lambat.
Chris Down
2
@ Chris Down: Oh ya itu. Tetapi pertanyaannya adalah tentang metode yang paling baik, bukan yang paling efisien. Walau halus untuk file kecil atau sedang atau tugas yang tidak penting.
Atau
3
PERINGATAN : Opsi wc -L, --max-line-lengthmencetak panjang garis terpanjang, menurut halaman manual, tetapi jika Anda menggali lebih dalam (seperti ketika Anda mendapatkan hasil yang salah / tidak terduga ), Anda menemukan bahwa opsi ini menambah panjang dengan 8 untuk setiap 1 tab karakter \x09 lihat Unix & Linux T / A
Peter.O
PS. Jawaban Anda akan mencetak semua baris "yang sama panjang", yang mungkin merupakan hal yang baik ... Untuk memaksa wc untuk menghitung hanya 1 karakter per tab, ini berfungsi. sed -rn "/.{$(<file expand -t1 |wc -L)}/p" file
Peter.O
1
read lineakan menafsirkan karakter backslash-lolos sebagai char literal, misalnya \Aresloves untuk A, yang tentu saja secara efektif melaporkan lebih pendek dari yang sebenarnya byte-penggunaan ... Untuk mencegah hal ini lolos interpretasi, gunakan: read -r line. . . . Juga, untuk membuat versi sed + wc berhenti setelah "garis terpanjang" pertama, ubah pke {p;q}..sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file
Peter.O
4

Inilah solusi Perl:

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

Atau, jika Anda ingin mencetak semua garis terpanjang

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

Karena tidak ada yang lebih baik untuk dilakukan, saya menjalankan beberapa tolok ukur pada file teks 625M. Anehnya, solusi Perl saya secara konsisten lebih cepat daripada yang lain. Memang, perbedaan dengan awksolusi yang diterima kecil, tetapi ada di sana. Jelas, solusi yang mencetak beberapa baris lebih lambat jadi saya telah mengurutkannya berdasarkan jenis, tercepat hingga paling lambat.

Cetak hanya satu dari garis terpanjang:

$ 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

Cetak semua garis terpanjang:

$ 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
terdon
sumber
3

Grep baris terpanjang pertama

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

Perintah ini sangat sulit dibaca tanpa latihan karena ia mencampur sintaks shell- dan regexp.
Untuk penjelasan, saya akan menggunakan kodesemu yang disederhanakan terlebih dahulu. Garis yang dimulai dengan ##tidak berjalan di shell.
Kode yang disederhanakan ini menggunakan nama file F, dan tidak mengutip dan bagian dari regexps untuk dibaca.

Bagaimana itu bekerja

Perintah memiliki dua bagian, a grep- dan wcdoa:

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

The wcdigunakan dalam ekspansi proses, $( ... )sehingga dijalankan sebelum grep. Ini menghitung panjang garis terpanjang. Sintaks ekspansi shell dicampur dengan sintaksis pola ekspresi reguler dengan cara yang membingungkan, jadi saya akan menguraikan ekspansi proses:

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

Di sini, ekspansi proses diganti dengan nilai yang akan dikembalikan, menciptakan baris grepperintah yang digunakan. Kita sekarang dapat membaca ekspresi reguler dengan lebih mudah: Ini cocok persis dari awal ( ^) hingga akhir ( $) pada baris. Ekspresi di antara mereka cocok dengan karakter apa pun kecuali baris baru, diulang sebanyak 42 kali. Gabungan, yaitu garis yang terdiri dari 42 karakter.


Sekarang, kembali ke perintah real shell: grepOpsi -E( --extended-regexp) memungkinkan untuk tidak luput dari {}keterbacaan. Opsi -m 1( --max-count=1) membuatnya berhenti setelah baris pertama ditemukan. Perintah <in wcmenulis file ke stdin-nya, untuk mencegah wcdari pencetakan nama file bersama dengan panjangnya.

Garis terpanjang mana?

Untuk membuat contoh lebih mudah dibaca dengan nama file muncul dua kali, saya akan menggunakan variabel funtuk nama file; Masing-masing $fdalam contoh dapat diganti dengan nama file.

f="file.txt"

Tampilkan garis terpanjang pertama - garis pertama sepanjang garis terpanjang:

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

Tampilkan semua garis terpanjang - semua garis sepanjang garis terpanjang:

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

Tampilkan garis terpanjang terakhir - baris terakhir sepanjang garis terpanjang:

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

Tampilkan garis terpanjang tunggal - garis terpanjang lebih panjang dari semua garis lain, atau gagal:

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

(Perintah terakhir bahkan lebih tidak efisien daripada yang lain, karena ia mengulangi perintah grep lengkap. Perintah ini harus diurai sehingga output dari wcdan baris yang ditulis oleh grepdisimpan ke variabel.
Perhatikan bahwa semua garis terpanjang mungkin sebenarnya semua baris Untuk menyimpan dalam suatu variabel, hanya dua baris pertama yang harus disimpan.)

Volker Siegel
sumber
Wow jawaban yang bagus, belajar banyak dari itu. terima kasih
sesuatu,
2

Contoh berikut akan menjadi, dan seharusnya, komentar untuk jawaban dmitry.malikov , tetapi karena Penggunaan Ruang Komentar Terlihat yang Tidak Berguna di sana, saya telah memilih untuk menyajikannya di sini, di mana setidaknya akan terlihat. ..

Ini adalah variasi sederhana dari metode single-pass awk dmitry .
Ini mencetak semua garis "sama terpanjang". (Catatan. delete arrayAdalah ekstensi gawk).

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

Dalam bash murni:

#!/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[@]}"
Chris Down
sumber
Seperti adanya, kode dapat mengembalikan hasil yang tidak valid. Pengaturan _max_line[0]=${_line}tidak menghapus sisa dari "garis terpanjang" yang terakumulasi sebelumnya ... ... unset _max_lineakan menghapus seluruh array ...
Peter.O
@fered Terima kasih untuk itu, ditulis dengan cukup cepat. Tetap.
Chris Down
0

Saya telah mengembangkan skrip shell kecil untuk ini. Ini menampilkan panjang, garis # dan garis itu sendiri dengan panjang yang melebihi ukuran tertentu seperti 80 karakter:

#!/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

Surinder432
sumber
1
Ada beberapa peningkatan yang bisa Anda lakukan. Kutip variabel Anda . Ini akan merusak semua nama file yang berisi spasi atau karakter aneh lainnya. Menggunakan $*jarang ide yang baik, Anda ingin"$@" . Di /.*/Anda awktidak melakukan apa-apa karena itu cocok dengan garis kosong juga. Anda dapat menghindari melarikan diri \$0jika Anda mengutip tunggal 'EOF'. Mengapa menggunakan BEGIN{}blok kosong ? Akhirnya, Anda tidak perlu cat, hanyaawk . . . "$file" | . . .
terdon
1
Anda juga dapat melakukan semuanya secara langsung:awk -vmax=15 '{len=length($0); if(len>=max){printf("%s, %d at line # %d %s\n", FILENAME, len, NR, $0);}}' file*
terdon
-3

Anda bisa menggunakan wc:

wc -L fileName
ynot1074
sumber
3
Silakan baca pertanyaan lagi. Output yang dibutuhkan adalah garis terpanjang itu sendiri, bukan panjang garis terpanjang. Juga lihat komentar Peter.O tentang wc -Lkelemahannya.
manatwork