VDS Хостинг для Ваших проектов

NHRPLC и SED для массовой замены текста в файлах

Октябрь 15th, 2015 Рубрики: Linux, Мини, Прикладные и прочие

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

NHREPLACE

Это консольная программа для автоматической замены текста, написанная русскоязычным разработчиком на C++. Она абсолютно бесплатная и позволяет производить обработку текста самыми разными способами. Среди её достоинств:

  • Поиск и замена как в режиме обычного текста, так и с использованием регулярных выражений.
  • Возможность указания кодировки файлов. Хотя предусмотрено и автоматическое определение, которое успешно работает.
  • Поддержка списка замен, содержащегося в файле.
  • Очень быстрая работа.
  • Постоянное развитие и усовершенствование утилиты благодаря обратной связи автора и пользователей на форуме.
  • Русская документация и простота использования.

На момент написания этого обзора актуальная версия 2.7, в которой единственным существенным недостатком является разве что невозможность использования в заменяющем тексте найденных шаблонов, задаваемых символом $. Но по просьбе одного из пользователей такая возможность планируется ко внедрению.уже 2.8 пока руки дошли опубликовать обзор, в которой реализована возможность ссылок на найденные шаблоны. До версии 2.7 также не поддерживались ESC-последовательности в заменяемом тексте, и по моей просьбе автор добавил такую возможность, за что ему конечно большая благодарность =)

Замена текста с NHRPLC

Аргументом программы является имя/имена файлов (можно и маску задать например *.txt) в которых нужно произвести замену. С помощью опций командной строки можно задавать режим работы программы и задавать искомый и заменяющий текст.

  • -spt:искомый_текст, -sre:регулярное_выражение – задают искомый текст в обычном виде или с помощью регулярного выражения. Если надо использовать ESC-последовательности (например для невводимых с клавиатуры символов), то используйте опцию -set:
  • -t:заменяющий_текст, -et:заменяющий_ESC_текст – задают искомый текст в обычном виде или с применением ESC-последовательности.
  • -list:файл_списка_замен – задаёт файл, который на каждой строке содержит пару любых вышеприведенных опций, например: -spt:"Техт" -t:"Текст"
  • -cp:кодировка – задаёт кодировку файлов

Надо обратить внимание, что искомый и заменяющий текст задаётся в обрамлении кавычками. Таким образом до версии 2.7 нельзя было искать и заменять фрагменты текста, если в нём содержались кавычки. С версии 2.7 со введением ESC-последовательностей это ограничение уже не действует, поскольку кавычки можно экранировать \".

Более подробно с синтаксисом NHRPLC, назначением и возможными значениями параметров можно ознакомиться на странице справки. Я лишь приведу 2 примера, как приблизительно выглядит использование утилиты NHRPLC из командной строки:

nhrplc -spt:"старый текст" -t:"новый текст" -cp:utf8 obrabatyvaemyi.txt
nhrplc -list:fail_zamen.txt -cp:unicode obrabatyvaemyi.txt

К сожалению нет NHRPLC для Linux-систем. Пока разработчик не имеет желания портировать на платформы Linux, хотя по поверхностным тестам может быть запущена через Wine. Но в ОС семейства линукс доступен также мощный инструмент под названием sed.

SED

SED (Stream EDitor) – потоковый редактор. Эта утилита принимает входной поток данных из файлов, указанных в качестве аргументов и делает над ним определённые заданные пользователем преобразования. В действительности их довольно таки много, включая удаление определённых последовательностей, копирование текста, обмен позициями, урезание строк и многое другое. Но в настоящий момент нас интересует поиск и замена текста.

Чтобы решить эту задачу нам надо познакомиться с некоторыми опциями

  • -f файл_скрипта – задаёт файл, который содержит команды для обработки текста (аналог опции -list: из nhrplc). В нём можно хранить список замен.
  • -i – редактирует файлы "на месте" не создавая новых с изменениями. На практике создаются ещё копии почему-то.
  • -s – рассматривать файлы отдельно вместо единого потока входных данных.
  • -r – расширенный синтаксис регулярных выражений. Методом тыка понял, что эту опция надо использовать, чтобы обратные слэши выполняли свою функцию.

Однако самым важным здесь является сам синтаксис команд для обработки текста. Для нашей цели поиска и замены некоторых кусков текста потребуется команда ’s’ имеющая следующий вид:

s/искомое_регулярное_выражение/замена_с_применением_специальных_последовательностей/gi

