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
... совпадает с s
in \1
и ''
нулевой строкой in \2
.
1\nstring2\nstring3
... сочетается с 1
в \1
и \nstring2\nstring3
в\2
\nstring2\nstring3
... совпадает с \n
in \1
и ''
нулевой строкой in \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
использовать первую появившуюся пустую строку, но в моих тестовых данных их было много, и я хотел обработать несколько абзацев.
И поэтому, если он содержит символ, его буквальная версия добавляется к запомненной строке, а его версия с разнесенным сравнением располагается в начале пространства шаблонов, например так:
^ \n \nremembered line\nnew$
Последнее замещение применяется к этому образцу пространства:
s/^\( *\)\(\n \1 *\)\{0,1\}\n//
Таким образом, если символ новой строки может уместиться в пределах пространства, необходимого для хранения запомненной строки, по крайней мере, с одним запасным символом, тогда первые две строки заменяются, в противном случае - только первая.
Независимо от результата, первая строка в шаблонном пространстве всегда D
выбирается в конце цикла, прежде чем начинать снова. Это означает, что если новая строка короче последней строки ...
new
... отправляется обратно к первой замене в цикле, которая всегда будет удаляться только с первого символа новой строки - и поэтому она остается целой. Но если это не так, строка ...
remembered line\nnew
... вместо этого начнется следующий цикл, и первая замена удалит из него строку ...
\nnew
...каждый раз.
В самой последней строке запомненная строка выводится на стандартный вывод, поэтому для приведенных данных примера она печатает:
4for
Но, если серьезно, используйте tr
.