Есть ли более быстрый способ удаления строки (с заданным номером строки) из файла?

Если вы можете жить с созданием двух оболочек (например, $ SHLVL увеличивается на два):

tcsh -c 'source /tmp/myRCFile.tcsh; tcsh'
7
13.04.2017, 15:36
10 ответов

Чтобы избежать записи копии файла, вы могли бы записать файл поверх самого себя, например:

{
  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
  • 1-е решение: 1,25s
  • 2-е решение: 0,057s
  • 3-е решение: 0.48s

Все они работают по одному и тому же принципу: мы открываем два файловых дескриптора для файла, один в режиме только для чтения (0), используя , сокращенно от 0 и один в режиме чтения-записи (1) с использованием 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 .

10
29.04.2021, 00:50

Использование sed - хороший подход: Он понятен, он передает файл (нет проблем с длинными файлами), и его можно легко обобщить, чтобы сделать больше. Но если вам нужен простой способ редактировать файл на месте, проще всего использовать ed или ex:

(echo 10,31d; echo wq) | ed input.txt

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

perl -n -i -e 'print if $. < 10 || $. > 31' input.txt

Объяснение:

-n: Применить скрипт к каждой строке. Никакого другого вывода.
-i: Редактировать файл на месте (используйте -i.bck для создания резервной копии).
-e ... : Выведите каждую строку, кроме строк с 10 по 31.

6
29.04.2021, 00:50

Если вам нужно прочитать и записать 50 ГБ, это займет много времени, независимо от того, что вы делаете. И если строки не имеют фиксированной длины или у вас нет другого способа узнать, где находятся строки, которые нужно удалить, нет способа прочитать файл до последней удаляемой строки. Возможно, специальная программа, которая просто считает символы новой строки и позже копирует полные блоки, немного быстрее, чем sed (1) , но я считаю, что это не ваше узкое место. Попробуйте использовать время (1) , чтобы узнать, как распределяется время.

1
29.04.2021, 00:50

Это поможет?

perl -e '
           $num1 = 5;
           $num2= 10000;
           open IN,"<","input_file.txt";
           open OUT,">","output_file.txt";
           print OUT <IN> for (1 .. $num1-1)
           <IN> for ($num1 .. $num2);
           undef $/ and print OUT <IN>;
           close IN;
           close OUT;
          '

Это удалит все строки от 5 до 10000 включительно. Измените числа по своему усмотрению. Однако не вижу эффективного способа сделать это на месте (т.е. при таком подходе придется печатать в другой выходной файл).

0
29.04.2021, 00:50

Если вы хотите отредактировать файл на месте, большинство инструментов оболочки вам не помогут, потому что, когда вы открываете файл для записи, у вас есть выбор только обрезать его (> ) или добавление к нему ( >> ), не перезаписывая существующее содержимое. dd - заметное исключение. См. Есть ли способ изменить файл на месте?

export LC_ALL=C
lines_to_keep=$((linenum1 - 1))
lines_to_skip=$((linenum2 - linenum1 + 1))
deleted_bytes=$({ { head -n "$lines_to_keep"
                    head -n "$lines_to_skip" >&3;
                    cat
                  } <big_file | dd of=big_file conv=notrunc;
                } 3>&1 | wc -c)
dd if=/dev/null of=big_file bs=1 seek="$(($(wc -c <big_file) - $deleted_bytes))"

(Предупреждение: не проверено!)

0
29.04.2021, 00:50

вы можете добавить инструкцию * q * uit к вашей команде sed, когда будет достигнута nnennum2, чтобы sed прекратил обработку файла.

sed 'linenum1,linenum2d;linenum2q' file
-1
29.04.2021, 00:50

Обратите внимание, что это ответ на другой вопрос, отмеченный как повторяющийся.

Возник вопрос об удалении строки 4125889 из in.csv.

Вы можете делать что-то небезопасное - тогда вы можете действовать быстро, но можете потерять весь файл, или вы будете зависеть от скорости редактора, который вы используете.

Рекомендую:

echo '\ 0013 \ 0003y' | VED_FTMPFIR =. ved +4125878 in.csv

, где вам нужно в 3 раза больше размера файла и заканчиваются на in.csv и in.csv.bak

или:

echo '\ 0013 \ 0003! ' | VED_FTMPFIR =. ved +4125878 in.csv

, где вам нужно вдвое больше размера файла, и получившийся файл будет записан на место.

Обратите внимание, что вам нужна POSIX-совместимая реализация оболочки (echo) для правильного расширения escape-последовательностей. Редактор ved является частью инструментов schily и доступен по адресу:

http://sourceforge.net/projects/schilytools/files/

в schily - *. Tar.bz2

Он использует самый быстрый из известных мне файлов подкачки.

VED_FTMPFIR =. Среда устанавливает каталог для файла подкачки в текущий каталог. выберите любой каталог, в котором достаточно места.

-1
29.04.2021, 00:50

Это хорошо и просто:

perl -i -n -e 'print unless $.==13' /path/to/your/file

чтобы удалить, например, строку 13 из /path/to/your/file

0
29.04.2021, 00:50

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

ex -sc '1d2|x' input.txt
  1. 1 перейти к первой строке

  2. 2 выбрать 2 строки

  3. d удалить

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

1
29.04.2021, 00:50

В особом случае, когда содержимое строк, которые следует удалить, уникально в файле, другой вариант может использовать grep -v и содержимое строки а не номера строк.Например, если должна быть удалена только одна уникальная строка (удаление одной строки, например, было запрошено в этом дублированном потоке ), или несколько строк, которые все имеют одинаковое уникальное содержание.

Вот пример

grep -v "content of lines to delete" input.txt > input.tmp

Я провел несколько тестов с файлом, который содержит 340 000 строк. В этом случае метод grep кажется примерно в 15 раз быстрее, чем метод sed .

Вот команды и тайминги:

time sed -i "/CDGA_00004.pdbqt.gz.tar/d" /tmp/input.txt

real    0m0.711s
user    0m0.179s
sys     0m0.530s

time perl -ni -e 'print unless /CDGA_00004.pdbqt.gz.tar/' /tmp/input.txt

real    0m0.105s
user    0m0.088s
sys     0m0.016s

time (grep -v CDGA_00004.pdbqt.gz.tar /tmp/input.txt > /tmp/input.tmp; mv /tmp/input.tmp /tmp/input.txt )

real    0m0.046s
user    0m0.014s
sys     0m0.019s

Я пробовал как с параметром LC_ALL = C, так и без него, он не меняет тайминги. Строка поиска (CDGA_00004.pdbqt.gz.tar) находится где-то в середине файла.

1
29.04.2021, 00:50

Теги

Похожие вопросы