Модификаторы g и i в конце указывают на глобальный поиск (обычно всегда надо, иначе будет заменено только первое вхождение) и нечувствительность к регистру (если надо). С помощью \1…\9 можно обращаться к подвыражениям поискового шаблона, а & представляет само совпадение с шаблоном. Эта команда такая же, как используются в языке программирования Perl. Однако синтаксис регулярных выражений только базовый и очень убогий. Например sed не понимает что \d – это десятичная цифра, ввести HEX-код символа, если он больше 1 байта нельзя ни в формате \uHHHH ни \x{HHHH}. Но удивительным образом не ASCII символы, если их использовать в регулярных выражениях воспринимаются корректно, т.е. можно спокойно писать кириллицей. Но юникод-символы, которые нельзя ввести с клавиатуры использовать не удастся (разве что копировать по одному с таблицы символов). Естественно, учитывая, что работа ведётся с регулярными выражениями, то для поиска обычного текста придётся экранировать символы имеющие специальные значения. Кто не знаком с регулярными выражениями, то проще говоря, если в искомом тексте есть символы:

. * ? [ ] ( ) | \ ^ $ { }

то перед ними надо ставить обратный слэш, а вместо пробела ставить \s, вместо перевода строки \n. Например, если ищем текст: 1000$ (тысяча). то он должен выглядеть вот так: 1000\$\s\(тысяча\)\.

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

Замена текста в файлах с sed

Всё бы ничего, но в качестве аргументов sed надо передать названия конкретных файлов, в которых будет производиться замена. Если их очень много, то перечислять вручную – дело неприятное. Однако если эти файлы имеют некий признак, по которому их можно выделить из остальных, то можно (и нужно) это сделать и передать их применив конвейер. Кто не в курсе, конвейер – это когда результат работы одной команды (в данном случае которая отберёт все нужные файлы) передаётся на вход другой (т.е. sed). Для нашего случая скорее всего будет достаточно команды find, предназначенной для поиска файлов и xarg, которая список найденных файлов передаст в качестве аргументов sed. Если вы с компьютером не на ты (а с ОС семейства Linux это обыденное явление) или блондинка, то чувствую, что ничего не поняли, поэтому несколько примеров, как это выглядит на практике.

sed s/Nick/John/g report.txt > report_new.txt
Эта команда указывает заменить все (потому что в конце модификатор g) вхождения Nick на John (с учётом регистра, потому что нет модификатора i) в файле report.txt и результат перенаправить (оператор >) в файл report_new.txt а не просто вывести на консоль.
sed -i -r s/Nick\./John\./gi report.txt
Эта команда указывает заменить все вхождения Nick. на John. (без учёта регистра, потому в конце модификатор i) в файле report.txt и результат сохранить в том же файле (опция -i). Обратите внимание на экранирование точки \.
find . -name *.txt -type f -print0 | xargs -0 sed -i -s -f list.rep
Этот конвейер команд указывает позволяет заменить содержимое в файлах текущего каталога с расширением txt (опция -name команды find) согласно правилам находящимся в файле list.rep и результат сохранить в тех же файлах (перезаписать с изменённым текстом). Этот последний пример иллюстрирует действительно массовую замену во множественных файлах.

Однако sed имеет один существенный недостаток – это невозможность задания кодировки в которой ведётся работа. На практических экспериментах я понял, что ведётся корректная обработка ANSI и UTF8 текстов если файл, заданный опцией -f и файлы, в которых производится поиск имеют одну и ту же кодировку. По другим источникам в сети – принимается во внимание текущая кодировка консоли (если в ней задавать шаблоны поиска-замены). А вот с кодировкой UTF16 не умеет работать в принципе, более того исходные файлы портит до неузнаваемости. Можно конечно попытаться работать в бинарном режиме с опцией -b, и записывать каждый символ для поиска и замены вместе с \x00, но это уже маразм какой-то, к тому же в таком случае могут ожидать сюрпризы с переводом строки. Единственным выходом, который я нарыл в сети, является использование в комбинации с утилитой iconv для приведения предварительно к кодировке UTF8. Выглядит это примерно так:

iconv -f utf-16 -t utf-8 < report.txt | sed s/Nick/John/ | 
iconv -f utf-8 -t utf-16 > report_new.txt

Но в таком случае обработка ведётся всего лишь одного файла. Думаю, что без скрипта тут не обойтись, если надо обрабатывать действительно много файлов в кодировке UTF16. Интересно отметить, что версия sed для Linux с UTF16/ANSI (символы с кодами больше 127) не умеет работать, т.е. если список замен в такой кодировке, то выдаётся ошибка типа команда s незаконченная или неопознанная команда. Что касается регулярных выражений, то линукс-версия также не поддерживает указания юникод символов через их коды. Поэтому учитывая все эти особенности и недостатки, пожалуй самим надёжным и оптимальным вариантом будет работа в три этапа: 1 – конвертация всех обрабатываемых файлов в кодировку UTF8 с помощью iconv, накопал в сети такой мини-скрипт (работает, сам проверял):

