Чтобы избежать записи копии файла, вы могли бы записать файл поверх самого себя, например:
{
sed "$l1,$l2 d" < file
perl -le 'truncate STDOUT, tell STDOUT'
} 1<> file
Опасно, поскольку у вас нет резервной копии.
Или избежать sed
, украсть часть идеи manatwork:
{
head -n "$(($l1 - 1))"
head -n "$(($l2 - $l1 + 1))" > /dev/null
cat
perl -le 'truncate STDOUT, tell STDOUT'
} < file 1<> file
Это все еще можно улучшить, потому что вы перезаписываете первые l1 - 1 строки сами по себе, пока не делаете ' Это необходимо, но его избегание означало бы немного более сложное программирование, и, например, делать все в perl
, что может оказаться менее эффективным:
perl -ne 'BEGIN{($l1,$l2) = ($ENV{"l1"}, $ENV{"l2"})}
if ($. == $l1) {$s = tell(STDIN) - length; next}
if ($. == $l2) {seek STDOUT, $s, 0; $/ = \32768; next}
if ($. > $l2) {print}
END {truncate STDOUT, tell STDOUT}' < file 1<> file
Некоторое время для удаления строк с 1000000 по 1000050 из вывода seq 1e7
:
sed -i "$ l1, $ l2 d" файл
: 16.2s Все они работают по одному и тому же принципу: мы открываем два файловых дескриптора для файла, один в режиме только для чтения (0), используя
0
1 <> файл
( <> файл
будет 0 <> файл
). Эти файловые дескрипторы указывают на два описания открытых файлов , каждое из которых будет иметь текущую позицию курсора в файле, связанном с ними.
Во втором решении, например, первая head -n "$ (($ l1 - 1))"
прочитает $ l1 - 1
строк данных из fd 0 и записать эти данные в fd 1. Таким образом, в конце этой команды курсор на обоих описаниях открытых файлов , связанных с fds 0 и 1, будет в начале $ l1
-я строка.
Затем в head -n "$ (($ l2 - $ l1 + 1))"> / dev / null
, head
будет читать $ l2 - $ l1 + 1
строк из того же описания открытого файла через его fd 0, который все еще связан с ним, поэтому курсор на fd 0 переместится в начало строки после $ l2
один.
Но его fd 1 был перенаправлен на / dev / null
, поэтому после записи в fd 1 он не будет перемещать курсор в описании открытого файла , на которое указывает {...}
fd 1.
Итак, после запуска cat
, курсор на описание открытого файла , на который указывает fd 0, будет в начале следующей строки после $ l2
, в то время как курсор на fd 1 все еще будет находиться в начале $ l1
-й строки. Или, иначе говоря, вторая голова
пропустит эти строки для удаления при вводе, но не при выводе. Теперь cat
заменит $ l1
-ю строку следующей строкой после $ l2
и так далее.
cat
вернется, когда достигнет конца файла на fd 0. Но fd 1 укажет на место в файле, которое еще не было перезаписано. Эта часть должна быть удалена, она соответствует пространству, занимаемому удаленными строками, теперь смещенным в конец файла. Что нам нужно, так это обрезать файл в том месте, где сейчас указывает этот fd 1.
Это делается с помощью системного вызова ftruncate
. К сожалению, для этого не существует стандартной утилиты Unix, поэтому мы прибегаем к perl
. tell STDOUT
дает нам текущую позицию курсора, связанную с fd 1. И мы усекаем файл по этому смещению, используя интерфейс Perl для системного вызова ftruncate
: truncate
.
В третьем решении мы заменяем запись в fd 1 первой команды head
одним системным вызовом lseek
.
Оба диска GNOME и системный монитор GNOME в моей Ubuntu предоставляют это: