Aggiungi righe all'inizio e alla fine dell'enorme file


23

Ho lo scenario in cui le linee da aggiungere all'inizio e alla fine dei file enormi.

Ho provato come mostrato di seguito.

  • per la prima riga:

    sed -i '1i\'"$FirstLine" $Filename
  • per l'ultima riga:

    sed -i '$ a\'"$Lastline" $Filename  

Ma il problema con questo comando è che sta aggiungendo la prima riga del file e attraversando l'intero file. Per l'ultima riga attraversa nuovamente l'intero file e aggiunge un'ultima riga. Dal momento che il suo enorme file (14 GB) richiede molto tempo.

Come posso aggiungere una riga all'inizio e un'altra alla fine di un file mentre leggo il file una sola volta?

Risposte:


20

sed -iusa i tempfile come dettaglio di implementazione, che è ciò che stai vivendo; tuttavia, anteporre i dati all'inizio di un flusso di dati senza sovrascrivere i contenuti esistenti richiede la riscrittura del file, non c'è modo di aggirarli, anche quando si evita sed -i.

Se riscrivere il file non è un'opzione, potresti considerare di manipolarlo durante la lettura, ad esempio:

{ echo some prepended text ; cat file ; } | command

Inoltre, sed è per la modifica di flussi: un file non è un flusso. Utilizzare un programma pensato per questo scopo, come ed o ex. L' -iopzione di sed non solo non è portatile, ma interromperà anche qualsiasi collegamento simbolico al tuo file, poiché essenzialmente lo elimina e lo ricrea, il che è inutile.

Puoi farlo in un singolo comando in questo edmodo:

ed -s file << 'EOF'
0a
prepend these lines
to the beginning
.
$a
append these lines
to the end
.
w
EOF

Si noti che, a seconda dell'implementazione di ed, potrebbe utilizzare un file di paging, che richiede di avere almeno lo spazio disponibile.


Ciao, il comando che hai fornito funziona molto bene per file di grandi dimensioni. Ma ho 3 file enormi come Test, Test1, Test 2. Ho dato il comando come ed -s Tes * << 'EOF' 0a ha anteposto queste righe all'inizio. $ a aggiungi queste righe alla fine. w EOF Ma sta prendendo solo il file di prova e aggiungendo la prima / ultima riga. Come possiamo apportare modifiche nello stesso comando in modo che debba aggiungere la prima e l'ultima riga in tutti i file.
UNIXbest

@UNIXbest - Usa un forloop:for file in Tes*; do [command]; done
Chris Down,

Ciao, ho usato sotto il comando per il file in Tes *; do ed -s Tes * << 'EOF' 0a HEllO HDR. $ a Ciao TLR. w EOF fatto Ma sta ancora scrivendo nel primo file.
UNIXbest,

Giusto, perché devi usare "$file", non Tes*come argomento per ed.
Chris Down,

2
@UNIXbest Se il tuo problema è stato risolto da questa risposta, dovresti considerare di accettarlo.
Joseph R.,

9

Se si desidera evitare di allocare un'intera copia del file su disco, è possibile:

sed '
1i\
begin
$a\
end' < file 1<> file

Ciò usa il fatto che quando il suo stdin / stdout è un file, sed legge e scrive per blocco. Quindi, qui è OK per sovrascrivere il file che sta leggendo finché la prima riga che stai aggiungendo è più piccola della seddimensione del blocco (dovrebbe essere qualcosa come 4k o 8k).

Nota però che se per qualche motivo sedfallisce (ucciso, crash della macchina ...), finirai con il file metà elaborato, il che significa che alcuni dati delle dimensioni della prima riga mancano da qualche parte nel mezzo.

Nota anche che a meno che tu non sedsia la GNU sed, ciò non funzionerà per i dati binari (ma poiché stai usando -i, stai usando GNU sed).


questo errore per me su Ubuntu 16.04
Csaba Toth

4

Ecco alcune opzioni (che creeranno una nuova copia del file, quindi assicurati di avere abbastanza spazio per quello):

  • semplice eco / cat

    echo "first" > new_file; cat $File >> new_file; \
      echo "last" >> new_file; 
  • awk / gawk ecc

    gawk 'BEGIN{print "first\n"}{print}END{print "last\n"}' $File > NewFile 

    awke il suo ilk legge i file riga per riga. Il BEGIN{}blocco viene eseguito prima della prima riga e il END{}blocco dopo l'ultima riga. Quindi, il comando sopra significa print "first" at the beginning, then print every line in the file and print "last" at the end.

  • Perl

    perl -ne 'BEGIN{print "first\n"} print;END{print "last\n"}' $File > NewFile

    Questa è essenzialmente la stessa cosa del gawk sopra appena scritto in Perl.


1
Tieni presente che in tutti questi casi, avrai bisogno di almeno 14 GB di spazio in più per il nuovo file.
Chris Down,

@ChrisDown buon punto, ho modificato la mia risposta per chiarirlo. Supponevo che non si trattasse di un problema poiché stava utilizzando l'OP sed -iche creava file temporanei.
terdon

3

Preferisco il molto più semplice:

gsed -i '1s/^/foo\n/gm; $s/$/\nbar/gm' filename.txt

