нелінійний інструмент для заміни рядків?


13

Нещодавно я задав питання про те, як видалити символ нового рядка, якщо він виникає після іншого конкретного символу.

Інструменти для обробки тексту Unix є дуже потужними, але майже всі вони мають справу з рядками тексту, що добре протягом більшої частини часу, коли вхід вкладається у наявну пам'ять.

Але що робити, якщо я хочу замінити послідовність тексту у величезному файлі, який не містить нових рядків?

Наприклад, замінити <foobar>на, \n<foobar>не читаючи вхідний рядок? (оскільки є лише один рядок і він має 2,5G символів).


1
Чи готові ви до використання perlчи python?
iruvar

Perl чудово. Я щойно знайшов gsar( home.online.no/~tjaberg ), що спробую.
MattBianco

Відповіді:


12

Перше, що трапляється мені, стикаючись з таким типом проблем, - це змінити роздільник записів. У більшості інструментів це налаштування \nза замовчуванням, але це можна змінити. Наприклад:

  1. Perl

    perl -0x3E -pe 's/<foobar>/\n$&/' file
    

    Пояснення

    • -0: це встановлює роздільник запису вхідного символу з урахуванням його шістнадцяткового значення . У цьому випадку я встановлюю його, >чиє шестигранне значення 3E. Загальний формат є -0xHEX_VALUE. Це лише хитрість розбити лінію на керовані шматки.
    • -pe: надрукувати кожен рядок введення після застосування сценарію, заданого -e.
    • s/<foobar>/\n$&/: проста заміна. У $&цьому випадку все, що було відповідним <foobar>.
  2. awk

    awk '{gsub(/foobar>/,"\n<foobar>");printf "%s",$0};' RS="<" file
    

    Пояснення

    • RS="<": встановіть роздільник запису вхідного сигналу на >.
    • gsub(/foobar>/,"\n<foobar>"): Замінити всі випадки foobar>з \n<foobar>. Зауважте, що оскільки RSбуло встановлено значення <, всі <видаляються з вхідного файлу (саме так awkпрацює), тому нам потрібно відповідати foobar>(без а <) та замінити на \n<foobar>.
    • printf "%s",$0: надрукувати поточний "рядок" після заміни. $0є поточним записом, awkтому він буде містити все, що було раніше <.

Я перевірив їх на однорядковому файлі 2,3 Гб, створеному за допомогою цих команд:

for i in {1..900000}; do printf "blah blah <foobar>blah blah"; done > file
for i in {1..100}; do cat file >> file1; done
mv file1 file

Як awkі perlвживаний мізерний об'єм пам'яті.


Ви коли-небудь пробували Tie::File perldoc.perl.org/Tie/File.html . Я думаю, що це найкращі риси Perlроботи з величезними файлами.
cuonglm

@Gnouc Я з цим трохи пограв, так. Але я) ОП вже висловив неприязнь до Perl в іншому питанні, тому я хотів би зробити його простим. Ii) Я схильний уникати використання зовнішніх модулів, якщо це абсолютно не потрібно, і iii) Використання модуля Tie :: File зробить синтаксис значно меншим ясний.
тердон

Погодьтеся. Маленька примітка, яка Tie::Fileє основним модулем з тих пір v5.7.3.
cuonglm

9

gsar (загальний пошук та заміна) - дуже корисний інструмент саме для цієї мети.

Більшість відповідей на це запитання використовують інструменти, засновані на записі, та різні хитрощі, щоб змусити їх адаптуватися до проблеми, наприклад, перемикання символу сепаратора запису за замовчуванням на щось, що, як вважається, трапляється досить часто на вході, щоб не зробити кожен запис занадто великим для обробки.

У багатьох випадках це дуже добре і навіть читабельно. Я як проблеми , які можуть бути легко / ефективно вирішено з допомогою усюди доступними інструментами , такими як awk, tr, sedі оболонка Борна.

Виконання двійкового пошуку та заміни у довільному величезному файлі з випадковим вмістом не дуже добре підходить для цих стандартних інструментів Unix.

Дехто з вас може подумати, що це обман, але я не бачу, як використання правильного інструменту для роботи може бути неправильним. У цьому випадку це програма C, яка називається gsarліцензованою під GPL v2 , тому мене це дуже дивує, що немає ні пакета для цього дуже корисного інструменту, ні в gentoo , redhat , ні ubuntu .

gsarвикористовує двійковий варіант алгоритму пошуку рядків Боєра-Мура .

Використання прямо:

gsar -F '-s<foobar>' '-r:x0A<foobar>'

де -Fозначає режим "фільтр", тобто читання stdinв stdout. Існують також методи роботи з файлами. -sвказує рядок пошуку та -rзаміну. Позначення двокрапки можна використовувати для визначення довільних байтових значень.

Режим, що не враховує регістр, підтримується ( -i), але немає підтримки регулярних виразів, оскільки алгоритм використовує довжину рядка пошуку для оптимізації пошуку.

Інструмент також може бути використаний просто для пошуку, трохи схоже grep. gsar -bвиводить байт зміщення узгодженої рядки пошуку, і gsar -lдрукує ім'я файлу і кількість збігів , якщо такі є, трохи як об'єднання grep -lз wc.

Інструмент написали Тормод Тяберг (початковий) та Ганс Пітер Верн (удосконалення).


Якщо це GPL, ви б
роздумали упакувати

1
Насправді я досить серйозно замислююся над тим, щоб зробити гентобудівництво для цього. Можливо, і об / хв. Але я ніколи раніше не створював .deb пакет, тому сподіваюся, що хтось мене обіграє (бо це займе у мене певний час).
MattBianco

Я сумніваюся, це дуже втішає, але домашня мова OS X має формулу gsar.
crazysim

5

У вузькому випадку, коли цільові та замінні рядки мають однакову довжину, на допомогу може прийти картування пам'яті . Це особливо корисно, якщо заміну потрібно проводити на місці. Ви в основному відображаєте файл у віртуальній пам'яті процесу, а адресний простір для 64-розрядних адресацій величезний. Зауважте, що файл не обов'язково відображається у фізичній пам'яті відразу , тому файли, що мають кілька разів більший розмір фізичної пам'яті, наявної на апараті, можуть бути оброблені.

Ось приклад Python, який замінює foobarзXXXXXX

#! /usr/bin/python
import mmap
import contextlib   
with open('test.file', 'r+') as f:
 with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE)) as m:
   pos = 0
   pos = m.find('foobar', pos)
   while pos > 0:
    m[pos: pos+len('XXXXXX')] = 'XXXXXX'
    pos = m.find('foobar', pos)

4

Для цього існує багато інструментів:

ddце те, що ви хочете використовувати, якщо ви хочете заблокувати файл - надійно прочитайте лише певну кількість байтів лише певну кількість разів. Він портативно обробляє блокування та розблокування потоків файлів:

tr -dc '[:graph:]' </dev/urandom | dd bs=32 count=1 cbs=8 conv=unblock,sync 2>/dev/null

###OUTPUT###

UI(#Q5\e BKX2?A:Z RAxGm:qv t!;/v!)N

Я також використовую trвище, тому що він може обробляти перетворення будь-якого байта ASCII в будь-який інший (або, у цьому випадку, видалення будь-якого байта ASCII, який не є символом для друку, який не є пробілом). Це те, що я використав, відповідаючи на ваше інше питання сьогодні вранці, коли я це робив:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n' 

Є багато подібних . Цей список повинен містити найменший підмножина спільного знаменника, з яким ви можете ознайомитися.

Але, якби я збирався робити обробку тексту на 2,5 гб двійкового файлу, я міг би почати з цього od. Він може надати вам octal dumpбудь-який або декілька інших форматів. Ви можете вказати всі види параметрів - але я буду робити лише один байт на рядок у \Cформаті, що уникнув:

Дані, які ви отримаєте, odбудуть регулярними в будь-який інтервал, який ви вказали - як я показую нижче. Але спочатку - ось відповідь на ваше запитання:

printf 'first\nnewline\ttab spacefoobar\0null' |
od -A n -t c -v -w1 |
sed 's/^ \{1,3\}//;s/\\$/&&/;/ /bd
     /\\[0nt]/!{H;$!d};{:d
    x;s/\n//g}'

Це трохи вище \nвідмежування на ewlines, \0nulls, \tabs та <spaces>, зберігаючи пропущений \Cрядок для роздільника. Зауважте, що Hі xвикористовувані функції - кожен раз, коли sedвиникає роздільник, він міняє вміст своїх буферів пам'яті. Таким чином sedзберігається лише стільки інформації, скільки вона повинна надійно розмежувати файл, і не піддається перекриттю буфера - ні, тобто до тих пір, поки воно насправді стикається зі своїми роздільниками. До тих пір, поки це станеться, він sedбуде продовжувати обробляти свої дані та odпродовжуватиме надавати їх, поки не зіткнеться EOF.

Як результат, його вихід виглядає приблизно так:

first
\nnewline
\ttab
 spacefoobar
\0null

Тож якщо я хочу foobar:

printf ... | od ... | sed ... | 
sed 's/foobar/\
&\
/g'

###OUTPUT###

first
\nnewline
\ttab
 space
foobar

\0null

Тепер, якщо ви хочете скористатися втечею, Cце досить просто - адже sedвже подвійний \\зворотний printfнахил уникнув усіх своїх вхідних косої риски, тож у виконанні xargsне виникне жодних проблем, пов'язаних із виробництвом результатів у вашій специфікації. Але xargs їсть цитати оболонок, тож вам потрібно буде ще раз подвоїти це:

printf 'nl\ntab\tspace foobarfoobar\0null' |
PIPELINE |
sed 's/./\\&/g' | 
xargs printf %b | 
cat -A

###OUTPUT###

nl$
tab^Ispace $
foobar$
$
foobar$
^@null%

Це можна було б легко зберегти до змінної оболонки та вивести пізніше однаково. Останній sedвставляє \зворотну косу рису перед кожним символом у своєму введенні, і це все.

А ось як це все виглядає, перш ніж коли-небудь sedздобути це:

printf 'nl\ntab\tspace foobarfoobar\0null' |
od -A n -t c -v -w1

   n
   l
  \n
   t
   a
   b
  \t
   s
   p
   a
   c
   e

   f
   o
   o
   b
   a
   r
   f
   o
   o
   b
   a
   r
  \0
   n
   u
   l
   l

2

Awk працює на послідовних записах. Він може використовувати будь-який символ як роздільник записів (крім нульового байта для багатьох реалізацій). Деякі реалізації підтримують довільні регулярні вирази (не відповідають порожній рядку) як розділювач записів, але це може бути непростим, оскільки роздільник записів обрізається з кінця кожного запису до його зберігання $0(GNU awk встановлює змінну RTна роздільник записів що було знято з кінця поточного запису). Зауважте, що printзавершується його вихід розділювачем записів на виході, ORSякий є новим рядком за замовчуванням і встановлюється незалежно від роздільника запису вхідних даних RS.

awk -v RS=, 'NR==1 {printf "input up to the first comma: %s\n", $0}'

Ви можете ефективно вибрати інший символ як роздільник записів для інших інструментів ( sort, sed, ...) шляхом заміни нового рядка з цим символом з tr.

tr '\n,' ',\n' |
sed 's/foo/bar/' |
sort |
tr '\n,' ',\n'

Багато текстових утиліт GNU підтримують використання нульового байта замість нового рядка як роздільника.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.