Cách tìm dòng có ít ký tự


22

Tôi đang viết một kịch bản shell, sử dụng bất kỳ lệnh UNIX chung nào. Tôi phải truy xuất dòng có ít ký tự nhất (bao gồm khoảng trắng). Có thể có tới 20 dòng.

Tôi biết tôi có thể sử dụng head -$L | tail -1 | wc -mđể tìm số ký tự của dòng L. Vấn đề là, phương pháp duy nhất tôi có thể nghĩ ra, sử dụng nó, sẽ là viết thủ công một câu lệnh if, so sánh các giá trị.

Dữ liệu ví dụ:

seven/7
4for
8 eight?
five!

Sẽ trở lại 4forvì dòng đó có ít ký tự nhất.

Trong trường hợp của tôi, nếu nhiều dòng có độ dài ngắn nhất, một dòng duy nhất sẽ được trả về. Không quan trọng cái nào được chọn, miễn là nó có độ dài tối thiểu. Nhưng tôi không thấy tác hại trong việc hiển thị cả hai cách cho những người dùng khác với các tình huống khác.


5
Nếu có nhiều dòng có độ dài bằng 4 thì sao? Có nên in chúng quá không?
hỗn loạn

Trong trường hợp của tôi, nếu nhiều dòng có độ dài ngắn nhất, một dòng sẽ được trả về. Không quan trọng cái nào được chọn, miễn là nó có độ dài tối thiểu. Nhưng tôi không thấy tác hại trong việc hiển thị cả hai cách cho những người dùng khác với các tình huống khác.
Matthew D. Scholefield

Câu trả lời:


13

Một cách Perl. Lưu ý rằng nếu có nhiều dòng có cùng độ dài, ngắn nhất, phương pháp này sẽ chỉ in một trong số chúng:

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

Giải trình

  • perl -lne: -ncó nghĩa là "đọc dòng tệp đầu vào theo từng dòng", -lkhiến các dòng mới bị xóa khỏi mỗi dòng đầu vào và một dòng mới được thêm vào mỗi printcuộc gọi; và -elà tập lệnh sẽ được áp dụng cho mỗi dòng.
  • $m//=$_: được đặt thành $mdòng hiện tại ( $_) trừ khi $mđược xác định. Các //=nhà điều hành có sẵn từ Perl 5.10.0.
  • $m=$_ if length()<length($m): nếu độ dài của giá trị hiện tại $mlớn hơn độ dài của dòng hiện tại, hãy lưu dòng hiện tại ( $_) dưới dạng $m.
  • END{print $m if $.}: một khi tất cả các dòng đã được xử lý, in giá trị hiện tại của $m, dòng ngắn nhất. Việc if $.đảm bảo rằng điều này chỉ xảy ra khi số dòng ( $.) được xác định, tránh in một dòng trống cho đầu vào trống.

Ngoài ra, vì tệp của bạn đủ nhỏ để vừa trong bộ nhớ, bạn có thể làm:

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

Giải trình

  • @K=sort{length($a) <=> length($b)}<>: <>đây là một mảng có các phần tử là các dòng của tệp. Các sortsẽ sắp xếp chúng theo chiều dài và các đường sắp xếp được lưu dưới dạng mảng @K.
  • print "$K[0]": in phần tử đầu tiên của mảng @K: dòng ngắn nhất.

Nếu bạn muốn in tất cả các dòng ngắn nhất, bạn có thể sử dụng

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

1
Thêm -Cđể đo độ dài theo số lượng ký tự thay vì số byte. Trong ngôn ngữ UTF-8, $$có ít byte hơn (2 so với 3), nhưng nhiều ký tự hơn (2 so với 1).
Stéphane Chazelas

17

Với sqlite3:

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

Đó là thứ tôi thích nhất ở đây, chưa bao giờ nghĩ đến SQL ...
hỗn loạn

2
Đây là mã trạng thái golf thông minh
Shadowtalker

2
Điều này sẽ đọc toàn bộ tập tin vào bộ nhớ và / hoặc tạo một bản sao trên đĩa thứ hai? Nếu vậy, nó thông minh nhưng không hiệu quả.
John Kugelman hỗ trợ Monica

