Как найти строку с наименьшим количеством символов

22

Я пишу сценарий оболочки, используя любые общие команды UNIX. Я должен получить строку, которая имеет наименьшее количество символов (включая пробелы). Там может быть до около 20 строк.

Я знаю, что могу использовать, head -$L | tail -1 | wc -mчтобы найти количество символов в строке L. Проблема в том, что единственный способ, который я могу придумать, используя это, состоит в том, чтобы вручную написать беспорядок операторов if, сравнивая значения.

Пример данных:

seven/7
4for
8 eight?
five!

Вернется, 4forтак как эта строка имеет наименьшее количество символов.

В моем случае, если несколько строк имеют самую короткую длину, должна быть возвращена одна. Неважно, какой из них выбран, если он имеет минимальную длину. Но я не вижу вреда в демонстрации обоих способов другим пользователям в других ситуациях.

Мэтью Д. Шолфилд
источник
5
Что делать, если есть несколько строк длиной 4? Должны ли они быть напечатаны тоже?
хаос
В моем случае, если несколько строк имеют самую короткую длину, должна быть возвращена одна. Неважно, какой из них выбран, если он имеет минимальную длину. Но я не вижу вреда в демонстрации обоих способов другим пользователям в других ситуациях.
Мэтью Д. Скоулфилд

Ответы:

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 против 3), но больше символов (2 против 1).
Стефан Шазелас
17

С sqlite3:

sqlite3 <<EOT
CREATE TABLE file(line);
.import "data.txt" file
SELECT line FROM file ORDER BY length(line) LIMIT 1;
EOT
FloHimself
источник
Это мой любимый здесь, никогда не думал о SQL ...
хаос
2
Это код состояния гольфа, умный
shadowtalker
2
Будет ли это читать весь файл в память и / или создавать вторую копию на диске? Если так, это умно, но неэффективно.
Джон Кугельман поддерживает Монику
1
@JohnKugelman Это, вероятно, впитает целые 4 строки в базу данных только с временной памятью (это то, что straceуказывает). Если вам нужно работать с действительно большими файлами (а ваша система не меняет местами), вы можете принудительно установить его, просто добавив имя файла, например, sqlite3 $(mktemp)и все данные будут записаны на диск.
FloHimself
Я получаю следующие ошибки: "" "xaa: 8146: unescaped" символ "" "и" "" xaa: 8825: ожидается 1 столбец, но найдено 2 - дополнительные функции игнорируются "" ". Файл состоит из документов json, по 1 на каждую строку .
Ахмедова
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}'
'
Janis
источник
12

Python выходит довольно лаконичным, а код делает то, что говорит на олове:

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

Признаюсь, последняя запятая неясна. Это препятствует тому, чтобы оператор печати добавил дополнительный перенос строки. Кроме того, вы можете написать это в Python 3, поддерживая 0 строк, например:

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

Стив Джессоп
источник
что говорит олово?
mikeserv
@mikeserve: там написано: «печатает минимум sys.stdin, используя len в качестве ключа» ;-)
Steve Jessop
1
ааа. ничего о размере двоичного файла, ползучести зависимости или времени выполнения, тогда?
mikeserv
2
@mikeserv: нет, мелкий шрифт не на банке. Это на консультативной листовке в запертом шкафу, в подвале, за дверью с надписью «остерегайтесь леопарда».
Стив Джессоп
Попался - так на дисплее.
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печатает abвместо cd.

yaegashi
источник
Это действительно самый чистый. Хотя неуклюжесть тестов bashубедила бы меня sortвместо этого направить промежуточный результат .
Орион
2
Вы пробовали скамейку не exec! решение против других, которые делают? Вот сравнение различий в производительности между exec! и нет exec! решения для аналогичной проблемы. Выделение отдельного процесса очень редко бывает выгодно, когда он спайдерный - в таких формах, как, например, var=$(get data)потому что он ограничивает поток данных одним контекстом - но когда вы перемещаете данные через конвейер - в потоке - каждый прикладной exec обычно полезен - потому что он позволяет применение модульных программ только при необходимости.
mikeserv
1
@DigitalTrauma - расширенная непрерывная цепочка цифр не более или менее освобождается от условий, которые делают кавычки необходимыми, чем любая другая расширенная строка. $IFSне различает цифры - даже если в $IFSзначении по умолчанию их нет , хотя многие оболочки будут принимать предустановленную конфигурацию среды $IFS- и поэтому это не особенно надежное значение по умолчанию.
mikeserv
1
Спасибо всем за комментарии и отзывы (некоторые из них должны перейти на @cuonglm для исправления моего ответа). Как правило, я не рекомендую другим ежедневно практиковать чистые сценарии оболочки, но этот навык может оказаться очень полезным в некоторых экстремальных условиях, когда нет ничего, кроме статической связи /bin/sh. Это случалось со мной несколько раз с хостами SunOS4 с /usrпотерянными или некоторыми .soповреждениями, и теперь, в современном веке Linux, я все еще время от времени сталкиваюсь с подобными ситуациями со встроенными системами или initrd систем с ошибками загрузки. BusyBox - одна из замечательных вещей, которые мы недавно приобрели.
Яэгаши
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\}правильно - многие этого не делают.

