Форматирование текста в терминале Linux с помощью команд Fold и FMT
Форматировать текст так, чтобы строки помещались в доступное пространство на целевом устройстве, не так просто, когда дело касается терминала. Вместо разрыва строк вручную вы можете использовать утилиту сгиба POSIX и команду GNU/BSD fmt для перекомпоновки текста, чтобы строки не превышали заданную длину.
При использовании текстового процессора форматирование текста таким образом, чтобы строки помещались в доступное пространство на целевом устройстве, не должно быть проблемой. А вот при работе за терминалом все не так просто.
Конечно, вы всегда можете разбить строки вручную с помощью любимого текстового редактора, но это редко бывает желательно и для автоматизированной обработки об этом даже не может быть и речи.
Надеемся, что утилита POSIX fold
и команда GNU/BSD fmt
помогут вам перекомпоновать текст так, чтобы строки не превышали заданную длину.
Что такое строка в Unix?
Прежде чем углубляться в детали команд fold
и fmt
, давайте сначала определим, о чем мы говорим. В текстовом файле строка состоит из произвольного количества символов, за которыми следует специальная управляющая последовательность новой строки (иногда называемая EOL, для конца строки).
В Unix-подобных системах управляющая последовательность конца строки состоит из (одного и единственного) символа перевода строки, иногда сокращенно LF или записываемого \. n
в соответствии с соглашением, унаследованным от языка C. На двоичном уровне символ перевода строки представлен как байт, содержащий шестнадцатеричное значение 0a
.
Вы можете легко убедиться, что с помощью утилиты hexdump
мы будем много использовать в этой статье. Так что это может быть хорошим поводом ознакомиться с этим инструментом. Вы можете, например, просмотреть шестнадцатеричные дампы ниже, чтобы узнать, сколько символов новой строки было отправлено каждой командой echo. Как только вы решите, что нашли решение, просто повторите эти команды без | hexdump -C
, чтобы проверить, правильно ли вы догадались.
sh$ echo hello | hexdump -C
00000000 68 65 6c 6c 6f 0a |hello.|
00000006
sh$ echo -n hello | hexdump -C
00000000 68 65 6c 6c 6f |hello|
00000005
sh$ echo -e 'hello\n' | hexdump -C
00000000 68 65 6c 6c 6f 0a 0a |hello..|
00000007
Здесь стоит упомянуть, что разные операционные системы могут следовать разным правилам относительно последовательности новой строки. Как мы видели выше, Unix-подобные операционные системы используют символ перевода строки, но Windows, как и большинство интернет-протоколов, использует два символа: Пара возврат каретки+перевод строки (CRLF, или 0d 0a
, или \r\n
). В «классической» Mac OS (вплоть до MacOS 9.2 в начале 2000-х годов) компьютеры Apple использовали только CR в качестве символа новой строки. Другие устаревшие компьютеры также использовали пару LFCR или даже совершенно другие последовательности байтов в случае старых ASCII-несовместимых систем. К счастью, последние являются пережитками прошлого, и я сомневаюсь, что вы увидите какой-либо компьютер EBCDIC, используемый сегодня!
Говоря об истории, если вам интересно, использование управляющих символов «возврат каретки» и «перевод строки» восходит к коду Бодо, использовавшемуся в эпоху телетайпа. Возможно, вы видели телетайп в старых фильмах как интерфейс к компьютеру размером с комнату. Но и до этого телетайпы использовались «автономно» для связи «точка-точка» или «многоточка». Типичный терминал того времени выглядел как тяжелая пишущая машинка с механической клавиатурой, бумагой и подвижной кареткой, на которой находилась печатающая головка. Чтобы начать новую линию, каретку необходимо вернуть в крайнее левое положение, а бумага должна двигаться вверх за счет вращения валика (иногда называемого «цилиндром»). Эти два движения контролировались двумя независимыми электромеханическими системами, причем символы управления переводом строки и возвратом каретки были напрямую подключены к этим двум частям устройства. Поскольку для перемещения каретки требуется больше времени, чем для вращения валика, логично было сначала инициировать возврат каретки. Разделение этих двух функций также имело несколько интересных побочных эффектов, например, разрешение наложения печати (путем отправки только CR) или эффективную передачу «двойного интерлайна» (один CR + два LF).
Определение в начале этого раздела в основном описывает, что такое логическая линия. Однако в большинстве случаев эту «произвольно длинную» логическую строку приходится отправлять на физическое устройство, такое как экран или принтер, где доступное пространство ограничено. Отображение коротких логических линий на устройстве, имеющем более крупные физические линии, не является проблемой. Просто справа от текста осталось неиспользуемое место. Но что, если вы попытаетесь отобразить строку текста, размер которой превышает доступное место на устройстве? На самом деле есть два решения, каждое из которых имеет свои недостатки:
Во-первых, устройство может обрезать строки до своего физического размера, тем самым скрывая часть контента от пользователя. Некоторые принтеры делают это, особенно тупые принтеры (и да, сегодня все еще используются базовые матричные принтеры, особенно в суровых или грязных условиях!)
Второй вариант отображения длинных логических строк — разбить их на несколько физических строк. Это называется переносом строк, поскольку создается впечатление, что строки обтекают доступное пространство, и этот эффект особенно заметен, если вы можете изменить размер экрана, как при работе с эмулятором терминала.
Такое автоматическое поведение весьма полезно, но все же бывают случаи, когда вам нужно разбить длинные строки в заданной позиции, независимо от физического размера устройства. Например, это может быть полезно, поскольку вы хотите, чтобы разрывы строк располагались в одном и том же месте как на экране, так и на принтере. Или потому, что вы хотите, чтобы ваш текст использовался в приложении, которое не выполняет перенос строк (например, если вы программно встраиваете текст в файл SVG). Наконец, хотите верьте, хотите нет, но все еще существует множество протоколов связи, которые устанавливают максимальную ширину строки при передаче, в том числе такие популярные, как IRC и SMTP (если вы когда-либо видели ошибку 550. Максимальная длина строки превышена, вы знаете, о чем я говорю). говоря о). Таким образом, во многих случаях вам нужно разбить длинные строки на более мелкие куски. Это задача команды POSIX fold
.
Команда сгиба
При использовании без каких-либо опций команда fold
добавляет дополнительные управляющие последовательности новой строки, чтобы гарантировать, что ни одна строка не превысит ограничение в 80 символов. Просто для ясности: одна строка может содержать не более 80 символов плюс последовательность новой строки.
Если вы загрузили вспомогательные материалы для этой статьи, вы можете попробовать это самостоятельно:
sh$ fold POSIX.txt | head -5
The Portable Operating System Interface (POSIX)[1] is a family of standards spec
ified by the IEEE Computer Society for maintaining compatibility between operati
ng systems. POSIX defines the application programming interface (API), along wit
h command line shells and utility interfaces, for software compatibility with va
riants of Unix and other operating systems.[2][3]
# Using AWK to prefix each line by its length:
sh$ fold POSIX.txt | awk '{ printf("%3d %s\n", length($0), $0) }'
80 The Portable Operating System Interface (POSIX)[1] is a family of standards spec
80 ified by the IEEE Computer Society for maintaining compatibility between operati
80 ng systems. POSIX defines the application programming interface (API), along wit
80 h command line shells and utility interfaces, for software compatibility with va
49 riants of Unix and other operating systems.[2][3]
0
80 The standards emerged from a project that began circa 1985. Richard Stallman sug
80 gested the name POSIX to the IEEE instead of former IEEE-IX. The committee found
71 it more easily pronounceable and memorable, and thus adopted it.[2][4]
Вы можете изменить максимальную длину выходной строки, используя опцию -w
. Более интересным, вероятно, является использование опции -s
, обеспечивающей разрыв строк на границе слова. Давайте сравним результат без и с опцией -s
при ее применении ко второму абзацу нашего примера текста:
# Without `-s` option: fold will break lines at the specified position
# Broken lines have exactly the required width
sh$ awk -vRS='' 'NR==2' POSIX.txt |
fold -w 30 | awk '{ printf("%3d %s\n", length($0), $0) }'
30 The standards emerged from a p
30 roject that began circa 1985.
30 Richard Stallman suggested the
30 name POSIX to the IEEE instea
30 d of former IEEE-IX. The commi
30 ttee found it more easily pron
30 ounceable and memorable, and t
21 hus adopted it.[2][4]
# With `-s` option: fold will break lines at the last space before the specified position
# Broken lines are shorter or equal to the required width
awk -vRS='' 'NR==2' POSIX.txt |
fold -s -w 30 | awk '{ printf("%3d %s\n", length($0), $0) }'
29 The standards emerged from a
25 project that began circa
23 1985. Richard Stallman
28 suggested the name POSIX to
27 the IEEE instead of former
29 IEEE-IX. The committee found
29 it more easily pronounceable
24 and memorable, and thus
17 adopted it.[2][4]
Очевидно, что если ваш текст содержит слова длиннее максимальной длины строки, команда сгиба не сможет учитывать флаг -s
. В этом случае утилита fold
разобьет слишком большие слова в максимальной позиции, всегда гарантируя, что ни одна строка не превысит максимально допустимую ширину.
sh$ echo "It's Supercalifragilisticexpialidocious!" | fold -sw 10
It's
Supercalif
ragilistic
expialidoc
ious!
Многобайтовые символы
Как и большинство, если не все, основные утилиты, команда fold
была разработана таким образом, что один символ был эквивалентен одному байту. Однако в современных вычислениях это уже не так, особенно с широким распространением UTF-8. Что-то, что приводит к неприятным проблемам:
# Just in case, check first the relevant locale
# settings are properly defined
debian-9.4$ locale | grep LC_CTYPE
LC_CTYPE="en_US.utf8"
# Everything is OK, unfortunately...
debian-9.4$ echo élève | fold -w2
é
l�
�v
e
Слово «élève» (по-французски «студент») состоит из двух ударных букв: é
(ЛАТИНСКАЯ СТРОЧНАЯ БУКВА E С ОСТРЫМ) и è
(ЛАТИНСКАЯ СТРОЧНАЯ БУКВА E С ОСТРЫМ) МОГИЛА). Используя набор символов UTF-8, эти буквы кодируются с использованием двух байтов каждая (соответственно, c3 a9
и c3 a8
), а не только одного байта, как в случае с безударные латинские буквы. Вы можете проверить это, проверив необработанные байты с помощью утилиты hexdump
. Вы должны быть в состоянии определить последовательности байтов, соответствующие символам é
и è
. Кстати, в этом дампе вы также можете увидеть нашего старого знакомого символ перевода строки, шестнадцатеричный код которого упоминался ранее:
debian-9.4$ echo élève | hexdump -C
00000000 c3 a9 6c c3 a8 76 65 0a |..l..ve.|
00000008
Давайте теперь рассмотрим вывод, полученный командой сгиба:
debian-9.4$ echo élève | fold -w2
é
l�
�v
e
debian-9.4$ echo élève | fold -w 2 | hexdump -C
00000000 c3 a9 0a 6c c3 0a a8 76 0a 65 0a |...l...v.e.|
0000000b
Очевидно, что результат, полученный командой fold
, немного длиннее исходной строки символов из-за дополнительных символов новой строки: соответственно 11 байт и 8 байт, включая символы новой строки. Говоря об этом, в выводе команды fold
вы могли видеть символ перевода строки (0a
), появляющийся каждые два байта. И в этом-то и заключается проблема: команда сгиба разрывала строки на позициях байтов, а не на позициях символов. Даже если этот разрыв происходит в середине многобайтового символа! Нет необходимости упоминать, что полученный результат больше не является допустимым потоком байтов UTF-8, поэтому мой терминал использует символ замены Юникода (�
) в качестве заполнителя для недопустимых последовательностей байтов.
Как и в случае с командой cut
, о которой я писал несколько недель назад, это ограничение в реализации GNU утилиты fold
, и это явно противоречит спецификациям POSIX, которые явно указано, что «Строка не должна разрываться посередине символа. ”
Таким образом, похоже, что реализация GNU fold
корректно работает только с однобайтовыми кодировками символов фиксированной длины (US-ASCII, Latin1 и т. д.). В качестве обходного пути, если существует подходящий набор символов, вы можете перекодировать текст в однобайтовую кодировку символов перед его обработкой, а затем перекодировать его обратно в UTF-8. Однако это, по меньшей мере, громоздко:
debian-9.4$ echo élève |
iconv -t latin1 | fold -w 2 |
iconv -f latin1 | hexdump -C
00000000 c3 a9 6c 0a c3 a8 76 0a 65 0a |..l...v.e.|
0000000a
debian-9.4$ echo élève |
iconv -t latin1 | fold -w 2 |
iconv -f latin1
él
èv
e
Все это меня весьма разочаровало, и я решил проверить поведение других реализаций. Как это часто бывает, реализация утилиты fold
в OpenBSD намного лучше в этом вопросе, поскольку она совместима с POSIX и учитывает настройку локали LC_CTYPE
для правильной обработки нескольких -байтовые символы:
openbsd-6.3$ locale | grep LC_CTYPE
LC_CTYPE=en_US.UTF-8
openbsd-6.3$ echo élève | fold -w 2 C
él
èv
e
openbsd-6.3$ echo élève | fold -w 2 | hexdump -C
00000000 c3 a9 6c 0a c3 a8 76 0a 65 0a |..l...v.e.|
0000000a
Как видите, реализация OpenBSD правильно разрезает строки по позициям символов, независимо от количества байтов, необходимых для их кодирования. В подавляющем большинстве случаев использования это именно то, что вам нужно. Однако если вам нужно устаревшее поведение (т. е. стиль GNU), рассматривающее один байт как один символ, вы можете временно изменить текущую локаль на так называемую локаль POSIX (определяемую константой «POSIX» или, по историческим причинам, «C»). »):
openbsd-6.3$ echo élève | LC_ALL=C fold -w 2
é
l�
�v
e
openbsd-6.3$ echo élève | LC_ALL=C fold -w 2 | hexdump -C
00000000 c3 a9 0a 6c c3 0a a8 76 0a 65 0a |...l...v.e.|
0000000b
Наконец, POSIX определяет флаг -b
, который указывает утилите fold
измерять длину строки в байтах, но тем не менее, это гарантирует, что многобайтовые символы (в соответствии с текущими настройками локали LC_CTYPE
) не будут повреждены.
В качестве упражнения я настоятельно рекомендую вам потратить время, необходимое на поиск различий на уровне байтов между результатом, полученным путем изменения текущей локали на «C» (выше), и результатом, полученным с помощью -b. Вместо этого флаг
(ниже). Это может быть тонко. Но есть есть разница:
openbsd-6.3$ echo élève | fold -b -w 2 | hexdump -C
00000000 c3 a9 0a 6c 0a c3 a8 0a 76 65 0a |...l....ve.|
0000000b
Итак, вы нашли разницу?
Что ж, изменив локаль на «C», утилита fold
не позаботилась о многобайтовых последовательностях, поскольку по определению, когда локаль равна «C», инструменты должны предполагать, что один символ — это один байт. Таким образом, новую строку можно добавить где угодно, даже в середине последовательности байтов, которая рассматривалась бы как многобайтовый символ в другой кодировке символов. Именно это и произошло, когда инструмент создал последовательность байтов c3 0a a8
: два байтаc3 a8
понимается как один символ, когда LC_CTYPE
определяет кодировку символов UTF-8. Но та же самая последовательность байтов рассматривается как два символа в локали «C»:
# Bytes are bytes. They don't change so
# the byte count is the same whatever is the locale
openbsd-6.3$ printf "%d bytes\n" $(echo -n é | LC_ALL=en_US.UTF-8 wc -c)
2 bytes
openbsd-6.3$ printf "%d bytes\n" $(echo -n é | LC_ALL=C wc -c)
2 bytes
# The interpretation of the bytes may change depending on the encoding
# so the corresponding character count will change
openbsd-6.3$ printf "%d chars\n" $(echo -n é | LC_ALL=en_US.UTF-8 wc -m)
1 chars
openbsd-6.3$ printf "%d chars\n" $(echo -n é | LC_ALL=C wc -m)
2 chars
С другой стороны, с опцией -b
инструмент все равно должен поддерживать многобайтовую обработку. Изменится только этот параметр — способ подсчета позиций, на этот раз в байтах, а не в символах, как по умолчанию. В этом случае, поскольку многобайтовые последовательности не разбиваются, результирующий вывод остается допустимым потоком символов (в соответствии с текущими настройками локали LC_CTYPE
):
openbsd-6.3$ echo élève | fold -b -w 2
é
l
è
ve
Как вы видели, теперь символ замены Юникода (�
) больше не встречается, и мы не потеряли ни одного значимого символа в процессе — за счет того, что на этот раз в итоге остались строки, содержащие переменное количество символов и переменное количество байтов. Наконец, весь инструмент гарантирует, что в строке не будет больше байтов, чем запрошено с помощью опции -w
. Кое-что мы можем проверить с помощью инструмента wc
:
openbsd-6.3$ echo élève | fold -b -w 2 | while read line; do
> printf "%3d bytes %3d chars %s\n" \
> $(echo -n $line | wc -c) \
> $(echo -n $line | wc -m) \
> $line
> done
2 bytes 1 chars é
1 bytes 1 chars l
2 bytes 1 chars è
2 bytes 2 chars ve
Еще раз найдите время, необходимое для изучения приведенного выше примера. Он использует команды printf и wc, которые я ранее не объяснял подробно. Так что, если что-то не совсем ясно, не стесняйтесь использовать раздел комментариев, чтобы попросить некоторых объяснений!
Из любопытства я проверил флаг -b
в своем компьютере с Debian, используя реализацию GNU fold
:
debian-9.4$ echo élève | fold -w 2 | hexdump -C
00000000 c3 a9 0a 6c c3 0a a8 76 0a 65 0a |...l...v.e.|
0000000b
debian-9.4$ echo élève | fold -b -w 2 | hexdump -C
00000000 c3 a9 0a 6c c3 0a a8 76 0a 65 0a |...l...v.e.|
0000000b
Не тратьте время, пытаясь найти разницу между версиями этого примера с -b
и без -b
: мы видели, что реализация сгиба GNU не является многоуровневой. -байт учитывается, поэтому оба результата идентичны. Если вы в этом не уверены, возможно, вы могли бы использовать команду diff -s
, чтобы ваш компьютер подтвердил это. Если вы это сделаете, пожалуйста, используйте раздел комментариев, чтобы поделиться использованной вами командой с другими читателями!
В любом случае, означает ли это, что опция -b
бесполезна в GNU-реализации утилиты fold
? Что ж, прочитав более внимательно документацию GNU Coreutils для команды fold
, я обнаружил, что опция -b
работает только с специальными символами как табуляция или пробел, которые соответственно учитываются за 1~8 (от одного до восьми) или -1 (минус один) позиции в обычном режиме, но они всегда учитываются за 1 позицию в байтовом режиме. Сбивает с толку? Итак, возможно, нам потребуется некоторое время, чтобы объяснить это более подробно.
Обработка табуляции и возврата на один пробел
Большинство текстовых файлов, с которыми вам придется иметь дело, содержат только печатные символы и последовательности конца строк. Однако иногда может случиться так, что некоторые управляющие символы попадут в ваши данные. Символ табуляции (\t
) — один из них. Гораздо реже можно встретить возвратное пространство (\b
). Я все равно упоминаю его здесь, потому что, как следует из названия, это управляющий символ, который заставляет курсор перемещаться на одну позицию назад (влево), тогда как большинство других символов заставляют его двигаться вперед (вправо).
sh$ echo -e 'tab:[\t] backspace:[\b]'
tab:[ ] backspace:]
Это может быть не видно в вашем браузере, поэтому я настоятельно рекомендую вам проверить это на своем терминале. Но символы табуляции (\t
) занимают несколько позиций при выводе. А бэкспейс? Кажется, в выводе есть что-то странное, не так ли? Итак, давайте немного замедлим работу, разбив текстовую строку на несколько частей и вставив между ними немного sleep
:
# For that to work, type all the commands on the same line
# or using backslashes like here if you split them into
# several (physical) lines:
sh$ echo -ne 'tab:[\t] backspace:['; \
sleep 1; echo -ne '\b'; \
sleep 1; echo -n ']'; \
sleep 1; echo ''
ХОРОШО? Вы видели это на этот раз? Разложим последовательность событий:
Первая строка символов отображается «обычно» до второй открывающей квадратной скобки. Из-за флага
-n
командаecho
не отправляет символ новой строки, поэтому курсор остается включенным та же линия.Первый сон.
Выдается Backspace, в результате чего курсор перемещается на одну позицию назад. Новой строки по-прежнему нет, поэтому курсор остается на той же строке.
Второй сон.
Отображается закрывающая квадратная скобка, заменяющая открывающую.
Третий сон.
При отсутствии опции
-n
последняя командаecho
наконец отправляет символ новой строки и курсор перемещается на следующую строку, где будет отображаться приглашение оболочки.
Конечно, такой же крутой эффект можно получить, используя возврат каретки, если вы его помните:
sh$ echo -n 'hello'; sleep 1; echo -e '\rgood bye'
good bye
Я почти уверен, что вы уже видели некоторые утилиты командной строки, такие как Curl и wget, отображающие индикатор выполнения. Они творят чудеса, используя комбинацию \b
и/или \r
.
Интересно, что обсуждение может быть самостоятельным, главное здесь было понять, что обработка этих символов может быть сложной задачей для утилиты fold
. Будем надеяться, что стандарт POSIX определяет правила:
Все эти специальные процедуры отключаются при использовании опции -b
. В этом случае управляющие символы, прежде всего, считаются (правильно) за один байт и, таким образом, увеличивают счетчик позиции на один и только один — точно так же, как и любые другие символы.
Для лучшего понимания я позволю вам самостоятельно изучить два следующих примера (возможно, с помощью утилиты hexdump
). Теперь вы сможете выяснить, почему «привет» превратилось в «ад» и где именно находится «i» в выводе (так как он есть, даже если вы его не видите!) Как всегда, если вам нужна помощь или просто если вы хотите поделиться своими выводами, раздел комментариев — ваш.
# Why "hello" has become "hell"? where is the "i"?
sh$ echo -e 'hello\rgood bi\bye' | fold -w4
hell
good
bye
# Why "hello" has become "hell"? where is the "i"?
# Why the second line seems to be made of only two chars instead of 4?
sh$ echo -e 'hello\rgood bi\bye' | fold -bw4
hell
go
od b
ye
Другие ограничения
Команда fold
, которую мы изучали до сих пор, была разработана для разбиения длинных логических строк на более мелкие физические строки, в частности, для целей форматирования.
Это означает, что предполагается, что каждая строка ввода является автономной и может быть разбита независимо от других строк. Однако это не всегда так. Например, давайте рассмотрим то очень важное письмо, которое я получил:
sh$ cat MAIL.txt
Dear friends,
Have a nice day!
We are manufactuer for event chairs and tables, more than 10 years experience.
We supply all kinds of wooden, resin and metal event chairs, include chiavari
chairs, cross back chairs, folding chairs, napoleon chairs, phoenix chairs, etc.
Our chairs and tables are of high quality and competitively priced.
If you need our products, welcome to contact me;we are happy to make you special
offer.
Best Regards
Doris
sh$ awk '{ length>maxlen && (maxlen=length) } END { print maxlen }' MAIL.txt
81
Очевидно, линии уже были разбиты на некоторую фиксированную ширину. Команда awk
сообщила мне, что максимальная ширина строки здесь составляет… 81 символ, исключая новую последовательность строк. Да, это было настолько странно, что я дважды проверил: действительно, самая длинная строка содержит 80 печатных символов плюс один дополнительный пробел на 81-й позиции, и только после этого идет символ перевода строки. Вероятно, ИТ-специалистам, работающим от имени «производителя» этого кресла, будет полезно прочитать эту статью!
В любом случае, если я захочу изменить форматирование этого письма, у меня возникнут проблемы с командой fold
из-за существующих разрывов строк. Я позволю вам самостоятельно проверить две команды, приведенные ниже, если хотите, но ни одна из них не будет работать должным образом:
sh$ fold -sw 100 MAIL.txt
sh$ fold -sw 60 MAIL.txt
Первый просто ничего не сделает, так как все строки уже короче 100 символов. Что касается второй команды, она разрывает строки на 60-й позиции, но сохраняет уже существующие символы новой строки, поэтому результат будет неровным. Особенно это будет заметно в третьем абзаце:
sh$ awk -v RS='' 'NR==3' MAIL.txt |
fold -sw 60 |
awk '{ length>maxlen && (maxlen=length); print length, $0 }'
53 We supply all kinds of wooden, resin and metal event
25 chairs, include chiavari
60 chairs, cross back chairs, folding chairs, napoleon chairs,
20 phoenix chairs, etc.
Первая строка третьего абзаца была разбита на позиции 53, что соответствует нашей максимальной ширине в 60 символов в строке. Однако вторая строка прервалась на позиции 25, поскольку этот символ новой строки уже присутствовал во входном файле. Другими словами, чтобы правильно изменить размер абзацев, нам нужно сначала соединить строки, прежде чем разрывать их в новой целевой позиции.
Вы можете использовать sed
или awk
для воссоединения строк. На самом деле, как я уже упоминал во вступительном видео, это будет для вас хорошим испытанием. Поэтому не стесняйтесь публиковать свое решение в разделе комментариев.
Что касается меня, то я пойду более простым путем и рассмотрю команду fmt
. Хотя это не стандартная команда POSIX, она доступна как в мире GNU, так и в BSD. Так что есть хорошие шансы, что его можно будет использовать в вашей системе. К сожалению, отсутствие стандартизации будет иметь некоторые негативные последствия, как мы увидим позже. Но сейчас давайте сосредоточимся на хороших моментах.
Команда ФМТ
Команда fmt
более развита, чем команда fold
, и имеет больше возможностей форматирования. Самое интересное то, что он может идентифицировать абзацы во входном файле по пустым строкам. Это означает, что все строки до следующей пустой строки (или конца файла) сначала будут объединены вместе, чтобы сформировать то, что я ранее называл «логической строкой» текста. Только после этого команда fmt
разобьет текст в запрошенной позиции.
Давайте теперь посмотрим, что это изменится при применении ко второму абзацу моего примера письма:
sh$ awk -v RS='' 'NR==3' MAIL.txt |
fmt -w 60 |
awk '{ length>maxlen && (maxlen=length); print length, $0 }'
60 We supply all kinds of wooden, resin and metal event chairs,
59 include chiavari chairs, cross back chairs, folding chairs,
37 napoleon chairs, phoenix chairs, etc.
Как ни странно, команда fmt
согласилась упаковать еще одно слово в первую строку. Но что еще интереснее, вторая строка теперь заполнена, то есть символ новой строки, уже присутствующий во входном файле после того, как слово «chiavari» (что это?) было отброшено. Конечно, все не идеально, и алгоритм обнаружения абзацев fmt
иногда выдает ложные срабатывания, как, например, в приветствиях в конце письма (строка 14 вывода):
sh$ fmt -w 60 MAIL.txt | cat -n
1 Dear friends,
2
3 Have a nice day! We are manufactuer for event chairs and
4 tables, more than 10 years experience.
5
6 We supply all kinds of wooden, resin and metal event chairs,
7 include chiavari chairs, cross back chairs, folding chairs,
8 napoleon chairs, phoenix chairs, etc.
9
10 Our chairs and tables are of high quality and competitively
11 priced. If you need our products, welcome to contact me;we
12 are happy to make you special offer.
13
14 Best Regards Doris
Ранее я говорил, что команда fmt
представляет собой более развитый инструмент форматирования текста, чем утилита fold
. В самом деле. Это может быть неочевидно на первый взгляд, но если вы внимательно посмотрите на строки 10–11, вы заметите, что после точки используются два пробела, что соответствует наиболее обсуждаемому соглашению о использование двух пробелов в конце предложения. Я не буду вдаваться в эту дискуссию, чтобы узнать, следует или не следует использовать два пробела между предложениями, но здесь у вас нет реального выбора: насколько мне известно, ни одна из распространенных реализаций команды fmt
не предлагает флаг для отключения двойного пробела после предложения. Разве где-то есть такая опция и я ее пропустил? Если это так, я буду рад, что вы сообщите мне об этом в разделе комментариев: как французский писатель, я никогда не использовал «двойной пробел» после предложения…
Больше возможностей ФМТ
Утилита fmt
имеет несколько больше возможностей форматирования, чем командаfold. Однако, поскольку они не определены в POSIX, между опциями GNU и BSD существуют серьезные несовместимости.
Например, опция -c
используется в мире BSD для центрирования текста, тогда как в fmt
GNU Coreutils она включает режим коронного поля, “ сохраняя отступы первых двух строк внутри абзаца и выравнивая левое поле каждой последующей строки с левым полем второй строки. «
Я позволю вам поэкспериментировать с GNU fmt -c
, если хотите. Лично я нахожу функцию центрирования текста BSD более интересной для изучения из-за некоторой странности: действительно, в OpenBSD fmt -c
центрирует текст в соответствии с целевой шириной, но без его перекомпоновки! Таким образом, следующая команда не будет работать так, как вы могли ожидать:
openbsd-6.3$ fmt -c -w 60 MAIL.txt
Dear friends,
Have a nice day!
We are manufactuer for event chairs and tables, more than 10 years experience.
We supply all kinds of wooden, resin and metal event chairs, include chiavari
chairs, cross back chairs, folding chairs, napoleon chairs, phoenix chairs, etc.
Our chairs and tables are of high quality and competitively priced.
If you need our products, welcome to contact me;we are happy to make you special
offer.
Best Regards
Doris
Если вы действительно хотите переформатировать текст до максимальной ширины 60 символов и центрировать результат, вам придется использовать два экземпляры команды fmt
:
openbsd-6.3$ fmt -w 60 MAIL.txt | fmt -c -w60
Dear friends,
Have a nice day! We are manufactuer for event chairs and
tables, more than 10 years experience.
We supply all kinds of wooden, resin and metal event chairs,
include chiavari chairs, cross back chairs, folding chairs,
napoleon chairs, phoenix chairs, etc.
Our chairs and tables are of high quality and competitively
priced. If you need our products, welcome to contact me;we
are happy to make you special offer.
Best Regards Doris
Я не буду приводить здесь исчерпывающий список различий между реализациями fmt
GNU и BSD… главным образом потому, что все варианты различны! За исключением, конечно, опции -w
. Говоря об этом, я забыл упомянуть -N
, где N — целое число, это сокращение для -wN
. Более того, вы можете использовать этот ярлык как с командами fold
, так и fmt
: так что, если вы были достаточно настойчивы, чтобы прочитать его статью до этого момента, в качестве награды вы теперь можете удивить своим друзьям, сохранив одно (!) целое нажатие клавиши при следующем использовании одной из этих утилит:
debian-9.4$ fmt -50 POSIX.txt | head -5
The Portable Operating System Interface
(POSIX)[1] is a family of standards specified
by the IEEE Computer Society for maintaining
compatibility between operating systems. POSIX
defines the application programming interface
openbsd-6.3$ fmt -50 POSIX.txt | head -5
The Portable Operating System Interface (POSIX)[1]
is a family of standards specified by the IEEE
Computer Society for maintaining compatibility
between operating systems. POSIX defines the
application programming interface (API), along
debian-9.4$ fold -sw50 POSIX.txt | head -5
The Portable Operating System Interface
(POSIX)[1] is a family of standards specified by
the IEEE Computer Society for maintaining
compatibility between operating systems. POSIX
defines the application programming interface
openbsd-6.3$ fold -sw50 POSIX.txt | head -5
The Portable Operating System Interface
(POSIX)[1] is a family of standards specified by
the IEEE Computer Society for maintaining
compatibility between operating systems. POSIX
defines the application programming interface
В заключение отметим, что в последнем примере версии утилиты fmt
для GNU и BSD используют другой алгоритм форматирования, что приводит к разным результатам. С другой стороны, более простой алгоритм fold
дает согласованные результаты между реализациями. Все это говорит о том, что если переносимость является приоритетом, вам нужно придерживаться команды fold
, которую в конечном итоге дополняют некоторые другие утилиты POSIX. Но если вам нужны более сложные функции и вы можете позволить себе нарушить совместимость, взгляните на руководство по команде fmt
, специфичное для вашей системы. И дайте нам знать, если вы обнаружили какое-нибудь интересное или творческое использование этих опций, специфичных для конкретного поставщика!