1
@JohnKugelman Điều này có thể sẽ đưa toàn bộ 4 dòng vào cơ sở dữ liệu chỉ bộ nhớ tạm thời (đó là những gì stracechỉ ra). Nếu bạn cần làm việc với các tệp thực sự lớn (và hệ thống của bạn không bị tráo đổi), bạn có thể buộc nó bằng cách chỉ thêm một tên tệp như sqlite3 $(mktemp)và tất cả dữ liệu sẽ được ghi vào đĩa.
FloHimelf

Tôi nhận được các lỗi sau: "" "xaa: 8146:" ký tự "không được giải mã" "" và "" xaa: 8825: dự kiến ​​1 cột nhưng tìm thấy 2 - phần bổ sung bị bỏ qua "" ". Tệp bao gồm các tài liệu json 1 trên mỗi dòng .
Ahmedov

17

Đây là một biến thể của một awkgiải pháp để in dòng tối thiểu đầu tiên được tìm thấy:

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

có thể đơn giản được mở rộng bởi một điều kiện để in tất cả các dòng tối thiểu:

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

12

Python xuất hiện khá súc tích và đoạn mã Does What It Says On The Tin:

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

Dấu phẩy cuối cùng là tối nghĩa, tôi thừa nhận. Nó ngăn chặn câu lệnh in thêm một ngắt dòng bổ sung. Ngoài ra, bạn có thể viết điều này trong Python 3 hỗ trợ 0 dòng như:

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


Tin nói gì?
mikeerv

@mikeerve: nó nói, "in tối thiểu sys.stdin, sử dụng len làm chìa khóa" ;-)
Steve Jessop

1
àh Không có gì về kích thước nhị phân, creep phụ thuộc hoặc thời gian thực hiện, sau đó?
mikeerv

2
@mikeerv: không, bản in nhỏ không có trên hộp thiếc. Đó là trên một tờ rơi tư vấn trong một tủ hồ sơ bị khóa, trong một hầm, đằng sau cánh cửa được đánh dấu là "coi chừng con báo".
Steve Jessop

Gotcha - hiển thị
mikeerv

10

Tôi luôn thích các giải pháp với kịch bản shell thuần (không có 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"

Lưu ý :

Có một vấn đề với byte NUL trong đầu vào. Vì vậy, printf "ab\0\0\ncd\n" | bash this_scriptin abthay vì cd.


Đây thực sự là tinh khiết nhất. Mặc dù, sự vụng về của các bài kiểm tra bashsẽ thuyết phục tôi đưa kết quả trung gian vào sortthay thế.
orion

2
Bạn đã thử băng ghế dự bị của bạn không thực hiện! Giải pháp so với những người khác làm gì? Dưới đây là so sánh về sự khác biệt hiệu suất giữa exec! không có người thực hiện! giải pháp cho một vấn đề tương tự. thực thi một quy trình riêng biệt rất hiếm khi thuận lợi khi nó thu thập var=$(get data)dữ liệu - ở dạng như vì nó hạn chế luồng dữ liệu vào một ngữ cảnh duy nhất - nhưng khi bạn di chuyển dữ liệu qua một đường ống - trong một luồng - mỗi thực thi được áp dụng thường hữu ích - bởi vì nó cho phép chuyên biệt chỉ áp dụng các chương trình mô-đun khi cần thiết.
mikeerv

1
@DigitalTrauma - một chuỗi các chữ số liền kề được mở rộng không được miễn trừ ít nhiều các điều kiện khiến trích dẫn shell cần thiết hơn bất kỳ chuỗi mở rộng nào khác. $IFSkhông phân biệt chữ số - ngay cả khi không có $IFSgiá trị mặc định , mặc dù nhiều shell sẽ chấp nhận cấu hình môi trường đặt trước cho $IFS- và do đó, đó không phải là mặc định đặc biệt đáng tin cậy.
mikeerv


1
Cảm ơn tất cả các bạn đã cho ý kiến ​​và upvote (một số đại diện nên truy cập @cuonglm để sửa câu trả lời của tôi). Nói chung, tôi không khuyên người khác thực hành kịch bản shell thuần nhưng kỹ năng đó có thể được tìm thấy rất hữu ích trong một số điều kiện khắc nghiệt khi không có gì ngoài liên kết tĩnh /bin/shcó sẵn. Điều đó đã xảy ra với tôi nhiều lần với các máy chủ SunOS4 /usrbị mất hoặc .sobị hỏng, và bây giờ trong thời đại Linux hiện đại, tôi vẫn thỉnh thoảng gặp phải tình huống tương tự với các hệ thống nhúng hoặc khởi động hệ thống bị lỗi khởi động. BusyBox là một trong những điều tuyệt vời mà chúng tôi có được gần đây.
yaegashi

9

Đây là một zshgiải pháp thuần túy (nó in tất cả các dòng có độ dài tối thiểu, từ file):

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

Ví dụ đầu vào:

seven/7
4for
8 eight?
five!
four

Đầu ra là:

4for
four

Tôi nghĩ rằng nó cần một lời giải thích ngắn :-)