Он в основном применяет одно и то же регулярное выражение для ввода многократно - что может быть очень полезно, когда пришло время их компилировать. Этот шаблон:

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

Который по-разному соответствует различным строкам. Например:

string1\nstring2\nstring3

... совпадает с sin \1и ''нулевой строкой in \2.

1\nstring2\nstring3

... сочетается с 1в \1и \nstring2\nstring3в\2

\nstring2\nstring3

... совпадает с \nin \1и ''нулевой строкой in \2. Это было бы проблематично, если бы была какая-либо вероятность появления \newline в начале пространства шаблонов, но для предотвращения этого используются команды /^\n/D, и //!g. Я использовал, [^\n]но другие потребности в этом небольшом скрипте сделали переносимость проблемой, и я не был удовлетворен многими путями, которые он часто неверно истолковывает. Плюс, .быстрее.

\nstring2
string1

... матч \nи sснова в, \1и оба получают'' нулевую строку \2. Пустые строки не совпадают вообще.

Когда шаблон применяется gлобально, два смещения - как крайнее левое стандартное смещение, так и меньшее правое смещение на боковой \nлинии - уравновешиваются, чтобы вызвать пропуск. Несколько примеров:

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

В основном я использую регулярное выражение, чтобы всегда обрабатывать только первую строку в любом шаблонном пространстве, к которому я его применяю. Это позволяет мне манипулировать двумя различными версиями как сохраненной строки с кратчайшим совпадением, так и самой последней строки, не прибегая к циклам тестирования - каждая примененная замена обрабатывает все пространство шаблона одновременно.

Различные версии необходимы для сравнения строк и строк - поэтому должна быть версия каждой строки, в которой все символы гарантированно равны. Но, конечно, если одна или другая из них на самом деле окажутся самой ранней из самых коротких строк на входе, то строка, напечатанная для вывода, вероятно, должна быть исходной версией строки, а не той, которую я санировал / гомогенизировал для сравнения. И поэтому мне нужны две версии каждого.

К сожалению, еще одной необходимостью является многократное переключение буфера для обработки одного и того же, но, по крайней мере, ни один из буферов никогда не превышает больше, чем четыре строки, необходимые для поддержания актуальности, - и поэтому, возможно, это не страшно.

Во всяком случае, для каждого цикла первое, что происходит, - это преобразование запомненной строки, потому что единственная фактически сохраненная копия - это буквальный оригинал - в ...

^               \nremembered line$

... и после этого nстрока ввода ext перезаписывает любой старый буфер. Если он не содержит хотя бы одного символа, он фактически игнорируется. Было бы намного проще простоq использовать первую появившуюся пустую строку, но в моих тестовых данных их было много, и я хотел обработать несколько абзацев.

И поэтому, если он содержит символ, его буквальная версия добавляется к запомненной строке, а его версия с разнесенным сравнением располагается в начале пространства шаблонов, например так:

^   \n               \nremembered line\nnew$

Последнее замещение применяется к этому образцу пространства:

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

Таким образом, если символ новой строки может уместиться в пределах пространства, необходимого для хранения запомненной строки, по крайней мере, с одним запасным символом, тогда первые две строки заменяются, в противном случае - только первая.

Независимо от результата, первая строка в шаблонном пространстве всегда Dвыбирается в конце цикла, прежде чем начинать снова. Это означает, что если новая строка короче последней строки ...

new

... отправляется обратно к первой замене в цикле, которая всегда будет удаляться только с первого символа новой строки - и поэтому она остается целой. Но если это не так, строка ...

remembered line\nnew

... вместо этого начнется следующий цикл, и первая замена удалит из него строку ...

\nnew

...каждый раз.

В самой последней строке запомненная строка выводится на стандартный вывод, поэтому для приведенных данных примера она печатает:

4for

Но, если серьезно, используйте tr.

