Dodaj linie na początku i na końcu dużego pliku

23

Mam scenariusz, w którym wiersze będą dodawane na początku i na końcu ogromnych plików.

Próbowałem, jak pokazano poniżej.

  • dla pierwszego wiersza:

    sed -i '1i\'"$FirstLine" $Filename
  • dla ostatniej linii:

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

Problem z tym poleceniem polega na tym, że dołącza on pierwszą linię pliku i przechodzi przez cały plik. W ostatnim wierszu ponownie przechodzi przez cały plik i dołącza ostatni wiersz. Ponieważ jest to bardzo duży plik (14 GB), zajmuje to bardzo dużo czasu.

Jak mogę dodać linię na początku, a drugą na końcu pliku, czytając plik tylko raz?

UNIXbest
źródło

Odpowiedzi:

20

sed -iużywa plików tymczasowych jako szczegółów implementacji, czego właśnie doświadczasz; jednak przygotowanie danych na początek strumienia danych bez nadpisywania istniejącej zawartości wymaga przepisania pliku, nie ma sposobu na obejście tego, nawet przy unikaniu sed -i.

Jeśli przepisywanie pliku nie jest opcją, możesz rozważyć manipulowanie nim podczas odczytu, na przykład:

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

Sed służy również do edycji strumieni - plik nie jest strumieniem. Użyj programu przeznaczonego do tego celu, takiego jak ed lub ex. -iOpcja sed nie tylko nie jest przenośny, będzie to również przełamać wszelkie dowiązania do pliku, ponieważ zasadniczo usuwa go i odtwarza go, co nie ma sensu.

Możesz to zrobić jednym poleceniem za pomocą ed:

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

Zauważ, że w zależności od implementacji ed, może używać pliku stronicowania, wymagając co najmniej tyle dostępnego miejsca.

Chris Down
źródło
Cześć, polecenie ed, które podałeś, działa bardzo dobrze dla dużych plików. Ale mam 3 ogromne pliki, takie jak Test, Test1, Test 2. Wydałem polecenie takie jak ed -s Tes * << 'EOF' 0a poprzedzam te wiersze na początku. $ a dopisz te linie do końca. w EOF Ale bierze tylko plik testowy i dodaje pierwszą / ostatnią linię. Jak możemy dokonać zmian w tym samym poleceniu, aby dodać pierwszy i ostatni wiersz we wszystkich plikach.
UNIXbest
@UNIXbest - Użyj forpętli:for file in Tes*; do [command]; done
Chris Down,
Cześć, użyłem poniżej polecenia dla pliku w Tes *; do ed -s Tes * << 'EOF' 0a HEllO HDR. $ a Hello TLR. w EOF zrobione Ale nadal zapisuje się do pierwszego pliku.
UNIXbest
Racja, ponieważ musisz użyć "$file", a nie Tes*argumentu ed.
Chris Down,
2
@UNIXbest Jeśli Twój problem został rozwiązany przez tę odpowiedź, powinieneś rozważyć jej zaakceptowanie.
Joseph R.
9

Pamiętaj, że jeśli chcesz uniknąć przydzielania całej kopii pliku na dysku, możesz:

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

Wykorzystuje to fakt, że gdy jego stdin / stdout jest plikiem, sed odczytuje i zapisuje blok. Więc tutaj jest OK, aby przesłonić plik, który czyta, dopóki pierwszy dodawany wiersz jest mniejszy niż sedrozmiar bloku (powinien wynosić około 4k lub 8k).

Zauważ jednak, że jeśli z jakiegoś powodu sednie powiedzie się (zabity, awaria komputera ...), skończysz z plikiem w połowie przetworzonym, co będzie oznaczać brak danych w rozmiarze pierwszej linii gdzieś pośrodku.

Zauważ też, że jeśli nie jesteś sedGNU sed, nie będzie to działać dla danych binarnych (ale ponieważ używasz -i, używasz GNU sed).

Stéphane Chazelas
źródło
te błędy dla mnie na Ubuntu 16.04
Csaba Toth
4

Oto kilka opcji (z których wszystkie utworzą nową kopię pliku, więc upewnij się, że masz na to wystarczająco dużo miejsca):

  • proste echo / cat

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

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

    awki podobnie czytają pliki linia po linii. BEGIN{}Blok jest wykonywany przed pierwszą linią i END{}bloku po ostatnim wierszu. Tak więc powyższe polecenie oznacza 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

    Jest to zasadniczo to samo, co gawk powyżej właśnie napisany w Perlu.

terdon
źródło
1
Pamiętaj, że we wszystkich tych przypadkach będziesz potrzebować co najmniej 14 GB więcej miejsca na nowy plik.
Chris Down,
@ChrisDown dobry punkt, zredagowałem moją odpowiedź, aby to wyjaśnić. Zakładałem, że to nie był problem, ponieważ OP używa, sed -iktóry tworzy pliki tymczasowe.
terdon
3

Wolę dużo prostsze:

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

To przekształca plik:

asdf
qwer

do pliku:

foo
asdf
qwer
bar
CommaToast
źródło
2

Możesz używać Vima w trybie Ex:

ex -sc '1i|ALFA' -c '$a|BRAVO' -cx file
  1. 1 wybierz pierwszą linię

  2. i wstaw tekst i nowy wiersz

  3. $ wybierz ostatnią linię

  4. a dołącz tekst i znak nowej linii

  5. x Zapisz i zamknij

