Добавьте строки в начало и конец огромного файла

23

У меня есть сценарий, где строки должны быть добавлены в начале и в конце огромных файлов.

Я попытался, как показано ниже.

  • для первой строки:

    sed -i '1i\'"$FirstLine" $Filename
  • для последней строки:

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

Но проблема этой команды в том, что она добавляет первую строку файла и пересекает весь файл. Для последней строки он снова пересекает весь файл и добавляет последнюю строку. Так как его очень большой файл (14 ГБ) занимает очень много времени.

Как добавить строку в начало, а другую в конец файла, читая файл только один раз?

UNIXbest
источник

Ответы:

20

sed -iиспользует временные файлы в качестве детали реализации, что вы и испытываете; однако, добавление данных к началу потока данных без перезаписи существующего содержимого требует перезаписи файла, нет способа обойти это, даже если этого избежать sed -i.

Если перезапись файла не является опцией, вы можете рассмотреть возможность манипулирования им, когда он читается, например:

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

Кроме того, sed предназначен для редактирования потоков - файл не является потоком. Используйте программу, которая предназначена для этой цели, например, ed или ex. -iВариант СЭД не только не портативный, он будет также нарушать любые символические ссылки на файл, так как он по существу удаляет его и воссоздает его, что не имеет смысла.

Вы можете сделать это одной командой ed:

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

Обратите внимание, что в зависимости от вашей реализации ed, он может использовать файл подкачки, требующий, чтобы у вас было как минимум столько свободного места.

Крис Даун
источник
Привет, команда ed, которую вы предоставили, работает очень хорошо для огромных файлов. Но у меня есть 3 огромных файла, таких как Test, Test1, Test 2. Я дал команду вроде ed -s Tes * << 'EOF' 0a, добавив эти строки в начало. $ a дописать эти строки до конца. w EOF Но он берет только тестовый файл и добавляет первые / последние строки. Как мы можем внести изменения в одну и ту же команду, чтобы она добавила первую и последнюю строку во всех файлах.
UNIXbest
@UNIXbest - Используйте forцикл:for file in Tes*; do [command]; done
Крис Даун
Привет, я использовал команду ниже для файла в Tes *; do ed -s Tes * << 'EOF' 0a HELLO HDR. $ a Hello TLR. w EOF выполнено, но оно все еще записывается в первый файл.
UNIXbest
Правильно, потому что вы должны использовать "$file", Tes*а не в качестве аргумента ed.
Крис Даун
2
@UNIXbest Если ваша проблема была решена с помощью этого ответа, вы должны принять его.
Джозеф Р.
9

Обратите внимание, что если вы хотите избежать размещения всей копии файла на диске, вы можете сделать следующее:

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

При этом используется тот факт, что когда его стандартный ввод / вывод является файлом, sed чтение и запись выполняется по блокам. Итак, здесь можно переопределить файл, который он читает, до тех пор, пока первая строка, которую вы добавляете, меньше sedразмера блока (это должно быть что-то вроде 4k или 8k).

Обратите внимание, что если по какой-то причине sedпроизойдет сбой (убит, сбой машины ...), вы получите половину обработанного файла, что будет означать, что некоторые данные размером с первую строку отсутствуют где-то посередине.

Также обратите внимание, что если вы sedне используете GNU sed, это не будет работать для двоичных данных (но поскольку вы используете -i, вы используете GNU sed).

Стефан Шазелас
источник
это ошибки для меня на Ubuntu 16.04
Csaba Toth
4

Вот несколько вариантов (каждый из которых создаст новую копию файла, поэтому убедитесь, что у вас достаточно места для этого):

  • простое эхо / кошка

    echo "first" > new_file; cat $File >> new_file; \
      echo "last" >> new_file; 
  • awk / gawk и т. д.

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

    awkи тому подобное читать файлы построчно. BEGIN{}Блок выполняется до первой строки и END{}блока после последней строки. Итак, команда выше означает 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

    По сути, это то же самое, что вышеописанный gawk, написанный на Perl.

Тердон
источник
1
Обратите внимание, что во всех этих случаях вам потребуется как минимум 14 ГБ свободного места для нового файла.
Крис Даун
@ChrisDown хороший момент, я отредактировал свой ответ, чтобы прояснить это. Я предположил, что это не проблема, так как используется OP, sed -iкоторый создает временные файлы.
Terdon
3

Я предпочитаю гораздо проще:

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

Это преобразует файл:

asdf
qwer

в файл:

foo
asdf
qwer
bar
CommaToast
источник
2

Вы можете использовать Vim в режиме Ex:

