sed la cancellazione della riga sul posto sul filesystem completo?


11

A causa di un bug dell'applicazione non ancora diagnosticato, ho diverse centinaia di server con un disco completo. C'è un file che è stato riempito con righe duplicate, non un file di registro, ma un file di ambiente utente con definizioni variabili (quindi non posso semplicemente eliminare il file).

Ho scritto un semplice sedcomando per verificare la presenza di righe erroneamente aggiunte ed eliminarle, e l'ho testato su una copia locale del file. Ha funzionato come previsto.

Tuttavia, quando l'ho provato sul server con l'intero disco, ho ottenuto approssimativamente il seguente errore (è dalla memoria, non dalla copia e incolla):

sed: couldn't flush /path/to/file/sed8923ABC: No space left on deviceServerHostname

Certo, so che non c'è più spazio. Ecco perché sto cercando di eliminare cose! (Il sedcomando che sto usando ridurrà un file di oltre 4000 righe a circa 90 righe.)

Il mio sedcomando è giustosed -i '/myregex/d' /path/to/file/filename

C'è un modo per applicare questo comando nonostante l'intero disco?

(Deve essere automatizzato, poiché devo applicarlo a diverse centinaia di server come soluzione rapida.)

(Ovviamente il bug dell'applicazione deve essere diagnosticato, ma nel frattempo i server non funzionano correttamente ....)


Aggiornamento: la situazione che ho affrontato è stata risolta eliminando qualcos'altro che ho scoperto di poter eliminare, ma mi piacerebbe comunque la risposta a questa domanda, che sarebbe utile in futuro e per le altre persone.

/tmpè un non-andare; è sullo stesso filesystem.

Prima di liberare spazio su disco, ho eseguito il test e ho scoperto che potevo eliminare le righe viaprendo il file ed eseguendolo, :g/myregex/dquindi salvando correttamente le modifiche con :wq. Sembra che dovrebbe essere possibile automatizzare questo, senza ricorrere a un filesystem separato per contenere un file temporaneo .... (?)



1
sed -icrea una copia temporanea su cui operare. Ho il sospetto che edsarebbe meglio per questo, anche se non ho abbastanza familiarità per vietare una soluzione effettiva
Eric Renouf,

2
Con edte eseguiresti: printf %s\\n g/myregex/d w q | ed -s infilema tieni presente che alcune implementazioni usano anche file temporanei proprio come sed(potresti provare busybox ed - afaik non crea un file temporaneo)
don_crissti

1
@Wildcard - non affidabile w / echo. usare printf. e sedaggiungi un po 'di carattere che lasci cadere nell'ultima riga in modo da evitare di perdere spazi vuoti finali. inoltre, la shell deve essere in grado di gestire l'intero file in una singola riga di comando. questo è il tuo rischio: prova prima. bashè particolarmente brutto (penso che debba fare w / stack space?) e può farti male in qualsiasi momento. i due sed"si" raccomanderebbero almeno di utilizzare il buffer del kernel del kernel con buoni risultati tra loro, ma il metodo è abbastanza simile. la tua cosa di comando secondaria troncerà anche filese il sed w / in ha successo.
Mikeserv,

1
@Wildcard - prova sed '/regex/!H;$!d;x' <file|{ read v && cat >file;}e se funziona leggi il resto della mia risposta. '
Mikeserv,

Risposte:


10

L' -iopzione non sovrascrive realmente il file originale. Crea un nuovo file con l'output, quindi lo rinomina con il nome file originale. Dato che non hai spazio sul filesystem per questo nuovo file, fallisce.

Dovrai farlo tu stesso nel tuo script, ma crea il nuovo file su un file system diverso.

Inoltre, se stai semplicemente eliminando le righe che corrispondono a una regexp, puoi usare grepinvece di sed.

grep -v 'myregex' /path/to/filename > /tmp/filename && mv /tmp/filename /path/to/filename

In generale, raramente è possibile che i programmi utilizzino lo stesso file di input e output - non appena inizia a scrivere sul file, la parte del programma che legge dal file non vedrà più il contenuto originale. Quindi deve prima copiare il file originale da qualche parte, oppure scrivere in un nuovo file e rinominarlo al termine.

Se non si desidera utilizzare un file temporaneo, è possibile provare a memorizzare nella cache il contenuto del file:

file=$(< /path/to/filename)
echo "$file" | grep -v 'myregex' > /path/to/filename

1
Ha conservato permessi, proprietà e timestamp? Forse rsync -a --no-owner --no-group --remove-source-files "$backupfile" "$destination"da qui
Hastur il

@Hastur - intendi dire che sed -ipreserva quella roba?
Mikeserv,

2
@Hastur sed -inon conserva nessuna di queste cose. L'ho appena provato con un file che non possiedo, ma che si trova in una directory di mia proprietà e mi consente di sostituire il file. Il sostituto appartiene a me, non al proprietario originale.
Barmar il

1
@ RalphRönnquist Per essere sicuri, dovresti farlo in due passaggi:var=$(< FILE); echo "$FILE" | grep '^"' > FILE
Barmar,

1
@Barmar - non funziona - non sai nemmeno di aver aperto correttamente l'input. Il molto minimo che si possa fare è v=$(<file)&& printf %s\\n "$v" >file, ma non hanno nemmeno usare &&. Il richiedente sta parlando di eseguirlo in uno script, automatizzando la sovrascrittura di un file con una parte di se stesso. dovresti almeno convalidare che puoi aprire con successo input e output. Inoltre, la shell potrebbe esplodere.
Mikeserv,

4

sedFunziona così . Se utilizzato con -i(modifica sul posto) sedcrea un file temporaneo con i nuovi contenuti del file elaborato. Al termine sed, sostituisce il file di lavoro corrente con quello temporaneo. L'utilità non modifica il file sul posto . Questo è esattamente il comportamento di ogni editore.

È come eseguire la seguente attività in una shell:

sed 'whatever' file >tmp_file
mv tmp_file file

A questo punto sed, tenta di scaricare i dati bufferizzati nel file indicato nel messaggio di errore con la fflush()chiamata di sistema:

Per i flussi di output, fflush()impone una scrittura di tutti i dati bufferizzati nello spazio utente per l'output dato o il flusso di aggiornamento tramite la funzione di scrittura sottostante del flusso.


Per il tuo problema, vedo una soluzione nel montare un filesystem separato (ad esempio a tmpfs, se hai memoria sufficiente o un dispositivo di archiviazione esterno) e spostare alcuni file lì, elaborarli lì e spostarli indietro.


3

Da quando ho pubblicato questa domanda ho imparato che exè un programma conforme a POSIX. È quasi universalmente collegato a vim, ma in entrambi i casi, il seguente è (penso) un punto chiave exin relazione ai filesystem (tratto dalla specifica POSIX):

Questa sezione utilizza il termine modifica buffer per descrivere il testo di lavoro corrente. Nessuna specifica implementazione è implicita da questo termine. Tutte le modifiche apportate alla modifica vengono eseguite sul buffer di modifica e nessuna modifica deve influire su alcun file fino a quando un comando dell'editor non scrive il file.

"... interesserà qualsiasi file ..." Credo che inserire qualcosa nel filesystem (per niente, anche un file temporaneo) significherebbe "influenzare qualsiasi file". Può essere?*

Attento studio delle specifiche POSIX perex indicare alcuni "gotchas" circa il suo uso portatile previsto rispetto agli usi comuni con script di extrovati online (che sono disseminati di vimcomandi specifici).

  1. L'implementazione +cmdè facoltativa secondo POSIX.
  2. Consentire più -copzioni è anche facoltativo.
  3. Il comando globale :g"mangia" tutto fino alla nuova riga non salvata (e quindi lo esegue dopo ogni corrispondenza trovata per la regex piuttosto che una volta alla fine). Quindi -c 'g/regex/d | x'elimina solo un'istanza e quindi esce dal file.

Quindi, secondo quanto ho studiato, il metodo conforme a POSIX per la modifica sul posto di un file su un file system completo per eliminare tutte le righe corrispondenti a una regex specifica, è:

ex -sc 'g/myregex/d
x' /path/to/file/filename

Questo dovrebbe funzionare a condizione che tu abbia memoria sufficiente per caricare il file in un buffer.

* Se trovi qualcosa che indica diversamente, per favore, menzionalo nei commenti.


2
ma ex scrive su tmpfiles ... sempre. ha specificato di scrivere periodicamente i suoi buffer su disco. ci sono anche comandi specifici per localizzare i buffer di file tmp sul disco.
Mikeserv,

@Wildcard Grazie per la condivisione, mi sono ricollegato a un post simile in SO . Presumo ex +g/match/d -scx filesia conforme anche a POSIX?
Kenorb,

@kenorb, non del tutto, secondo la mia lettura delle specifiche — vedi il mio punto 1 nella risposta sopra. La citazione esatta da POSIX è "L'utilità ex deve essere conforme alle Linee guida per la sintassi dell'utilità XBD, ad eccezione dell'uso non specificato di '-' e che '+' può essere riconosciuto come delimitatore di opzioni e '-'."
Wildcard

1
Non posso provarlo, se non facendo appello al buon senso, ma credo che tu stia leggendo più in quella dichiarazione dalle specifiche di quanto non sia realmente lì. Suggerisco che l'interpretazione più sicura è che nessuna modifica al buffer di modifica influirà su qualsiasi file esistente prima dell'inizio della sessione di modifica o che l'utente abbia nominato. Vedi anche i miei commenti sulla mia risposta.
G-Man dice "Ripristina Monica" il

@ G-Man, in realtà penso che tu abbia ragione; la mia interpretazione iniziale era probabilmente un pio desiderio. Tuttavia, poiché la modifica del file ha vi funzionato su un file system completo, credo che nella maggior parte dei casi funzionerebbe anche con ex- anche se forse non per un file enorme. sed -inon funziona su un file system completo indipendentemente dalla dimensione del file.
Wildcard

2

Usa la pipa, Luke!

Leggi il file | filtro | rispondere

sed 's/PATTERN//' BIGFILE | dd of=BIGFILE conv=notrunc

in questo caso sednon crea un nuovo file e invia semplicemente l'output di piping a ddcui si apre lo stesso file . Naturalmente si può usare grepin un caso particolare

grep -v 'PATTERN' BIGFILE | dd of=BIGFILE conv=notrunc

quindi tronca il rimanente.

dd if=/dev/null of=BIGFILE seek=1 bs=BYTES_OF_SED_OUTPUT

1
Hai notato la parte "completa del filesystem" della domanda?
Wildcard il

1
@Wildcard, sedusa sempre i file temporanei? grepcomunque no
Leben Gleben il

Questa sembra un'alternativa al spongecomando. Sì, sedcon -isempre crea i file lilke "seduyUdmw" con 000 diritti.
Pablo A

1

Come notato in altre risposte, sed -ifunziona copiando il file in un nuovo file nella stessa directory , apportando modifiche nel processo e quindi spostando il nuovo file sull'originale. Ecco perché non funziona.  ed(l'editor di riga originale) funziona in modo un po 'simile, ma, l'ultima volta che ho controllato, lo utilizza /tmpper il file scratch. Se il tuo /tmpè su un filesystem diverso da quello pieno, edpuò fare il lavoro per te.