Questo trasforma il file:

asdf
qwer

al file:

foo
asdf
qwer
bar

2

Puoi usare Vim in modalità Ex:

ex -sc '1i|ALFA' -c '$a|BRAVO' -cx file
  1. 1 seleziona la prima riga

  2. i inserisci testo e newline

  3. $ seleziona l'ultima riga

  4. a aggiungi testo e newline

  5. x salva e chiudi


e se volessimo farlo su più file?
geoyws,

1
@geoyws che non rientra realmente in questa domanda
Steven Penny,

sei sicuro che sia $ a e non% a?
Carlos Robles,

2

Non è possibile inserire dati all'inizio di un file¹, tutto ciò che si può fare è creare un nuovo file, scrivere i dati aggiuntivi e aggiungere i vecchi dati. Quindi dovrai riscrivere l'intero file almeno una volta per inserire la prima riga. Tuttavia, puoi aggiungere l'ultima riga senza riscrivere il file.

sed -i '1i\'"$FirstLine" $Filename
echo "$LastLine" >>$Filename

In alternativa, è possibile combinare i due comandi in un'unica sessione di sed.

sed -i -e '1i\'"$FirstLine" -e '$ a\'"$Lastline" $Filename

sed -icrea un nuovo file di output e quindi lo sposta sul vecchio file. Ciò significa che mentre sed sta funzionando, c'è una seconda copia del file che occupa spazio. Puoi evitarlo sovrascrivendo il file in atto , ma con importanti restrizioni: la linea che stai aggiungendo deve essere più piccola del buffer di sed e se il tuo sistema si arresta in modo anomalo finirai con un file danneggiato e alcuni contenuti persi nel mezzo, quindi sconsiglio vivamente.

¹ Linux ha un modo per inserire dati in un file, ma può solo inserire un numero intero di blocchi di filesystem, non può inserire stringhe di lunghezza arbitraria. È utile per alcune applicazioni, come database e macchine virtuali, ma è inutile per i file di testo.


Non vero. Guarda fallocate()con FALLOC_FL_INSERT_RANGEdisponibile su XFS ed ext4 nei kernel moderni (4.xx) man7.org/linux/man-pages/man2/fallocate.2.html
Eric

@Eric È possibile inserire solo blocchi interi, non lunghezze di byte arbitrarie, almeno a partire da Linux 4.15.0 con ext4. Esiste un filesystem che può inserire lunghezze di byte arbitrarie?
Gilles 'SO- smetti di essere malvagio' il

Giusto ma non rende ancora corretta la tua affermazione. Hai scritto: "Non è possibile inserire dati all'inizio di un file". Questo non è ancora vero: esiste un meccanismo per inserire estensioni all'inizio di un file. Viene fornito con avvertenze, certo, ma vale la pena menzionare perché alcuni utenti potrebbero non preoccuparsi delle restrizioni sulla dimensione del blocco riempiendo di spazi o ritorni a capo.
Eric,

0
$ (echo "Some Text" ; cat file1) > file2

4
Solo la risposta al codice non è accettabile, migliora la tua risposta
Networker,

Valuta di espandere la tua risposta per includere una spiegazione del tuo suggerimento o collegamenti a documentazione che supporti la tua soluzione.
HalosGhost

-1

I kernel Linux moderni (superiori a 4.1 o 4.2) supportano l'inserimento di dati all'inizio di un file tramite la fallocate()chiamata di sistema con FALLOC_FL_INSERT_RANGEfilesystem su ext4 e xfs. In sostanza si tratta di un'operazione di spostamento logico: i dati vengono ricollocati logicamente con un offset maggiore.

Esiste un vincolo relativo alla granularità dell'intervallo che si desidera inserire all'inizio del file. Ma per i file di testo puoi probabilmente allocare un po 'più del necessario (fino al limite di granularità) e riempire di spazi o ritorni a capo, ma questo dipende dalla tua applicazione

Non conosco alcuna utility linux facilmente disponibile che manipoli le estensioni dei file ma non è difficile da scrivere: ottenere un descrittore di file e chiamare fallocate()con gli argomenti appropriati. Per ulteriori dettagli, consultare la pagina man della fallocatechiamata di sistema: http://man7.org/linux/man-pages/man2/fallocate.2.html


Un'utilità non è il problema (supponendo un Linux non incorporato): util-linux contiene fallocateun'utilità. Il problema è che una granularità di interi blocchi lo rende inutile per la maggior parte dei file di testo. Un altro problema è che l'allocazione dell'intervallo e la successiva modifica non sono atomiche. Quindi questo in realtà non risolve il problema qui.
Gilles 'SO- smetti di essere malvagio' il

La granularità è un avvertimento che ho già menzionato e no, non lo rende inutile, dipende dall'applicazione. Dove hai visto nella domanda che l'atomicità è importante? Vedo solo il problema delle esibizioni. Anche così questo syscall sembra essere atomico: elixir.bootlin.com/linux/latest/source/fs/open.c#L228 e se l'atomicità diventa importante (non lo è, ma dico che è per ragioni di argomento) allora basta usare il blocco dei file. (indicami il posto nel codice del kernel in cui l' fallocateatomicità è rotta per favore, sono curioso)
Eric
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.