У меня довольно большой файл (35 ГБ), и я хотел бы отфильтровать этот файл на месте (т. Е. У меня недостаточно места на диске для другого файла), в частности, я хочу grep и игнорировать некоторые шаблоны - есть ли способ сделать это без использования другого файла?
Допустим, я хочу отфильтровать все строки, содержащие, foo:
например ...
Ответы:
На уровне системных вызовов это должно быть возможно. Программа может открыть целевой файл для записи, не обрезая его, и начать писать то, что читает со стандартного ввода. При чтении EOF выходной файл может быть обрезан.
Поскольку вы фильтруете строки из входных данных, позиция записи выходного файла всегда должна быть меньше позиции чтения. Это означает, что вы не должны портить свой ввод новым выводом.
Однако, поиск программы, которая делает это, является проблемой.
dd(1)
имеет опциюconv=notrunc
, которая не усекает выходной файл при открытии, но также не усекает в конце, оставляя исходное содержимое файла после содержимого grep (с помощью команды вродеgrep pattern bigfile | dd of=bigfile conv=notrunc
)Поскольку это очень просто с точки зрения системного вызова, я написал небольшую программу и протестировал ее на небольшой (1 МБ) файловой системе с полной обратной связью. Он сделал то, что хотел, но вы действительно хотите сначала проверить это с некоторыми другими файлами. Переписывать файл всегда будет рискованно.
overwrite.c
Вы бы использовали его как:
В основном я публикую это, чтобы другие могли прокомментировать, прежде чем вы попробуете. Возможно, кто-то еще знает о программе, которая делает что-то подобное, что более проверено.
источник
grep
не будет выводиться больше данных, чем считывается, позиция записи всегда должна быть позади позиции чтения. Даже если вы пишете с той же скоростью, что и чтение, все равно все будет в порядке. Попробуйте с помощью rot13 вместо grep, а затем снова. md5sum до и после, и вы увидите то же самое.dd
, но это громоздко.Вы можете использовать
sed
для редактирования файлов на месте (но это создает промежуточный временный файл):Удалить все строки, содержащие
foo
:Сохранить все строки, содержащие
foo
:источник
$HOME
будет доступна для записи, но/tmp
будет только для чтения (по умолчанию). Например, если у вас Ubuntu и вы загрузились в консоль восстановления, это обычно так. Кроме того, оператор here-document там<<<
также не будет работать, поскольку он/tmp
должен быть r / w, потому что он также запишет туда временный файл. (см. этот вопрос сstrace
выводом «а»)Я предполагаю, что ваша команда фильтра - это то, что я назову фильтром сжатия префикса , у которого есть свойство, что байт N в выходных данных никогда не записывается прежде, чем прочитает по крайней мере N байтов ввода.
grep
имеет это свойство (при условии, что он только фильтрует и не выполняет никаких других действий, таких как добавление номеров строк для совпадений). С таким фильтром вы можете перезаписать ввод по мере продвижения. Конечно, вы должны быть уверены, что не допустили ошибок, так как перезаписанная часть в начале файла будет потеряна навсегда.Большинство инструментов Unix предоставляют выбор добавления файла или его усечения, без возможности его перезаписи. Единственное исключение в стандартном наборе инструментов -
dd
это указание не обрезать свой выходной файл. Таким образом, план , чтобы отфильтровать команду вdd conv=notrunc
. Это не меняет размер файла, поэтому мы также берем длину нового содержимого и усекаем файл до этой длины (снова с помощьюdd
). Обратите внимание, что эта задача по своей сути не является надежной - если возникает ошибка, вы самостоятельно.Вы можете написать грубый эквивалент Perl. Вот быстрая реализация, которая не пытается быть эффективной. Конечно, вы можете захотеть выполнить первоначальную фильтрацию непосредственно на этом языке.
источник
С любой Bourne-подобной оболочкой:
Почему-то кажется, что люди забывают об этом 40-летнем и стандартном операторе перенаправления чтения + записи.
Мы открываем
bigfile
в режиме чтения + записи и (что здесь наиболее важно) без усечения, вstdout
то времяbigfile
как открыт (отдельно) наcat
'sstdin
. Послеgrep
завершения, и если он удалил несколько строк,stdout
теперь указывает где-то внутриbigfile
, нам нужно избавиться от того, что находится за этой точкой. Следовательноperl
команда, которая усекает file (truncate STDOUT
) в текущей позиции (как возвращеноtell STDOUT
).(
cat
это для GNU,grep
который иначе жалуется, если stdin и stdout указывают на один и тот же файл).¹ Ну, хотя
<>
он был в оболочке Bourne с самого начала в конце семидесятых, он изначально не был документирован и не реализован должным образом . Его не было в первоначальной реализацииash
1989 года, и, хотя он являетсяsh
оператором перенаправления POSIX (с начала 90-х, поскольку POSIXsh
основан на том,ksh88
что всегда имелось), он не был добавлен во FreeBSD,sh
например, до 2000 года, так что 15 лет назад старый , вероятно, более точный. Также обратите внимание, что дескриптор файла по умолчанию, если он не указан, присутствует<>
во всех оболочках, за исключением того, чтоksh93
в 2010 году он был изменен с 0 на 1 в ksh93t + (что нарушает обратную совместимость и соответствие POSIX).источник
perl -e 'truncate STDOUT, tell STDOUT'
? Это работает для меня без учета этого. Есть ли способ достичь того же, не используя Perl?redirection "<>" fixed and documented (used in /etc/inittab f.i.).
один совет.Несмотря на то, что это старый вопрос, мне кажется, что это постоянный вопрос, и доступно более общее, более четкое решение, чем предполагалось до сих пор. Кредит, где кредит должен: я не уверен, что я придумал бы это без учета упоминания Стефана Шазеласа об
<>
операторе обновлений.Открытие файла для обновления в оболочке Bourne имеет ограниченную полезность. Оболочка не дает возможности искать файл и не может устанавливать его новую длину (если она короче старой). Но это легко исправить, так легко я удивляюсь, что он не входит в число стандартных утилит в
/usr/bin
.Это работает:
Как это (шляпа Стефану):
(Я использую GNU grep. Возможно, что-то изменилось, так как он написал свой ответ.)
За исключением того, что у вас нет / usr / bin / ftruncate . Для пары десятков строк C, вы можете увидеть ниже. Эта утилита ftruncate усекает произвольный дескриптор файла до произвольной длины, по умолчанию используется стандартный вывод и текущая позиция.
Приведенная выше команда (1-й пример)
T
для обновления. Как и в случае с open (2), открытие файла таким образом устанавливает текущее смещение в 0.T
нормально, и оболочка перенаправляет свои выходные данныеT
через дескриптор 4.Затем подоболочка завершается, закрывая дескриптор 4. Вот ftruncate :
Примечание: ftruncate (2) является непереносимым при использовании таким способом. Для абсолютной общности прочитайте последний записанный байт, снова откройте файл O_WRONLY, найдите, запишите байт и закройте.
Учитывая, что этому вопросу 5 лет, я собираюсь сказать, что это решение неочевидно. Для открытия нового дескриптора используется exec , а
<>
оператор - оба являются загадочными. Я не могу вспомнить стандартную утилиту, которая манипулирует индексом по дескриптору файла. (Синтаксис может бытьftruncate >&4
, но я не уверен, что это улучшение.) Это значительно короче, чем компетентный, исследовательский ответ Camh. Это немного яснее, чем у Стефана, ИМО, если вы не любите Perl больше, чем я. Я надеюсь, что кто-то найдет это полезным.Другим способом сделать то же самое может быть исполняемая версия lseek (2), которая сообщает о текущем смещении; вывод может быть использован для / usr / bin / truncate , который предоставляют некоторые Linuxi.
источник
ed
вероятно правильный выбор для редактирования файла на месте:источник
ed
версии ведут себя по-разному ..... это изman ed
(GNU Ed 1.4) ...If invoked with a file argument, then a copy of file is read into the editor's buffer. Changes are made to this copy and not directly to file itself.
ed
это не решение gool для редактирования 35-гигабайтных файлов, поскольку файл считывается в буфер.!
), поэтому у него может быть несколько более интересных трюковed
усекает файл и переписывает его. Так что это не изменит данные на диске на месте, как того пожелает OP. Кроме того, он не может работать, если файл слишком велик для загрузки в память.Вы можете использовать файловый дескриптор bash для чтения / записи, чтобы открыть ваш файл (перезаписать его in-situ), затем
sed
иtruncate
... но, конечно, никогда не допускайте, чтобы ваши изменения превышали объем прочитанных данных. ,Вот скрипт (использует: переменная bash $ BASHPID)
Вот тестовый вывод
источник
Я бы отображал файл в памяти, делал все на месте, используя указатели char *, чтобы освободить память, а затем разархивировать файл и обрезать его.
источник
Не совсем на месте, но - это может быть полезно в подобных обстоятельствах.
Если дисковое пространство является проблемой, сначала сожмите файл (так как это текст, это даст огромное сокращение), затем используйте sed (или grep, или что-то еще) обычным способом в середине конвейера распаковки / сжатия.
источник
sed -e '/foo/d' MyFile | gzip -c >MyEditedFile.gz && gzip -dc MyEditedFile.gz >MyFile
В интересах любого, кто ищет этот вопрос, правильный ответ - прекратить поиск неясных функций оболочки, которые рискуют испортить ваш файл из-за незначительного прироста производительности, и вместо этого использовать некоторые варианты этого шаблона:
Только в крайне необычной ситуации, когда это по какой-то причине неосуществимо, вы должны серьезно рассмотреть любые другие ответы на этой странице (хотя их, безусловно, интересно читать). Я признаю, что загадка ОП, связанная с отсутствием дискового пространства для создания второго файла, является именно такой ситуацией. Хотя даже тогда есть и другие доступные варианты, например, предоставленные @Ed Randall и @Basile Starynkevitch.
источник
echo -e "$(grep pattern bigfile)" >bigfile
источник
grepped
данные превышают длину, разрешенную командной строкой. Затем он искажает данные