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

Несомненно, создайте функцию обертки и вставьте ее Ваш .bashrc.

Что-то вроде этого:

ssh () {
  ssh-add -l | grep 'The agent has no identities.' 2>&1 > /dev/null && ssh-add
  /usr/bin/ssh "$@"
}

Хотя, так как Вы используете Cygwin, Вам будет нужен надлежащий путь к тому, везде, где ssh действительно.

22
18.07.2015, 06:29
17 ответов

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

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

Explanation

  • perl -lne : -n означает «прочитать ввод файл построчно », -l вызывает удаление завершающих символов новой строки из каждой строки ввода и добавление новой строки к каждому вызову print ; и -e - сценарий, который будет применяться к каждой строке.
  • $ m // = $ _ : установить $ m в текущую строку ( $ _ ), если не определено $ m . Оператор // = доступен начиная с Perl 5.10.0.
  • $ m = $ _ if length () : если длина текущего значения $ m больше, чем длина текущей строки, сохранить текущая строка ( $ _ ) как $ m .
  • END {вывести $ m, если $.} : после обработки всех строк вывести текущее значение $ m , самой короткой строки. , если $. гарантирует, что это происходит только тогда, когда определен номер строки ( $. ), избегая печати пустой строки для пустого ввода.

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

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

Пояснение

  • @ K = sort {length ($ a) <=> length ($ b)} <> : <> здесь массив, элементами которого являются строки файла. Сортировка отсортирует их по длине, а отсортированные строки сохранятся в виде массива @K .
  • вывести «$ K [0]» : вывести первый элемент массива @K : самую короткую строку.

Если вы хотите напечатать все самые короткие строки, вы можете использовать

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

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

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

, чтобы получить только первую кратчайшую строку:

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

, чтобы получить все самые короткие львы, просто изменить {p; q} на p


Другой метод (несколько необычный) состоит в том, чтобы сортировать Сортировать фактическое сортировка по длине . Это относительно медленно даже с короткими линиями, и становится резко медленнее, поскольку длина линии увеличивается.
Тем не менее, я нахожу идею сортировки по клавишам довольно интересно. Я размещаю это на случай, если другие могут также найти это интересным / информативным.

Как это работает:
Сортировать по длине-вариантам того же ключа - ключ 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
27.01.2020, 19:42

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

Python -C «Импорт Sys; print min (sys.stdin, ключ = len),"

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

Python3 -C «Импорт Sys; Print (Min (Sys.S.Stdin, Key = Len). «

12
27.01.2020, 19:42

Предполагая, что пустые строки не считаются самой короткой линией, и могут существовать пустые строки, следующий чистый 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
2
27.01.2020, 19:42

Я всегда люблю решения с помощью чистых сценариев Shell (No 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 Bytes. Итак, printf "ab \ 0 \ 0 \ ncd \ n" | bash this_script Отпечатки AB вместо CD .

10
27.01.2020, 19:42

Вот вариант решения 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}'
'
18
27.01.2020, 19:42

С POSIX awk:

awk 'FNR==1{l=$0;next};length<length(l){l=$0};END{print l}' file
5
27.01.2020, 19:42

Попробуйте:

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

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

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

.
7
27.01.2020, 19:42

Мне пришло в голову, что все возможно в одном 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
$

Обратите внимание, что это скорее ответ «потому что это возможно», чем серьезная попытка дать лучший практический ответ. Думаю, это означает, что я слишком много играл в code-colf

3
27.01.2020, 19:42

Здесь чистое решение zsh (выводит все строки минимальной длины из файла ) :

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], который является самым коротким, в вашем случае это now ???? .

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

Сопоставление выполняется для каждого элемента массива отдельно, и несогласованные элементы массива удаляются ( M ). Каждый элемент, соответствующий ???? (4 символа), остается в массиве. Таким образом, оставшиеся элементы - это те, которые имеют 4 символа (самые короткие).

Изменить: Если вам нужна только одна из самых коротких строк, эта измененная версия печатает первую:

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

С sqlite3 :

sqlite3 <<EOT
CREATE TABLE file(line);
.import "data.txt" file
SELECT line FROM file ORDER BY length(line) LIMIT 1;
EOT
18
27.01.2020, 19:42

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

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

Заимствуя некоторые идеи @ 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 "целая строка" - это выражение оболочки, которое можно вычислить
  • Команда e для s является расширением GNU sed для оценки пространства шаблонов и возврата результата обратно в пространство шаблонов.
  • G добавляет новую строку и содержимое удерживаемого пространства (исходная строка) в пространство шаблонов
  • заключительные s заменяют новую строку табуляцией

Количество теперь символы - это число в начале каждой строки, поэтому sort -n сортирует по длине строки.

Последний sed затем удаляет все, кроме первой (самой короткой) строки и длины строки, и печатает результат.

3
27.01.2020, 19:42
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? - для обозначения длины линии. Похожий - и, возможно, более аккуратный - подход может заключаться в сжатии этой информации в потоке. Первая идея в этом направлении, которая приходит мне в голову, состоит в том, что я должен раскрыть это:

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 \) \ {считает \} правильно - многие - нет.

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

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

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

string1\nstring2\nstring3

... соответствует s в \ 1 и '' нулевая строка в \ 2 .

1\nstring2\nstring3

... соответствует 1 в \ 1 и \ nstring2 \ nstring3 в \ 2

\nstring2\nstring3

... соответствует с \ n в \ 1 и '' нулевой строкой в ​​ \ 2 . Это было бы проблематично, если бы существовала вероятность появления \ n строки ewline в начале пространства шаблонов - но / ^ \ 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 uit на первой появившейся пустой строке, но, ну, в моих тестовых данных их было много, и я хотел обработать несколько абзацев.

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

^   \n               \nremembered line\nnew$

Наконец, к этому пространству шаблонов применяется подстановка:

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

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

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

new

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

remembered line\nnew

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

\nnew

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

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

4for

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

8
27.01.2020, 19:42

С 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
27.01.2020, 19:42

Метод оболочек среднего -уровня, без 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
1
27.01.2020, 19:42

Теги

Похожие вопросы