Steven Penny
źródło
co jeśli chcielibyśmy to zrobić dla wielu plików?
geoyws,
1
@geoyws, które tak naprawdę nie wchodzi w zakres tego pytania
Steven Penny
czy na pewno jest to $ a, a nie% a?
Carlos Robles
2

Nie ma możliwości wstawienia danych na początku pliku¹, wystarczy utworzyć nowy plik, zapisać dodatkowe dane i dołączyć stare dane. Musisz więc przepisać cały plik przynajmniej raz, aby wstawić pierwszy wiersz. Możesz jednak dołączyć ostatni wiersz bez przepisywania pliku.

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

Alternatywnie możesz połączyć te dwa polecenia w jednym uruchomieniu sed.

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

sed -itworzy nowy plik wyjściowy, a następnie przenosi go nad starym plikiem. Oznacza to, że gdy sed działa, druga kopia pliku zajmuje więcej miejsca. Możesz tego uniknąć przez zastępując plik w miejscu , ale z poważnymi ograniczeniami: dodawana linia musi być mniejsza niż bufor sed, a jeśli system ulegnie awarii, skończy się to uszkodzeniem pliku i utratą części zawartości w pliku środkowy, więc zdecydowanie odradzam.

¹ Linux ma sposób wstawiania danych do pliku, ale może wstawiać tylko całą liczbę bloków systemu plików, nie może wstawiać ciągów o dowolnej długości. Jest przydatny w niektórych aplikacjach, takich jak bazy danych i maszyny wirtualne, ale jest bezużyteczny w przypadku plików tekstowych.

Gilles „SO- przestań być zły”
źródło
Nie prawda. Spójrz na fallocate()z FALLOC_FL_INSERT_RANGEdostępnych na ext4 i XFS w nowoczesnych jądrach (4.xx) man7.org/linux/man-pages/man2/fallocate.2.html
Eric
@Eric Można wstawiać tylko całe bloki, a nie dowolne długości bajtów, przynajmniej w Linuksie 4.15.0 z ext4. Czy istnieje system plików, który może wstawiać dowolne długości bajtów?
Gilles „SO- przestań być zły”
Zgadza się, ale nadal nie poprawia twojego stwierdzenia. Napisałeś: „Nie ma możliwości wstawienia danych na początku pliku”. To wciąż nieprawda: istnieje mechanizm wstawiania zakresu na początku pliku. Oczywiście ma pewne zastrzeżenia, ale warto o tym wspomnieć, ponieważ niektórzy użytkownicy mogą nie przejmować się ograniczeniami rozmiaru bloku, wypełniając spacje lub znaki powrotu karetki.
Eric
0
$ (echo "Some Text" ; cat file1) > file2
Koushik Karmakar
źródło
4
Tylko odpowiedź na kod jest niedopuszczalna, popraw swoją odpowiedź
Networker
Rozważ poszerzenie swojej odpowiedzi o wyjaśnienie swojej sugestii lub łącza do dokumentacji, która popiera twoje rozwiązanie.
HalosGhost
-1

Nowoczesne jądra Linuksa (wyższe niż 4.1 lub 4.2) obsługują wstawianie danych na początku pliku za pomocą fallocate()wywołania systemowego za pomocąFALLOC_FL_INSERT_RANGE w systemach plików ext4 i xfs. Zasadniczo jest to logiczna operacja przesunięcia: dane są logicznie przenoszone z większym przesunięciem.

Istnieje ograniczenie dotyczące ziarnistości zakresu, który chcesz wstawić na początku pliku. Ale w przypadku plików tekstowych można prawdopodobnie przydzielić nieco więcej niż jest to wymagane (do granicy ziarnistości) i wypełnić spacje lub znaki powrotu karetki, ale to zależy od aplikacji

Nie znam żadnego łatwo dostępnego narzędzia linuxowego, które manipuluje zakresami plików, ale nie jest to trudne do napisania: pobierz deskryptor pliku i wywołaj fallocate()z odpowiednimi argumentami. Więcej informacji można znaleźć na stronie man fallocatewywołania systemowego: http://man7.org/linux/man-pages/man2/fallocate.2.html

Eric
źródło
Narzędzie nie stanowi problemu (przy założeniu, że Linux nie jest osadzony): util-linux zawiera fallocatenarzędzie. Problem polega na tym, że ziarnistość całych bloków czyni to bezużytecznym dla większości plików tekstowych. Innym problemem jest to, że przydział zakresu i późniejsza modyfikacja nie są atomowe. Więc to tak naprawdę nie rozwiązuje problemu.
Gilles „SO- przestań być zły”
Szczegółowość jest zastrzeżeniem, o którym już wspomniałem i nie, nie czyni jej bezużytecznym, zależy od aplikacji. Gdzie widziałeś w pytaniu, że atomowość jest ważna? Widzę tylko problem występów. Mimo to ten system wydaje się być atomowy: elixir.bootlin.com/linux/latest/source/fs/open.c#L228, a jeśli atomowość staje się ważna (nie jest, ale powiedzmy, że ze względu na argument), to po prostu użyj blokowania plików. (wskaż mi miejsce w kodzie jądra, w którym fallocateatomowość jest zepsuta, proszę, jestem ciekawa)
Eric