Prova questo (al tuo prompt della shell interattiva):

$ ed / percorso / in / file / nomefile
P
g / myregex / d
w
q

La P(che è una P maiuscola ) non è strettamente necessaria. Attiva il prompt; senza di essa, stai lavorando nel buio e alcune persone lo trovano sconcertante. L' we qsono w rite e q uit.

edè noto per la diagnostica criptica. Se in qualsiasi momento visualizza qualcosa di diverso dal prompt (che è *) o qualcosa che è chiaramente una conferma del corretto funzionamento ( specialmente se contiene un ?), non scrivere il file (con w). Esci da ( q). Se non ti lascia uscire, prova a qripetere.

Se la tua /tmpdirectory si trova sul filesystem pieno (o anche se il suo filesystem è pieno), prova a trovare spazio da qualche parte. il caos menzionato durante il montaggio di un tmpfs o di un dispositivo di archiviazione esterno (ad esempio un'unità flash); ma, se hai più filesystem e non sono tutti pieni, puoi semplicemente usare uno degli altri esistenti. caos suggerisce di copiare i file nell'altro filesystem, modificandoli lì (con sed) e poi copiandoli di nuovo. A questo punto, questa potrebbe essere la soluzione più semplice. Ma un'alternativa sarebbe quella di creare una directory scrivibile su un filesystem che abbia dello spazio libero, impostare la variabile d'ambiente TMPDIRin modo che punti a quella directory, e quindi eseguire ed. (Rivelazione: non sono sicuro che funzionerà, ma non può far male.)

Una volta che hai iniziato a edlavorare, puoi automatizzare ciò facendo

ed il nome del file << EOF
g / myregex / d
w
q
EOF

in una sceneggiatura. Oppure , come suggerito da don_crissti.printf '%s\n' 'g/myregex/d' w q | ed -s filename


Hmmm. Si può fare la stessa cosa (con edo con ex) in modo tale che venga utilizzata la memoria anziché un file system separato? Questo è quello che stavo davvero cercando (e il motivo per cui non ho accettato una risposta.)
Wildcard

Hmm. Questo può essere più complicato di quanto pensassi. Ho studiato edampiamente la fonte di molti anni fa. C'erano ancora cose come i computer a 16 bit, in cui i processi erano limitati a uno spazio di indirizzi di 64 KB (!), Quindi l'idea di un editor che leggeva l'intero file in memoria non era un inizio. Da allora, ovviamente, la memoria è diventata più grande, ma anche dischi e file. Poiché i dischi sono così grandi, le persone non sentono il bisogno di affrontare la contingenza di /tmprimanere senza spazio. Ho appena dato una rapida occhiata al codice sorgente di una versione recente di ed, e sembra ancora ... (continua)
G-Man dice 'Reinstate Monica'

(Continua) ... per implementare il "modifica buffer" come file temporaneo, incondizionatamente - e non riesco a trovare alcuna indicazione che qualsiasi versione di ed(o exo vi) offra un'opzione per mantenere il buffer in memoria.  D'altra parte, Modifica del testo con ed e vi - Capitolo 11: Elaborazione del testo - Parte II: Esplorazione di Red Hat Linux - Segreti professionali di Red Hat Linux 9 - I sistemi Linux affermano che edil buffer di modifica risiede nella memoria, ... (continua )
G-Man dice "Ripristina Monica" il

(Continua) ... e UNIX Elaborazione e composizione di documenti di Balasubramaniam Srinivasan dice la stessa cosa vi(che è lo stesso programma di ex). Credo che stiano solo usando una formulazione imprecisa e imprecisa - ma, se è su Internet (o in stampa), deve essere vero, giusto? Paghi i tuoi soldi e fai la tua scelta.
G-Man dice "Ripristina Monica" il

Ma comunque, ho aggiunto una nuova risposta.
G-Man dice "Ripristina Monica" il

1

È possibile troncare il file abbastanza facilmente se è possibile ottenere il conteggio dei byte per l'offset e le linee si verificano da un punto iniziale alla fine.

o=$(sed -ne'/regex/q;p' <file|wc -c)
dd if=/dev/null of=file bs="$o" seek=1

Oppure se il tuo ${TMPDIR:-/tmp}è su qualche altro file system forse:

{   cut -c2- | sed "$script" >file
} <file <<FILE
$(paste /dev/null -)
FILE

Perché (la maggior parte) delle shell inserisce i loro documenti qui in un file temporaneo cancellato. È perfettamente sicuro fintanto che il <<FILEdescrittore viene mantenuto dall'inizio alla fine e ${TMPDIR:-/tmp}ha tutto lo spazio di cui hai bisogno.

Le conchiglie che non usano file temporanei usano pipe, quindi non sono sicure da usare in questo modo. Queste conchiglie sono in genere ashderivati come busybox, dash, BSD sh- zsh, bash, ksh, e la shell Bourne, tuttavia, tutti i file temporanei uso.

a quanto pare ho scritto un piccolo programma di shell lo scorso luglio per fare qualcosa di molto simile


Se /tmpnon è praticabile, basta che tu possa adattare il file in memoria qualcosa come ...

sed 'H;$!d;x' <file | { read v &&
sed "$script" >file;}

... come caso generale garantirebbe almeno che il file fosse completamente bufferizzato dal primo sedprocesso prima di tentare di troncare il file in / out.

Una soluzione più mirata - ed efficace - potrebbe essere:

sed '/regex/!H;$!d;x' <file|{ read v && cat >file;}

... perché non avrebbe disturbato le linee di buffering che intendevi eliminare comunque.

Un test del caso generale:

{   nums=/tmp/nums
    seq 1000000 >$nums
    ls -lh "$nums"
    wc -l  "$nums"
    sed 'H;$!d;x' <$nums | { read script &&  ### read always gets a blank
    sed "$script" >$nums;}
    wc -l  "$nums"
    ls -lh "$nums"
}

-rw-r--r-- 1 mikeserv mikeserv 6.6M Dec 22 20:26 /tmp/nums
1000000 /tmp/nums
1000000 /tmp/nums
-rw-r--r-- 1 mikeserv mikeserv 6.6M Dec 22 20:26 /tmp/nums

Confesso di non aver mai letto la tua risposta in dettaglio prima, perché inizia con soluzioni non realizzabili (per me) che coinvolgono il conteggio dei byte (diverso tra ciascuno dei molti server) e /tmpche si trova sullo stesso filesystem. Mi piace la tua sedversione doppia . Penso che una combinazione di Barmar e la tua risposta sarebbe probabilmente la migliore, qualcosa del tipo: myvar="$(sed '/myregex/d' < file)" && [ -n "$myvar" ] && echo "$myvar" > file ; unset myvar (In questo caso non mi interessa preservare le nuove righe finali.)
Wildcard

2
@Wildcard - potrebbe essere. ma non dovresti usare la shell come un database. il sed| catla cosa sopra non apre mai l' output a meno che non sedabbia già memorizzato nel buffer l'intero file ed è pronto per iniziare a scrivere tutto sull'output. Se tenta di bufferizzare il file e fallisce, readnon ha successo perché trova EOF sulla |pipe prima che legga la sua prima riga nuova e quindi cat >out non succede mai fino al momento di scriverlo completamente dalla memoria. un overflow o qualcosa del genere fallisce. anche l'intera pipeline restituisce sempre successo o fallimento. memorizzarlo in un var è solo più rischioso.
Mikeserv,

@Wildcard - se lo volessi davvero anche in una variabile, penso che lo farei così: file=$(sed '/regex/!H;$!d;x' <file | read v && tee file) && cmp - file <<<"$file" || shitequindi il file di output e il var sarebbero scritti simultaneamente, il che renderebbe uno o un backup efficace , che è l'unica ragione per cui vorresti complicare le cose oltre il necessario.
Mikeserv,

@mikeserv: sto affrontando lo stesso problema dell'OP ora e trovo la tua soluzione davvero utile. Ma non capisco l'uso di read scripte read vnella tua risposta. Se puoi approfondire la questione, sarò molto apprezzato, grazie!
sylye,

1
@sylye - $scriptè lo sedscript che useresti per indirizzare qualunque porzione del tuo file volessi; è lo script che ti dà il risultato finale che desideri in streaming. vè solo un segnaposto per una riga vuota. in una bashshell non è necessario perché bashuserà automaticamente la $REPLYvariabile shell al suo posto se non ne specifichi una, ma POSIX dovresti sempre farlo. sono contento che lo trovi utile, comunque. buona fortuna. im mikeserv @ gmail se hai bisogno di qualcosa di approfondito. dovrei avere di nuovo un computer tra qualche giorno
mikeserv,

0

Questa risposta prende in prestito idee da quest'altra risposta e da questa altra risposta ma si basa su di esse, creando una risposta che è più generalmente applicabile:

num_bytes = $ (sed '/ myregex / d' / path / to / file / nomefile | wc -c)
sed '/ myregex / d' / path / to / file / nomefile 1 <> / percorso / in / file / nomefile 
dd se = / dev / null di = / percorso / in / file / nomefile bs = "$ num_bytes" cerca = 1

La prima riga esegue il sedcomando con l'output scritto nell'output standard (e non in un file); in particolare, a una pipa wcper contare i personaggi. La seconda riga esegue anche il sedcomando con l'output scritto nell'output standard, che, in questo caso, viene reindirizzato al file di input in modalità di sovrascrittura in lettura / scrittura (senza troncamento), che viene discusso qui . Questa è una cosa piuttosto pericolosa da fare; è sicuro solo quando il comando di filtro non aumenta mai la quantità di dati (testo); vale a dire, per ogni n byte che legge, scrive n o meno byte. Questo è, ovviamente, vero per il sed '/myregex/d'comando; per ogni riga che legge, scrive esattamente la stessa riga o nulla. (Altri esempi:s/foo/fu/o s/foo/bar/sarebbe sicuro, ma s/fu/foo/e s/foo/foobar/non lo sarebbe.)

Per esempio:

$ cat filename
It was
a dark and stormy night.
$ sed '/was/d' filename 1<> filename
$ cat filename
a dark and stormy night.
night.

perché questi 32 byte di dati:

I  t     w  a  s \n  a     d  a  r  k     a  n  d     s  t  o  r  m  y     n  i  g  h  t  . \n

sono stati sovrascritti con questi 25 personaggi:

a     d  a  r  k     a  n  d     s  t  o  r  m  y     n  i  g  h  t  . \n

lasciando i sette byte night.\nrimasti alla fine.

Infine, il ddcomando cerca la fine dei nuovi dati cancellati (byte 25 in questo esempio) e rimuove il resto del file; cioè, tronca il file in quel punto.


Se, per qualsiasi motivo, il 1<>trucco non funziona, puoi farlo

sed '/ myregex / d' / path / to / file / nomefile | dd di = / percorso / in / file / nomefile conv = notrunc

Inoltre, nota che, fintanto che tutto ciò che stai facendo è rimuovere le linee, tutto ciò di cui hai bisogno è grep -v myregex(come sottolineato da Barmar ).


-3

sed -i 'd' / path / to / file / nomefile


1
Ciao! Sarebbe meglio spiegare nel modo più dettagliato possibile il modo in cui la tua soluzione funziona e risponde alla domanda.
Dhag,

2
Questa è una terribile non risposta. (a) Fallirà su un filesystem completo, proprio come il mio comando originale; (b) Se avesse successo, svuoterebbe il file INTERO, piuttosto che solo le righe corrispondenti al mio regex.
Wildcard il
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.