Эффективно удалить первую пару строк из текстового файла

umask является глобальным в bash . Вы можете создать оболочку mkdir (сценарий, вы даете ему имя), которая изменяла бы маску после его выполнения.

#!/bin/bash
umask 0701 ; /path/to/real/mkdir $1 ; umask 0604

Здесь ответили:

Помните: для каталогов базовые разрешения ( rwxrwxrwx ) 0777 и для файлы это 0666 , что означает, что вы не получите разрешения на выполнение при создании файла внутри вашей оболочки, даже если umask позволяет. Это явно сделано для повышения безопасности при создании новых файлов .

Пример:

[admin@host test]$ pwd
/home/admin/test
[admin@host test]$ umask
0002
[admin@host test]$ mkdir test
[admin@host test]$ touch test_file
[admin@host test]$ ls -l
total 4
drwxrwxr-x 2 admin admin 4096 Jan 13 14:53 test
-rw-rw-r-- 1 admin admin    0 Jan 13 14:53 test_file

umask Спецификация Unix ничего не говорит о математических особенностях прав доступа к файлу. Это решать разработчикам оболочки (и производителям ОС).

1
18.05.2017, 02:48
3 ответа

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

0
27.01.2020, 23:11

Жиль меня опередил: нет «указателя, указывающего на первую строку файла». Первая строка файла — начало файла — всегда является первым символом файла. (Могут быть неясные, отдельные приложения которые признают такое понятие, но на системном уровне ничего подобного нет.)

Что вы уже знаете:

Команды вида

  • sed '1,6d' имя файла
  • sed -n '7,$p' имя файла
  • tail -n +7 имя файла

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

  • sed -n '1,6p' имя файла
  • sed '7,$d' имя файла
  • head -n 6 имя файла
  • sed '6q' имя_файла

запишет первые 6 строк из имя_файла в стандартный вывод. Первые два могут прочитать или не прочитать весь файл; последние два наверное не будет.

Кроме того,

command input_filename > the_same_filename
не работает, как обсуждалось в Предупреждение относительно «>».

Возможно, вы не знали:

command arguments    1<> filename

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

sed '1,6d' filename  1<> the_same_filename
может быть первым шагом к решению, которое вы ищете. Это, вероятно, так близко, как вы собираетесь прийти к удалению первых M строк файла «на месте»; он будет читать файл и перезаписывать его одновременно, без создания другого файла. Если M достаточно мало (или, в частности, если количество байтов в первых M строках достаточно мало), это может прочитать каждый блок файла один раз и записать каждый блок один раз - и вы не можете сделать ничего лучше, чем это.

Только первый шаг?

Я создал этот тестовый файл:

$ cat -n foo
     1  a
     2  bcd
     3  efghi
     4  jklmnop
     5  qrstuvwxy
     6  z0123456789
     7  ABCDEFGHIJKLM
     8  Once upon a midnight dreary, while I pondered, weak and weary,
     9  Over many a quaint and curious volume of forgotten lore—
    10  While I nodded, nearly napping, suddenly there came a tapping,
    11  As of some one gently rapping—rapping at my chamber door.
    12  "'Tis some visitor," I muttered, "tapping at my chamber door—
    13                                    Only this and nothing more."
    14  The quick brown
    15  fox jump over the
    16  lazy dog. Once upon
    17  this midnight dreary,

Этот файл тщательно сконструирован чтобы длины строк (включая новые строки) 2, 4, 6, 8, 10, 12, 14, 63, 57, 63, 58, 62, 63, 16, 18, 20, и 22. Обратите внимание, что первые шесть строк содержат 2+4+6+8+10+12=42 байта. Последние две строки содержат 20+22 байта, что по совпадению (!) тоже 42. (Общий размер файла 504.) Итак,

$ ls -l foo
-rw-r--r-- 1 myusername mygroupname 504 May 18 04:25 foo

$ sed '1,6d' foo 1<> foo

$ ls -l foo
-rw-r--r-- 1 myusername mygroupname 504 May 18 04:32 foo

