Поиск по сайту:

Форматирование текста в терминале 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).

Определение в начале этого раздела в основном описывает, что такое логическая линия. Однако в большинстве случаев эту «произвольно длинную» логическую строку приходится отправлять на физическое устройство, такое как экран или принтер, где доступное пространство ограничено. Отображение коротких логических линий на устройстве, имеющем более крупные физические линии, не является проблемой. Просто справа от текста осталось неиспользуемое место. Но что, если вы попытаетесь отобразить строку текста, размер которой превышает доступное место на устройстве? На самом деле есть два решения, каждое из которых имеет свои недостатки:

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

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

Такое автоматическое поведение весьма полезно, но все же бывают случаи, когда вам нужно разбить длинные строки в заданной позиции, независимо от физического размера устройства. Например, это может быть полезно, поскольку вы хотите, чтобы разрывы строк располагались в одном и том же месте как на экране, так и на принтере. Или потому, что вы хотите, чтобы ваш текст использовался в приложении, которое не выполняет перенос строк (например, если вы программно встраиваете текст в файл 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 ''

ХОРОШО? Вы видели это на этот раз? Разложим последовательность событий:

  1. Первая строка символов отображается «обычно» до второй открывающей квадратной скобки. Из-за флага -n команда echo не отправляет символ новой строки, поэтому курсор остается включенным та же линия.

  2. Первый сон.

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

  4. Второй сон.

  5. Отображается закрывающая квадратная скобка, заменяющая открывающую.

  6. Третий сон.

  7. При отсутствии опции -n последняя команда echo наконец отправляет символ новой строки и курсор перемещается на следующую строку, где будет отображаться приглашение оболочки.

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

sh$ echo -n 'hello'; sleep 1; echo -e '\rgood bye'
good bye

Я почти уверен, что вы уже видели некоторые утилиты командной строки, такие как Curl и wget, отображающие индикатор выполнения. Они творят чудеса, используя комбинацию \b и/или \r.

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

Текущий счетчик ширины линии должен быть уменьшен на единицу, хотя счетчик никогда не должен становиться отрицательным. Утилита сгиба не должна вставлять <новую строку> непосредственно перед или после любого . <возврат каретки> Текущий счетчик ширины строки должен быть установлен в ноль. Утилита сгиба не должна вставлять <новую строку> непосредственно перед или после любого <возврата каретки>. <tab> Каждый встреченный элемент <tab> перемещает указатель позиции столбца на следующую позицию табуляции. Позиции табуляции должны быть в каждой позиции столбца n, так что n по модулю 8 равно 1. _

Все эти специальные процедуры отключаются при использовании опции -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, специфичное для вашей системы. И дайте нам знать, если вы обнаружили какое-нибудь интересное или творческое использование этих опций, специфичных для конкретного поставщика!

Статьи по данной тематике: