strumento non orientato alla linea per la sostituzione di stringhe?


13

Di recente ho posto una domanda su come rimuovere il carattere di nuova riga se si verifica dopo un altro personaggio specifico.

Gli strumenti di elaborazione del testo Unix sono molto potenti, ma quasi tutti si occupano di righe di testo, il che va bene per la maggior parte del tempo in cui l'input si adatta alla memoria disponibile.

Ma cosa devo fare se desidero sostituire una sequenza di testo in un file enorme che non contiene righe?

Ad esempio sostituire <foobar>con \n<foobar>senza leggere l'input riga per riga? (poiché esiste solo una riga ed è lunga 2,5 G caratteri).


1
Sei aperto all'utilizzo perlo python?
Iruvar,

Perl sta bene. Ho appena trovato gsar( home.online.no/~tjaberg ) che proverò.
MattBianco,

Risposte:


12

La prima cosa che mi viene in mente di fronte a questo tipo di problema è cambiare il separatore di record. Nella maggior parte degli strumenti, questo è impostato di \ndefault ma può essere modificato. Per esempio:

  1. Perl

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

    Spiegazione

    • -0: imposta il separatore del record di input su un carattere dato il suo valore esadecimale . In questo caso, lo sto impostando sul >valore esadecimale 3E. Il formato generale è -0xHEX_VALUE. Questo è solo un trucco per spezzare la linea in blocchi gestibili.
    • -pe: stampa ogni riga di input dopo aver applicato lo script fornito da -e.
    • s/<foobar>/\n$&/: una semplice sostituzione. L' $&è tutto ciò che era abbinato, in questo caso <foobar>.
  2. awk

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

    Spiegazione

    • RS="<": imposta il separatore del record di input su >.
    • gsub(/foobar>/,"\n<foobar>"): sostituisce tutti i casi di foobar>con \n<foobar>. Si noti che, poiché RSè stato impostato su <, tutti <vengono rimossi dal file di input (è così che awkfunziona), quindi dobbiamo abbinare foobar>(senza a <) e sostituirlo con \n<foobar>.
    • printf "%s",$0: stampa la "linea" corrente dopo la sostituzione. $0è il record corrente in awkmodo da contenere qualunque cosa fosse prima <.

Li ho testati su un file a riga singola da 2,3 GB creato con questi comandi:

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

Sia la quantità di memoria trascurabile utilizzata awksia quella perlutilizzata.


Hai mai provato Tie::File perldoc.perl.org/Tie/File.html . Penso che sia la migliore caratteristica di Perlquando si tratta di file di grandi dimensioni.
Cuonglm

@Gnouc Ci ho giocato un po ', sì. Ma i) l'OP ha già professato una antipatia per il Perl in un'altra domanda, quindi volevo mantenerlo semplice ii) Tendo a evitare l'uso di moduli esterni a meno che non sia assolutamente necessario e iii) L'uso del modulo Tie :: File renderebbe la sintassi notevolmente inferiore chiaro.
terdon

Essere d'accordo. Una piccola nota che Tie::Fileè un modulo di base da allora v5.7.3.
cuonglm,

9

gsar (ricerca generale e sostituzione) è uno strumento molto utile proprio per questo scopo.

La maggior parte delle risposte a questa domanda utilizza strumenti basati su record e vari trucchi per adattarli al problema, come passare dal carattere di separazione record predefinito a qualcosa che si suppone si verifichi abbastanza frequentemente nell'input per non rendere ogni record troppo grande da gestire.

In molti casi questo è molto bello e persino leggibile. Faccio come i problemi che possono essere facilmente / efficacemente risolti con strumenti dovunque-disponibili quali awk, tr, sede la shell Bourne.

L'esecuzione di una ricerca binaria e la sostituzione in un enorme file arbitrario con contenuti casuali non si adatta molto bene a questi strumenti unix standard.

Alcuni di voi potrebbero pensare che questo sia un imbroglio, ma non vedo come usare lo strumento giusto per il lavoro possa essere sbagliato. In questo caso si tratta di un programma chiamato C gsarche è concesso in licenza in GPL v2 , quindi mi sorprende un po 'che non ci sia un pacchetto per questo strumento molto utile in Gentoo , Redhat e Ubuntu .

gsarusa una variante binaria dell'algoritmo di ricerca di stringhe Boyer-Moore .

L'uso è diretto:

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

dove -Fsignifica modalità "filtro", ovvero lettura e stdinscrittura su stdout. Esistono metodi per operare anche sui file. -sspecifica la stringa di ricerca e -rla sostituzione. La notazione dei due punti può essere utilizzata per specificare valori di byte arbitrari.

È supportata la modalità senza distinzione tra maiuscole e minuscole ( -i), ma non esiste alcun supporto per le espressioni regolari, poiché l'algoritmo utilizza la lunghezza della stringa di ricerca per ottimizzare la ricerca.

Lo strumento può essere utilizzato anche solo per la ricerca, un po 'come grep. gsar -bgenera l'offset di byte della stringa di ricerca corrispondente e gsar -lstampa il nome file e il numero di corrispondenze, se presenti, un po 'come combinarli grep -lcon wc.

Lo strumento è stato scritto da Tormod Tjaberg (iniziale) e Hans Peter Verne (miglioramenti).


Se fosse GPL, valuteresti di confezionarlo per una distribuzione :)
Rqomey,

1
In effetti sto pensando piuttosto seriamente a fare un ebuild gentoo per questo. Forse anche un numero di giri. Ma non ho mai creato un pacchetto .deb prima, quindi spero che qualcuno mi picchi (perché ci vorrà del tempo).
MattBianco,

Dubito che sia una grande consolazione, ma l'homebrew di OS X ha la formula per gsar.
crazysim,

5

Nel caso stretto in cui le stringhe di destinazione e di sostituzione hanno la stessa lunghezza, la mappatura della memoria può venire in soccorso. Ciò è particolarmente utile se la sostituzione deve essere eseguita sul posto. Fondamentalmente stai mappando un file nella memoria virtuale di un processo e lo spazio degli indirizzi per l'indirizzamento a 64 bit è enorme. Si noti che il file non è necessariamente mappato nella memoria fisica in una sola volta , quindi è possibile gestire file di dimensioni diverse della memoria fisica disponibile sulla macchina.

Ecco un esempio di Python che sostituisce foobarconXXXXXX

#! /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

Ci sono molti strumenti per questo:

ddè ciò che si desidera utilizzare se si desidera bloccare un file: leggere in modo affidabile solo un determinato numero di byte solo un determinato numero di volte. Gestisce in modo portabile il blocco e lo sblocco dei flussi di file:

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

Uso anche trsopra perché può gestire la conversione di qualsiasi byte ASCII in qualsiasi altro (o, in questo caso, l'eliminazione di qualsiasi byte ASCII che non sia un carattere stampabile non spaziale). È quello che ho usato in risposta alla tua altra domanda questa mattina, infatti, quando ho fatto:

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

Ce ne sono molti simili . Tale elenco dovrebbe fornire un sottoinsieme del minimo comune denominatore con il quale è possibile acquisire familiarità.

Ma, se avessi intenzione di eseguire l'elaborazione del testo su 2,5 gbs di file binario, potrei iniziare con od. Può darti uno octal dumpo uno dei tanti altri formati. Puoi specificare tutti i tipi di opzioni, ma farò solo un byte per riga in un \Cformato con escape:

I dati che riceverai odsaranno regolari a qualunque intervallo tu specifichi, come mostrerò di seguito. Ma prima: ecco una risposta alla tua domanda:

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}'

Quel po 'sopra delimita su \newline, \0null, \tabs e <spaces>preservando la \Cstringa di escape per il delimitatore. Nota le funzioni He xutilizzate: ogni volta che sedincontra un delimitatore scambia il contenuto dei suoi buffer di memoria. In questo modo sedconserva solo tutte le informazioni necessarie per delimitare in modo affidabile il file e non soccombe ai sovraccarichi del buffer - non, cioè fino a quando incontra effettivamente i suoi delimitatori. Per tutto il tempo, sedcontinuerà a elaborare il suo input e odcontinuerà a fornirlo fino a quando non si incontrerà EOF.

Così com'è, il suo output è simile al seguente:

first
\nnewline
\ttab
 spacefoobar
\0null

Quindi se voglio foobar:

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

###OUTPUT###

first
\nnewline
\ttab
 space
foobar

\0null

Ora, se si desidera utilizzare le escape C, è abbastanza facile - perché sedha già una doppia \\barra rovesciata è sfuggita a tutte le singole barre rovesciate dell'input, quindi printfeseguita da xargsnon avrà problemi a produrre l'output secondo le vostre specifiche. Ma xargs mangia le virgolette della shell, quindi dovrai raddoppiarla di nuovo:

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

###OUTPUT###

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

Ciò avrebbe potuto essere facilmente salvato in una variabile di shell e successivamente prodotto in modo identico. L'ultimo sedinserisce una \barra rovesciata prima di ogni carattere nel suo input, e questo è tutto.

Ed ecco come appare tutto prima che mai se ne sedaccorga:

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 opera su record successivi. Può utilizzare qualsiasi carattere come separatore del record (tranne il byte null su molte implementazioni). Alcune implementazioni supportano espressioni regolari arbitrarie (non corrispondenti alla stringa vuota) come separatore di record, ma ciò può essere ingombrante perché il separatore di record viene troncato dalla fine di ogni record prima di essere riposto in $0(GNU awk imposta la variabile RTsul separatore di record che è stato rimosso dalla fine del record corrente). Si noti che printtermina il suo output con il separatore del record di output ORSche è una nuova riga per impostazione predefinita e impostato indipendentemente dal separatore del record di input RS.

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

È possibile selezionare in modo efficace un carattere diverso, come il separatore di record per altri strumenti ( sort, sed, ...) scambiando a capo con quel personaggio con tr.

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

Molte utility di testo GNU supportano l'utilizzo di un byte null anziché di una nuova riga come separatore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.