$ cat -n foo
     1  ABCDEFGHIJKLM
     2  Once upon a midnight dreary, while I pondered, weak and weary,
     3  Over many a quaint and curious volume of forgotten lore—
     4  While I nodded, nearly napping, suddenly there came a tapping,
     5  As of some one gently rapping—rapping at my chamber door.
     6  "'Tis some visitor," I muttered, "tapping at my chamber door—
     7                                    Only this and nothing more."
     8  The quick brown
     9  fox jump over the
    10  lazy dog. Once upon
    11  this midnight dreary,
    12  lazy dog. Once upon
    13  this midnight dreary,

Хорошо, первые шесть строк исчезли. Исходная строка номер 7 («ABCDEFGHIJKLM») теперь является строкой номер 1. Но что это? Файл увеличился с 17 строк до 13. Должно быть 11 (17−6). А последние две строчки («ленивая собака… полночная тоска») встречаются дважды.

Это одна из ловушек оператора 1<> — если вы не обрезаете выходной файл, вы не можете получить файл меньшего размера, чем тот, с которого вы начали. В частности, здесь вывод sed '1,6d' foo составляет 462 байта. (504−42, так как первые шесть строк содержат 42 байта), поэтому он перезаписывает первые 462 байта выходного файла — который также является foo. И первые 462 байта foo — это все, кроме последних 42 (504−462) — поэтому последние две строки не перезаписываются. Две копии последних двух строк («ленивая собака… полночная тоска») являются результатом sed, за которым следует тот, который остался от исходного содержимого файла.

Итак, что дальше?

Все, что нам нужно сделать сейчас, это отбросить последние 42 байта файла. Как оказалось, это можно сделать просто перемещая указатель, указывающий на конец файла. Хорошо, на самом деле это не указатель; это целочисленный размер файла — горшок Ато, горшок Ато. За последние 20 или 30 лет, Unix позволяет обрезать файл до нужного размера, оставив данные до этого момента нетронутыми, и отбрасывание данных после этой точки.

Старая команда, которая сделает это, называется

dd if=/dev/null bs=462 seek=1 of=foo 2> /dev/null

, которая копирует /dev/null поверх foo, начиная с байта 462. Да, это какой-то клуге. Более новая команда, выполняющая эту функцию, —

truncate -s 462 foo

. Она может присутствовать не во всех системах; это не указано в POSIX.

Итак, собирая все вместе,

#!/bin/sh
filename="$1"
bytes_to_remove=$(sed '6q' "$filename" | wc -c)
total_size=$(stat -c '%s' "$filename")
sed '1,6d' "$filename" 1<> "$filename"
new_size=$((total_size - bytes_to_remove))
truncate -s "$new_size" "$filename"

Мы используем wc -c для подсчета символов в первых шести строках. (созданный sed '6q'), вычтите это из общего размера файла, и обрезать файл до этого размера.Вы можете использовать любую из альтернативных команд для вывода первых M строк или последних N−M строк, и вы можете заменить последнюю строку на

dd if=/dev/null bs="$new_size" seek=1 of="$filename" 2> /dev/null

Предостережения:

Я не проверял это на файлах с окончаниями строк

  • CR-LF или
  • многобайтовыми символами,

, и это может быть проблематично.

4
27.01.2020, 23:11

Why not just move the pointer that points to the first line of the file and move it to the line that we want?

Потому что не существует такого понятия, как «указатель, указывающий на первую строку файла».

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

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

Самый эффективный способ удалить данные в начале файла — скопировать данные, которые необходимо сохранить, в новый файл. Именно это и делают tail -n +42или sed '41,$p'.

¹ Современные системы Linux имеют системный вызов для удаления части файла :fallocate(fd, FALLOC_FL_COLLAPSE_RANGE, …), который можно вызвать с помощью утилитыfallocate --collapse-range=…. Также есть FALLOC_FL_INSERT_RANGEи --insert-range. Но они ограничены блоками, что делает их практически бесполезными для текстовых файлов, и они доступны не для всех файловых систем.

6
27.01.2020, 23:11

Теги

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