find . -name '*.txt' | while read i; do iconv -f UTF-16 -t UTF-8 "$i" >tmp; mv tmp "$i"; done

2 – Потом их обработка с помощью sed. 3 – И при необходимости повторная перекодировка с помощью iconv в обратном порядке. Обратите внимание как прописана кодировка в опции -f! Нужно именно так, чтобы на выходе получить UTF-8 без BOM, если же указать UTF16-2LE и т.п. то на выходе будет UTF-8 с BOM. Это я на практике наковырял.

Вот на этом пока всё. Надеюсь материалы и мои исследования, изложенные в этом посте, пригодятся вам, а утилиты NHREPLACE и SED помогут вам решать задачи массовой и автоматической замены текста. А малоопытным пользователям хочу лишь сказать: пускай вас не смущает то, что они консольные. Главное функционал!

NHREPLACE скачать последнюю версию
NHREPLACE справка
SED скачать для Windows с sourceforge (нужны также библиотеки libiconv2.dll, libintl3.dll, regex2.dll в архиве с -dep)
Понравился пост? Поделись с другими!
Теги: , , ,

Есть 12 коммент. к “NHRPLC и SED для массовой замены текста в файлах”

  1. Серб и молод
    Январь 9th, 2016 at 19:13
    1

    Спасибо, не знал про NHUtils!

    • Январь 10th, 2016 at 21:22
      2

      Пожалуйста. Приятно узнать, что пост был информативным =)

  2. Дмитрий
    Октябрь 30th, 2016 at 05:36
    3

    Добрый день,
    подскажите, я правильно понимаю, что это выражение, через Терминал позволит произвести замены в файле report.txt
    sed -i -r s/Nick\./John\./gi report.txt

    при этом, если задать папку и маску файлов, например, *.docx, то такие замены можно сделать потоком?

    • Ноябрь 3rd, 2016 at 20:27
      4

      @Дмитрий, да, только файлы должны быть текстовыми. DOCX это бинарный файл,. поэтому само понятие замены там не применимо как мы понимаем. Слова, которые видны при открытии в офисных программах так нельзя заменить.

  3. Октябрь 30th, 2016 at 15:29
    5

    Здравствуйте! Не очень понятно с этой строкой: find . -name *.txt -type f -print0 | xargs -0 sed -i -s -f list.rep
    Не понятно где указание на каталог файлов (путь к каталогу) которые (файлы) необходимо изменить.

    • Октябрь 30th, 2016 at 20:13
      6

      Здравствуйте, Игорь. Файлы, которые подлежат замене ищет команда find. Каталог задаётся первым аргументом:
      find /catalog -name *.txt
      Подробнее в документации по find.

  4. Ноябрь 1st, 2016 at 19:48
    7

    Спасибо за ответ! Простите уж за мою настойчивость и неопытность. А по какому правилу пишется файл list.rep?
    В одной строке что надо найти, а в другой на что надо заменить?

    • Ноябрь 3rd, 2016 at 20:21
      8

      Нет, на каждой строке своя команда:
      s/Odin/Dwa/g
      s/Tri/Cetyre/g

      и так сколько нужно. В команде задаётся что на что менять.

  5. Ноябрь 5th, 2016 at 17:48
    9

    Спасибо за Ваш ответ! Я правильно понимаю, что файл list.rep должен лежать в том же месте, куда указывает команда find ? К примеру: find /home/igor/kontakti-html -name *.html -type f -print0 | xargs -0 -i -s -f list.rept
    Просто терминал указывает в таком случае: sed: невозможно открыть файл list.rep Нет такого файла или каталога. Указание полного пути к list.rep (/home/igor/kontakti-html/list.rep) дают тоже отрицательный результат. Подскажите пожалуйста!

    • Ноябрь 6th, 2016 at 20:18
      10

      Пожалуйста. Файл list.rep должен находиться в текущем рабочем каталоге. Указание полного пути должно работать. Возможно Вы сделали опечатку где-то. Также права доступа проверьте.

  6. Ноябрь 15th, 2016 at 13:15
    11

    Спасибо что ещё с нами :)
    Итак, без указания полного пути к файлу list.rep терминал сообщает: sed: невозможно открыть файл list.rep: Нет такого файла или каталога. При указании полного пути терминал сообщает: sed: отсутствуют входные файлы. Запуск с правами root ничего не меняют. Запись в файле (находящаяся в текущем рабочем каталоге, где и файлы для замены) list.rep выглядит:
    s/orenburg-banner.gif/advert-banner.gif/g
    Мда, задача получается….

    • Ноябрь 17th, 2016 at 20:55
      12

      По видимому у Вас ошибка в конвейере команд где-то. Проблема не в sed, не в содержимом файла, а именно в построении конвейера.

Написать комментарий

   b2bbonbone