Đầu tiên, chúng tôi đặt dấu tách trường nội bộ thành dòng mới:

IFS=$'\n';

Cho đến nay là tốt, bây giờ là phần khó khăn. printsử dụng -lcờ để in kết quả được phân tách bằng dòng mới thay vì dấu cách.

Bây giờ, chúng ta bắt đầu ở bên trong:

$(<file)

Các tập tin được đọc từng dòng và được coi là mảng. Sau đó:

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

Các olá cờ nói rằng kết quả nên được sắp xếp theo thứ tự tăng dần, các @phương tiện để xử lý kết quả như mảng quá. Phần đằng sau ( //?/?) là phần thay thế thay thế tất cả các ký tự bằng a ?. Hiện nay:

${~...[1]}

Chúng tôi lấy phần tử mảng đầu tiên [1], là phần tử ngắn nhất, trong trường hợp của bạn là bây giờ ????.

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

Việc so khớp được thực hiện trên từng thành phần mảng riêng biệt và các thành phần mảng chưa từng có được loại bỏ ( M). Mỗi yếu tố phù hợp???? (4 ký tự) vẫn nằm trong mảng. Vì vậy, các yếu tố còn lại là những yếu tố có 4 ký tự (những ký tự ngắn nhất).

Chỉnh sửa: Nếu bạn chỉ cần một trong những dòng ngắn nhất, phiên bản sửa đổi này sẽ in dòng đầu tiên:

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

... Và người chiến thắng là ... dòng 2, có vẻ như vậy.

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

Nhưng vấn đề với điều đó là mỗi dòng phải dài hơn gấp đôi để nó hoạt động - vì vậy LINE_MAX bị giảm một nửa hiệu quả. Nguyên nhân là nó đang sử dụng - cái gì, cơ sở 1? - để thể hiện độ dài của dòng. Một cách tiếp cận tương tự - và có lẽ gọn gàng hơn - có thể là nén thông tin đó trong luồng. Ý tưởng đầu tiên dọc theo những dòng xảy ra với tôi là tôi phải thực hiện unexpandnó:

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

Đó là bản in ...

2
4for

Một số khác, chỉ 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

Cú pháp tuân thủ các tiêu chuẩn - nhưng điều đó không đảm bảo rằng bất kỳ cái cũ nào sedsẽ xử lý \(reference-group\)\{counts\}chính xác - nhiều người không làm như vậy.

Về cơ bản, nó áp dụng cùng một biểu thức chính quy cho đầu vào lặp đi lặp lại - điều này có thể rất có lợi khi đến lúc biên dịch chúng. Mô hình đó là:

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

Mà phù hợp với các chuỗi khác nhau theo những cách khác nhau. Ví dụ:

string1\nstring2\nstring3

... được khớp với strong \1''chuỗi null trong \2.

1\nstring2\nstring3

... được kết hợp với 1trong \1\nstring2\nstring3trong\2

\nstring2\nstring3

... được khớp với \ntrong \1''chuỗi null trong \2. Điều này sẽ có vấn đề nếu có bất kỳ cơ hội nào của một \newline xảy ra ở phần đầu của không gian mẫu - nhưng các lệnh /^\n/D//!gđược sử dụng để ngăn chặn điều này. Tôi đã sử dụng [^\n]nhưng các nhu cầu khác đối với kịch bản nhỏ này khiến tính di động trở thành mối quan tâm và tôi không hài lòng với nhiều cách nó thường bị hiểu sai. Thêm vào đó, .là nhanh hơn.

\nstring2
string1

... khớp \nsmột lần nữa \1và cả hai đều nhận được ''chuỗi null \2. Các dòng trống không khớp chút nào.

Khi mô hình được áp dụng theo gchiều dọc , hai xu hướng - cả độ lệch chuẩn bên trái nhất và độ \nlệch ewline bên phải ít hơn - được cân bằng đối nghịch để tạo ra sự bỏ qua. Một vài ví dụ:

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

... nếu tất cả được áp dụng (không liên tiếp) cho chuỗi sau ...

string1\nstring2

... sẽ biến nó thành ...

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

Về cơ bản, tôi sử dụng biểu thức chính quy để luôn xử lý dòng đầu tiên trong bất kỳ không gian mẫu nào mà tôi áp dụng nó. Điều đó cho phép tôi xử lý hai phiên bản khác nhau của cả hai dòng trùng khớp ngắn nhất được giữ lại và dòng gần đây nhất mà không cần dùng đến các vòng kiểm tra - mỗi lần thay thế đều xử lý toàn bộ không gian mẫu.

Các phiên bản khác nhau là cần thiết để so sánh chuỗi / chuỗi theo nghĩa đen - vì vậy phải có một phiên bản của mỗi dòng trong đó tất cả các ký tự được đảm bảo bằng nhau. Nhưng tất nhiên, nếu cái này hay cái kia thực sự là dòng ngắn nhất xuất hiện sớm nhất trong đầu vào, thì dòng được in thành đầu ra có lẽ phải là phiên bản gốc của dòng - không phải là dòng tôi đã khử trùng / đồng nhất để so sánh. Và vì vậy tôi cần hai phiên bản của mỗi.

Thật không may là một điều cần thiết khác là rất nhiều bộ đệm chuyển đổi để xử lý giống nhau - nhưng ít nhất không có bộ đệm nào vượt quá bốn dòng cần thiết để duy trì hiện tại - và vì vậy có lẽ nó không khủng khiếp.

Dù sao, đối với mỗi chu kỳ, điều đầu tiên xảy ra là một phép biến đổi trên dòng ghi nhớ - bởi vì bản sao duy nhất thực sự được lưu là bản gốc theo nghĩa đen - thành ...

^               \nremembered line$

... và sau đó, ndòng đầu vào ext ghi đè lên bất kỳ bộ đệm cũ nào. Nếu nó không chứa ít nhất một ký tự thì nó bị bỏ qua một cách hiệu quả. Nó sẽ dễ dàng hơn nhiều chỉ đểq ở dòng trống xuất hiện đầu tiên, nhưng, tốt, dữ liệu thử nghiệm của tôi có rất nhiều dữ liệu và tôi muốn xử lý nhiều đoạn văn.

Và vì vậy, nếu nó có chứa một ký tự, phiên bản theo nghĩa đen của nó được gắn vào dòng được nhớ và phiên bản so sánh cách nhau của nó được đặt ở đầu của không gian mẫu, như thế này:

^   \n               \nremembered line\nnew$

Thay thế cuối cùng được áp dụng cho không gian mẫu đó:

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

Vì vậy, nếu dòng mới có thể vừa trong không gian cần thiết để chứa dòng đã nhớ với ít nhất một char để dự phòng thì hai dòng đầu tiên được thay thế, chỉ khác dòng đầu tiên.

Bất kể kết quả như thế nào, dòng đầu tiên trong không gian mẫu luôn luôn bị Dxóa ở cuối chu kỳ trước khi bắt đầu lại. Điều này có nghĩa là nếu dòng mới ngắn hơn chuỗi cuối ...

new

... được gửi trở lại thay thế đầu tiên trong chu kỳ sẽ luôn chỉ thoát khỏi char dòng mới đầu tiên trên - và vì vậy nó vẫn còn nguyên. Nhưng nếu không thì chuỗi ...

remembered line\nnew

... thay vào đó sẽ bắt đầu chu kỳ tiếp theo và thay thế đầu tiên sẽ loại bỏ chuỗi ...

\nnew

...mỗi lần.

Trên dòng cuối cùng, dòng ghi nhớ được in ra theo tiêu chuẩn, và vì vậy đối với dữ liệu mẫu được cung cấp, nó sẽ in:

4for

Nhưng, nghiêm túc, sử dụng tr.



Bạn thậm chí có cần phải chèn số dòng? Tôi đọc OP là chỉ cần dòng ngắn nhất, và không nhất thiết phải là số dòng của dòng đó. Tôi đoán không có hại trong việc hiển thị nó cho đầy đủ.
Chấn thương kỹ thuật số

@DigitalTrauma - không, có lẽ là không. Nhưng nó hầu như không hữu ích nếu không có chúng - và chúng đến rất rẻ. Khi làm việc một luồng, tôi luôn thích bao gồm một phương tiện tái tạo đầu vào ban đầu giống hệt trong đầu ra - các số dòng làm cho điều đó có thể ở đây. Ví dụ: để biến kết quả của đường ống đầu tiên xung quanh : REINPUT | sort -t: -nk1,1 | cut -d: -f3-. Và thứ hai là một vấn đề đơn giản bao gồm một sed --expressionkịch bản khác ở phần đuôi.
mikeerv

@DigitalTrauma - oh, và trong ví dụ đầu tiên số dòng làm ảnh hưởng đến sorthành vi của một tie-breaker khi dòng cùng độ dài xảy ra ở đầu vào - vì vậy dòng xảy ra sớm nhất luôn trôi vào phần trên trong trường hợp đó.
mikeerv

7

Thử:

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

Ý tưởng là sử dụng awkđể in độ dài của mỗi dòng đầu tiên. Điều này sẽ xuất hiện dưới dạng:

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

Sau đó, sử dụng số đếm ký tự để sắp xếp các dòng theo sort, cutđể loại bỏ số đếm và headgiữ dòng đầu tiên (dòng có ít ký tự nhất). Tất nhiên bạn có thể sử dụng tailđể có được dòng có nhiều ký tự nhất trong trường hợp này.

(Điều này đã được thông qua từ câu trả lời này )


+1 cho logic nhưng nó sẽ không hoạt động trong tất cả các trường hợp. Nếu hai dòng có cùng số lượng ký tự và mức tối thiểu. Nó sẽ chỉ cung cấp cho bạn dòng đầu tiên gặp phải vìhead -1
Vì vậy,

Để có được dòng dài nhất, việc đảo ngược sắp xếp sẽ hiệu quả hơn một chút so với sử dụng tail( headcó thể thoát ngay khi công việc của nó được thực hiện mà không cần đọc phần còn lại của đầu vào).
Toby Speight

@Thushi Sử dụng một chút regex, sau khi in số dòng, mọi thứ trừ các dòng có cùng số với dòng 1, có thể bị xóa, do đó xuất ra tất cả các dòng ngắn nhất.
Matthew D. Scholefield

5

Với POSIX awk:

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

Sẽ không hoạt động nếu có nhiều hơn một dòng có cùng số lượng ký tự và cũng là mức tối thiểu.
Do đó,

@Thushi: Nó sẽ báo cáo dòng tối thiểu đầu tiên.
cuonglm

Vâng. Nhưng điều đó không đúng đầu ra phải không? Ngay cả các dòng khác cũng có số lượng ký tự tối thiểu.
Do đó,

1
@Thushi: Điều đó không đề cập đến trong yêu cầu của OP, chờ cập nhật từ OP.
cuonglm

3
Tôi không nghĩ Llà chữ cái tốt nhất để chọn đặt tên cho biến: D Một cái gì đó giống như minsẽ làm cho mọi thứ rõ ràng hơn
fedorqui

3

Mượn một số ý tưởng của @ mikeerv:

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

Việc đầu tiên sedlàm như sau:

  • h lưu dòng gốc vào bộ đệm giữ
  • Thay thế mọi ký tự trong dòng bằng :- điều này là để loại bỏ mọi nguy hiểm của việc tiêm mã
  • Thay thế toàn bộ dòng bằng expr length "whole line"- đây là biểu thức shell có thể được đánh giá
  • Lệnh es là một phần mở rộng GNU sed để đánh giá không gian mẫu và đưa kết quả trở lại vào không gian mẫu.
  • G nối thêm một dòng mới và nội dung của không gian giữ (dòng ban đầu) vào không gian mẫu
  • cuối cùng sthay thế dòng mới bằng một tab

Số lượng ký tự bây giờ là một số ở đầu mỗi dòng, do đó sort -nsắp xếp theo độ dài dòng.

Cuối cùng sedsau đó loại bỏ tất cả trừ dòng đầu tiên (ngắn nhất) và độ dài dòng và in kết quả.


1
@mikeerv Có tôi nghĩ exprlà đẹp hơn ở đây. Có, esẽ sinh ra một vỏ cho mỗi dòng. Tôi đã chỉnh sửa biểu thức sed để nó thay thế từng char trong chuỗi bằng một :eval mà tôi nghĩ nên loại bỏ bất kỳ khả năng tiêm mã nào.
Chấn thương kỹ thuật số

Tôi thường sẽ chọn xargs exprcá nhân - nhưng, ngoài việc tránh một lớp vỏ trung gian, đó có lẽ là một điều phong cách hơn. Dù sao thì tôi cũng thích nó.
mikeerv

3

Nó xảy ra với tôi rằng toàn bộ điều có thể trong một sedbiểu thức. Nó không đẹp:

$ 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
$ 

Phá vỡ điều này:

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 trong OS X tinh tế hơn một chút với các dòng mới. Phiên bản này hoạt động cho cả phiên bản BSD và GNU của sed:

$ 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
$

Lưu ý đây là một câu trả lời "bởi vì nó có thể" hơn là một nỗ lực nghiêm túc để đưa ra một câu trả lời thực hành tốt nhất. Tôi đoán nó có nghĩa là tôi đã chơi quá nhiều code-colf


@mikeerv Từ man sedtrên OS X: "Chuỗi thoát \ n khớp với một ký tự dòng mới được nhúng trong không gian mẫu" . Vì vậy, tôi nghĩ GNU sed cho phép \ntrong regex và trong thay thế, trong khi BSD chỉ cho phép \ntrong regex chứ không cho phép thay thế.
Chấn thương kỹ thuật số

Mượn \ntừ không gian mẫu là một ý tưởng hay và sẽ hoạt động trong s///biểu thức thứ hai , nhưng s/.*/&\n&/biểu thức đang chèn một \nvào không gian mẫu mà trước đó không có. Ngoài ra sed BSD dường như yêu cầu các dòng mới theo nghĩa đen sau khi định nghĩa nhãn và các nhánh.
Chấn thương kỹ thuật số

1
Những dòng mới này là các dấu phân cách tham số - bạn cần chúng để phân định bất kỳ lệnh nào có thể chấp nhận một tham số tùy ý - ít nhất, đó là những gì thông số kỹ thuật nói. Thông số kỹ thuật cũng nói rằng một sedtập lệnh sẽ là một tệp văn bản ngoại trừ việc nó không cần kết thúc trong một dòng mới . Vì vậy, bạn thường có thể phân định chúng như các đối số riêng biệt - sed -e :\ label -e :\ label2v.v. Vì 1hdù sao bạn cũng đang làm , bạn chỉ có thể chuyển sang một số logic dựa trên x;Hđể có được dòng mới của mình - và bạn có thể cắt một dòng mới hàng đầu từ không gian mẫu vào cuối chu kỳ mà không cần kéo theo dòng mới w / D.
mikeerv

@mikeerv Đẹp. Có, tôi đã chèn dòng mới tôi cần bằng cách thực hiện Gđầu tiên và thay đổi s///biểu thức. Việc tách nó ra bằng cách sử dụng -echo phép tất cả đi trên một dòng (dài) mà không có dòng mới.
Chấn thương kỹ thuật số

Lối \nthoát cũng được quy định cho sedLHS, và tôi nghĩ đó là nguyên văn của tuyên bố của thông số kỹ thuật, ngoại trừ các biểu thức khung POSIX cũng được mô tả theo cách mà tất cả các ký tự đều mất ý nghĩa đặc biệt - (bao gồm rõ ràng \\) - trong một ngoại trừ dấu ngoặc, dấu gạch ngang như một dấu phân cách phạm vi và dấu chấm, bằng, dấu mũ, dấu hai chấm để đối chiếu, tương đương, phủ định và các lớp.
mikeerv

2

Một giải pháp perl khác: lưu trữ các dòng trong một mảng băm, khóa băm là độ dài dòng. Sau đó, in ra các dòng với khóa tối thiểu.

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

Bạn có thể sử dụng push @{$lines{+length}};print @{$lines{+min keys %lines}};để gõ ít hơn :)
cuonglm

Nếu tôi chơi gôn, tôi cũng sẽ không sử dụng tên biến "đường":perl -MList::Util=min -nE'push @{$l{+length}},$_}END{say@{$l{min keys%l}}' sample
glenn jackman

+1 cho phiên bản không chơi gôn (hoạt động!), Mặc dù chỉ in tất cả các biến thể. - perlcó một chút sởn gai ốc cho những người trong chúng ta, những người không theo kịp perlbản chất khó hiểu của par.with . BTW. sân gôn sayin một dòng trống giả ở cuối. đầu ra.
Peter.O

2

Để chỉ nhận được dòng ngắn nhất đầu tiên:

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

Để có được tất cả các gợi ý ngắn nhất, chỉ cần thay đổi {p;q}thànhp


Một phương pháp khác (hơi khác thường) là sortthực hiện sắp xếp thực tế theo chiều dài . Nó tương đối chậm ngay cả với các dòng ngắn và trở nên chậm hơn đáng kể khi chiều dài dòng tăng.
Tuy nhiên, tôi thấy ý tưởng sắp xếp bằng các phím chồng chéo khá thú vị. Tôi đang đăng nó trong trường hợp những người khác cũng có thể thấy nó thú vị / nhiều thông tin.

Cách thức hoạt động:
Sắp xếp theo các biến thể độ dài của cùng một khóa -key 1 kéo dài toàn bộ dòng
Mỗi biến thể khóa liên tiếp tăng chiều dài khóa theo một ký tự, cho đến độ dài của dòng dài nhất của tệp (được xác định bởi wc -L)