ex -sc '1i|ALFA' -c '$a|BRAVO' -cx file
  1. 1 выберите первую строку

  2. i вставить текст и перевод строки

  3. $ выберите последнюю строку

  4. a добавить текст и перевод строки

  5. x сохранить и закрыть

Стивен Пенни
источник
Что, если мы хотим сделать это с несколькими файлами?
geoyws
1
@geoyws, который на самом деле не подходит для этого вопроса
Стивен Пенни
Вы уверены, что это $ a, а не% a?
Карлос Роблес
2

Невозможно вставить данные в начало файла¹, все, что вы можете сделать, это создать новый файл, записать дополнительные данные и добавить старые данные. Таким образом, вам придется переписать весь файл хотя бы один раз, чтобы вставить первую строку. Однако вы можете добавить последнюю строку, не переписывая файл.

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

Кроме того, вы можете объединить две команды в одном запуске sed.

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

sed -iсоздает новый выходной файл, а затем перемещает его поверх старого файла. Это означает, что во время работы sed существует вторая копия файла, занимающая место. Вы можете избежать этого, перезаписав файл на месте , но с серьезными ограничениями: добавляемая строка должна быть меньше буфера sed, и в случае сбоя системы вы получите поврежденный файл и некоторое содержимое, потерянное в средний, поэтому я настоятельно рекомендую против этого.

¹ В Linux есть способ вставки данных в файл, но он может вставлять только целое число блоков файловой системы, он не может вставлять строки произвольной длины. Это полезно для некоторых приложений, таких как базы данных и виртуальные машины, но бесполезно для текстовых файлов.

Жиль "ТАК - перестань быть злым"
источник
Не правда. Посмотрите fallocate()с FALLOC_FL_INSERT_RANGEдоступны на XFS и ext4 в современных ядрах (4.xx) man7.org/linux/man-pages/man2/fallocate.2.html
Eric
@Eric Вы можете вставлять только целые блоки, но не произвольную длину в байтах, по крайней мере, в Linux 4.15.0 с ext4. Есть ли файловая система, которая может вставить произвольную длину в байтах?
Жиль "ТАК - прекрати быть злым"
Правильно, но это все еще не делает ваше утверждение правильным. Вы писали: «Нет способа вставить данные в начало файла». Это все еще не так: есть механизм для вставки экстентов в начало файла. Конечно, он поставляется с оговорками, но стоит упомянуть, потому что некоторые пользователи могут не заботиться об ограничениях размера блока, заполняя пробелы или возврат каретки.
Эрик
0
$ (echo "Some Text" ; cat file1) > file2
Кошик Кармакар
источник
4
Только кодовый ответ не приемлем, пожалуйста, улучшите свой ответ
Networker
Вы можете расширить свой ответ, включив в него объяснение вашего предложения или ссылки на документацию, поддерживающую ваше решение.
HalosGhost
-1

Современные ядра Linux (выше 4.1 или 4.2) поддерживают вставку данных в начало файла с помощью fallocate()системного вызова с FALLOC_FL_INSERT_RANGEфайловыми системами ext4 и xfs. По сути, это логическая операция смещения: данные логически перемещаются с большим смещением.

Существует ограничение в отношении степени детализации диапазона, который вы хотите вставить в начало файла. Но для текстовых файлов вы, вероятно, можете выделить немного больше, чем требуется (до границы гранулярности) и заполнить пробелами или возвратом каретки, но это зависит от вашего приложения

Я не знаю ни одной доступной утилиты linux, которая манипулирует экстентами файлов, но написать ее несложно: получить дескриптор файла и вызвать fallocate()с соответствующими аргументами. Для получения дополнительной информации см. Справочную страницу fallocateсистемного вызова: http://man7.org/linux/man-pages/man2/fallocate.2.html.

Эрик
источник
Утилита не проблема (если не встроенный Linux): util-linux содержит fallocateутилиту. Проблема в том, что гранулярность целых блоков делает это бесполезным для большинства текстовых файлов. Другая проблема заключается в том, что распределение диапазона и последующее изменение не являются атомарными. Так что это на самом деле не решает проблему здесь.
Жиль "ТАК - прекрати быть злым"
Гранулярность - это оговорка, о которой я уже упоминал, и нет, она не делает ее бесполезной, это зависит от приложения. Где вы увидели в вопросе, что атомарность важна? Я вижу только проблему выступлений. Несмотря на это, этот системный вызов кажется атомарным: elixir.bootlin.com/linux/latest/source/fs/open.c#L228, и если атомарность становится важной (это не так, но, скажем, ради аргумента), тогда просто используйте блокировку файлов. (укажите мне место в коде ядра, где fallocateатомарность нарушена, пожалуйста, мне любопытно)
Эрик