mikeserv
источник
Вам даже нужно вставить номера строк? Мое чтение ОП заключается в том, что требуется только самая короткая строка, а не обязательно номер строки этой строки. Я полагаю, не вредно, чтобы показать это для полноты.
Цифровая травма
@DigitalTrauma - нет, наверное нет. Но вряд ли это очень полезно без них - и они идут так дешево. Когда я работаю с потоком, я всегда предпочитаю включать средства вывода оригинального ввода идентично на выходе - номера строк делают это возможным здесь. Например, чтобы включить результаты первого вокруг трубопровода: REINPUT | sort -t: -nk1,1 | cut -d: -f3-. И второе - это просто включить еще один sed --expressionскрипт в хвост.
mikeserv
@DigitalTrauma - ой, и в первом примере номер строк действительно влияет на sortповедение «s в качестве связующего выключателя , когда же длина линия возникает на входе - так самые ранние встречающиеся линии всегда всплывает на поверхность в этом случае.
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чтобы получить строку с наибольшим количеством символов в этом случае.

(Это было принято из этого ответа )

Bichoy
источник
+1 за логику, но она не будет работать во всех случаях. Если две строки имеют одинаковое количество символов и является минимальным. Это даст вам только первую строчку, которая встречается из-заhead -1
Thushi
Чтобы получить самую длинную строку, немного эффективнее изменить сортировку, чем использовать tail(так как headможно завершить работу, как только ее работа будет выполнена, без чтения оставшейся части ее ввода).
Тоби Спейт
@Thushi Используя немного регулярных выражений, после печати номеров строк можно удалить все, кроме строк с тем же номером, что и строка 1, и вывести все самые короткие строки.
Мэтью Д. Скоулфилд
5

С POSIX awk:

awk 'FNR==1{l=$0;next};length<length(l){l=$0};END{print l}' file
cuonglm
источник
Это не будет работать, если несколько строк имеют одинаковое количество символов и также минимально.
Thushi
@Thushi: он сообщит о первой минимальной строке.
Cuonglm
Да. Но это не правильный вывод, верно? Даже другие строки имеют минимальное количество символов.
Thushi
1
@ Thushi: Это не упоминается в требовании OP, ожидая обновления от OP.
cuonglm
3
Я не думаю, что это Lбыло лучшее письмо, чтобы выбрать имя переменной: D Нечто подобное minпрояснит
ситуацию
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" - это выражение оболочки, которое может быть оценено
  • Команда е кs является СЭД расширение 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:

$ 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: «escape-последовательность \ n соответствует символу новой строки, встроенному в пространство образца» . Поэтому я думаю, что GNU sed допускает \nрегулярное выражение и замену, тогда как BSD допускает только \nрегулярное выражение, а не замену.
Цифровая травма
Заимствование \nиз пространства шаблонов является хорошей идеей и будет работать во втором s///выражении, но s/.*/&\n&/выражение вставляет \nв пространство шаблонов, где его раньше не было. Также для BSD sed, по-видимому, требуются буквальные символы новой строки после определений меток и ветвей.
Цифровая травма
1
Эти новые строки являются разделителями параметров - они нужны для разделения любой команды, которая может принимать произвольный параметр - по крайней мере, так говорится в спецификации. В спецификации также сказано, что sedскрипт должен быть текстовым файлом, за исключением того, что он не должен заканчиваться переводом строки . Таким образом, вы обычно можете разделять их как отдельные аргументы - sed -e :\ label -e :\ label2и так далее. Так как вы все 1hравно делаете , вы можете просто переключиться на некоторую логику, основанную на x;Hполучении новой строки - и вы можете урезать ведущую новую строку из пространства образца в конце цикла, не вытягивая новую строку с / D.
Микесерв
@mikeserv Ницца. Да, я вставил нужную мне новую строку, выполнив Gпервое и изменив s///выражение. Разделение на части с помощью -eпозволяет всем идти по одной (длинной) строке без буквальных переносов строк.
Цифровая травма
\nЭвакуации билд для sed«s LHS, также, и я думаю , что это утверждение спецификации дословно, за исключением того, что выражения скобки POSIX также билд таким образом , чтобы все символы теряют свое особое значение - (явно включая \\) - в пределах одного, за исключением скобок, тире как разделитель диапазона и точка, равно, каретка, двоеточие для сопоставления, эквивалентности, отрицания и классов.
mikeserv
2

Другое решение Perl: хранить строки в хеш-массивах, ключом хеша является длина строки. Затем распечатайте строки с ключом минимума.

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
Если бы я играл в гольф, я бы тоже не использовал переменное имя «линии»:perl -MList::Util=min -nE'push @{$l{+length}},$_}END{say@{$l{min keys%l}}' sample
Гленн Джекман
+1 для версии без игры в гольф (которая работает!), Хотя только для варианта печати весь . - perlстановится немного грубовато для тех из нас, кто не в порядке perlс загадочной природой. КСТАТИ. игра в гольф 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
Peter.O
источник
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
snth
источник
2

Как насчет использования сортировки?

awk '{ print length($0) "\t" $0 }' input.txt | sort -n | head -n 1 | cut -f2-
Gaurav
источник
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вовремя

Iruvar
источник
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