Để chỉ nhận dòng ngắn nhất (được sắp xếp) đầu tiên:

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

tương tự như:

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

2

Giả sử các dòng trống không được coi là dòng ngắn nhất và các dòng trống có thể tồn tại, AWK thuần sau đây sẽ hoạt động:

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

2

Còn việc sử dụng sort thì sao?

awk '{ print length($0) "\t" $0 }' input.txt | sort -n | head -n 1 | cut -f2-

1

Với GNU awk

gawk '
    {
         a[length]=$0
    };
    END
    {
        PROCINFO["sorted_in"]="@ind_num_asc";
        for (i in a)
        {
            print a[i]; 
            exit
        }
    }
    ' file
  • Đọc từng dòng thành một mảng được lập chỉ mục theo chiều dài dòng.

  • Đặt PROCINFO["sorted_in"]thành @ind_num_ascbắt buộc quét mảng được sắp xếp theo chỉ mục mảng, được sắp xếp theo số

  • Cài đặt PROCINFOtheo cách trên buộc các dòng có độ dài nhỏ nhất được chọn trước tiên trong đường ngang của mảng. Vì vậy, in phần tử đầu tiên từ mảng và thoát

Đây có những bất lợi trở thành một nlogntrong khi một số phương pháp tiếp cận khác là ntrong thời gian


1

Phương pháp công cụ shell trung cấp, không có sedhoặc 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

Thật tuyệt khi không cần một $fbiến số; Tôi có một khái niệm có thể sử dụng teebằng cách nào đó ...
agc
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.