Существует ли способ изменить оперативный файл?

Сначала необходимо подключиться, AFAIK CentOS 6 минимальный набор сетевое устройство к ONBOOT=No, просто сделайте a dhclient с административными привилегиями к Вашему сетевому интерфейсу и необходимо быть в порядке:

$ sudo dhclient

54
12.04.2011, 10:08
11 ответов

На уровне системного вызова это должно быть возможно. Программа может открыть Ваш конечный файл для записи, не усекая его и начать писать то, что это читает из stdin. При чтении EOF выходной файл может быть усеченным.

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

Однако находя программу, которая, это - проблема. dd(1) имеет опцию conv=notrunc это не усекает выходной файл на открытом, но он также не усекает в конце, оставляя исходное содержание файла после grep содержания (с командой как grep pattern bigfile | dd of=bigfile conv=notrunc)

Так как это очень просто с точки зрения системного вызова, я записал небольшую программу и протестировал ее в маленькой полной петлевой файловой системе (на 1 МиБ). Это сделало то, что Вы хотели, но Вы действительно хотите протестировать это с некоторыми другими файлами сначала. Это всегда будет опасной перезаписью файла.

overwrite.c

/* This code is placed in the public domain by camh */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, char **argv)
{
        int outfd;
        char buf[1024];
        int nread;
        off_t file_length;

        if (argc != 2) {
                fprintf(stderr, "usage: %s <output_file>\n", argv[0]);
                exit(1);
        }
        if ((outfd = open(argv[1], O_WRONLY)) == -1) {
                perror("Could not open output file");
                exit(2);
        }
        while ((nread = read(0, buf, sizeof(buf))) > 0) {
                if (write(outfd, buf, nread) == -1) {
                        perror("Could not write to output file");
                        exit(4);
                }
        }
        if (nread == -1) {
                perror("Could not read from stdin");
                exit(3);
        }
        if ((file_length = lseek(outfd, 0, SEEK_CUR)) == (off_t)-1) {
                perror("Could not get file position");
                exit(5);
        }
        if (ftruncate(outfd, file_length) == -1) {
                perror("Could not truncate file");
                exit(6);
        }
        close(outfd);
        exit(0);
}

Вы использовали бы его как:

grep pattern bigfile | overwrite bigfile

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

41
27.01.2020, 19:33
  • 1
    , который я хотел видеть, мог ли я уйти, не пишущий что-то для него!:) Я предполагаю, что это добьется цели!Спасибо! –  Nim 11.04.2011, 16:06
  • 2
    +1 для C; действительно кажется, работает, но я вижу потенциальную проблему: файл читается из левой стороны в то время, как право пишет в тот же файл и если Вы не координируете два процесса, у Вас были бы проблемы перезаписи потенциально на тех же блоках. Для целостности файлов могло бы быть лучше использовать меньший размер блока, так как большинство базовых инструментов будет, вероятно, использовать 8192. Это могло бы замедлить программу достаточно для предотвращения конфликтов (но не может гарантировать). Возможно, считайте большие части в память (не все) и запишите в меньших блоках. Мог также добавить наносон (2)/usleep (3). –  Arcege 11.04.2011, 16:15
  • 3
    @Arcege: Записи не делаются в блоках. Если Ваш процесс считывания считал 2 байта, и Ваш процесс записи пишет 1 байт, только первый байт изменится, и процесс считывания может продолжить читать на уровне байта 3 с исходным содержанием в той неизменной точке. С тех пор grep не произведет больше данных, чем они читают, положение записи должно всегда быть позади положения чтения. Даже если Вы запишете на том же уровне как чтение, то он все еще будет в порядке. Попробуйте rot13 этим вместо grep, и с другой стороны. md5sum прежде и после и Вы будете видеть, что это - то же. –  camh 11.04.2011, 16:45
  • 4
    Хороший. Это может быть ценным дополнением к moreutils Joey Hess. Можно использовать dd, но это является громоздким. –  Gilles 'SO- stop being evil' 12.04.2011, 00:24
  • 5
    'grep шаблон bigfile | перезаписывает bigfile' - я получил эту работу без ошибок, но что я не понимаю, - разве, требование не состоит в том, чтобы заменить то, что находится в шаблоне с некоторым другим текстом? так не был должен это быть чем-то как: 'шаблон grep bigfile | перезаписывает/replace-text/bigfile' –  Alexander Mills 25.12.2016, 23:14

Можно использовать sed для редактирования файлов на месте (но это действительно создает промежуточный временный файл):

Удалить все строки, содержащие foo:

sed -i '/foo/d' myfile

Сохранять все строки, содержащие foo:

sed -i '/foo/!d' myfile
20
27.01.2020, 19:33
  • 1
    , это будет работать временно потребность файла быть тем же размером как оригинал хотя? –  Nim 11.04.2011, 14:50
  • 2
    Да, таким образом, это, вероятно, бесполезно. –  pjc50 11.04.2011, 16:00
  • 3
    Это не то, что OP просит то, так как он создает второй файл. –  Arcege 11.04.2011, 16:15
  • 4
    Это решение перестанет работать в файловой системе только для чтения, где "только для чтения" средства что Ваш $HOME будет перезаписываемо, но /tmp будет только для чтения (по умолчанию). Например, если у Вас есть Ubuntu, и Вы загрузились в Консоль восстановления, это обычно имеет место. Кроме того, оператор здесь-документа <<< не будет работать там также, как это требует /tmp быть r/w, потому что это впишет во временный файл там также. (cf. этот вопрос, включая a strace'd вывод) –  syntaxerror 03.12.2014, 16:36
  • 5
    да, это не будет работать на меня также, все команды sed, которые я попробовал, заменит текущий файл новым файл (несмотря на - оперативный флаг). –  Alexander Mills 25.12.2016, 22:51

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

Большинство инструментов Unix только дает выбор добавления в файл или усечения его без возможности перезаписи его. Одно исключение на стандартной панели инструментов dd, которому можно сказать не усечь его выходной файл. Таким образом, план состоит в том, чтобы проникнуть команду в dd conv=notrunc. Это не изменяет размер файла, таким образом, мы также захватываем длину нового содержания и усекаем файл к той длине (снова с dd). Обратите внимание, что эта задача по сути неустойчива — если ошибка происходит, Вы самостоятельно.

export LC_ALL=C
n=$({ grep -v foo <big_file |
      tee /dev/fd/3 |
      dd of=big_file conv=notrunc; } 3>&1 | wc -c)
dd if=/dev/null of=big_file bs=1 seek=$n

Можно записать примерно эквивалентный Perl. Вот быстрая реализация, которая не пытается быть эффективной. Конечно, можно хотеть сделать начальную букву, фильтрующую непосредственно на том языке также.

grep -v foo <big_file | perl -e '
  close STDOUT;
  open STDOUT, "+<", $ARGV[0] or die;
  while (<STDIN>) {print}
  truncate STDOUT, tell STDOUT or die
' big_file
20
27.01.2020, 19:33

С любой оболочкой типа Борна:

{
  cat < bigfile | grep -v to-exclude
  perl -e 'truncate STDOUT, tell STDOUT'
} 1<> bigfile

По какой-то причине кажется, что люди склонны забывать об этом 40-летнем¹ и стандартном операторе перенаправления чтения+записи.

Мы открываем bigfile в режиме чтение+запись и (что здесь наиболее важно) без усечения на stdout, в то время как bigfile открывается (отдельно) на cat stdin. После того, как grep закончился, и если он удалил несколько линий, stdout теперь указывает где-то в пределах bigfile, нам нужно избавиться от того, что находится за этой точкой. Поэтому команда perl, которая усекает файл (усекает STDOUT) в текущей позиции (как возвращается командой tell STDOUT).

(параметр cat предназначен для GNU grep, который в противном случае будет жаловаться, если stdin и stdout указывают на один и тот же файл).


¹ Ну, хотя <> находилась в оболочке Борна с самого начала в конце семидесятых годов, первоначально она была недокументирована и не была реализована должным образом . Он не был в первоначальной реализации ash с 1989 года и, хотя это POSIX sh оператор перенаправления (с начала 90-х годов, так как POSIX sh основан на ksh88, который всегда им обладал), например, она не добавлялась во FreeBSD sh до 2000 года, так что портативно 15-летняя , вероятно, более точна. Также обратите внимание, что дескриптор файла по умолчанию, когда он не указан, это <> во всех оболочках, за исключением того, что в ksh93 в 2010 году он изменился с 0 до 1 в ksh93t+ (нарушение обратной совместимости и соответствия POSIX)

18
27.01.2020, 19:33

Хотя это старый вопрос, мне кажется, что это вечный вопрос, и доступно более общее и более четкое решение, чем предлагалось до сих пор . Кредит, если необходимо: я не уверен, что придумал бы его, не учитывая упоминание Стефана Шазеласа об операторе обновления <> .

Открытие файла для обновления в оболочке Bourne имеет ограниченную полезность. Оболочка не дает вам возможности искать в файле и устанавливать его новую длину (если она короче старой). Но это легко исправить, поэтому я удивлен, что ее нет среди стандартных утилит в / usr / bin .

Это работает:

$ grep -n foo T
8:foo
$ (exec 4<>T; grep foo T >&4 && ftruncate 4) && nl T; 
     1  foo

Как и это (подсказка Стефану):

$ { grep foo T && ftruncate; } 1<>T  && nl T; 
     1  foo

(Я использую GNU grep. Возможно, что-то изменилось с тех пор, как он написал свой ответ.)

За исключением того, что у вас нет ] / usr / bin / ftruncate . Пару десятков строк на C вы можете увидеть ниже. Эта утилита ftruncate усекает произвольный дескриптор файла до произвольной длины, по умолчанию используется стандартный вывод и текущая позиция.

Приведенная выше команда (1-й пример)

  • открывает файловый дескриптор 4 на T для обновления. Как и в случае с open (2), при открытии файла текущее смещение устанавливается на 0.
  • grep затем нормально обрабатывает T , а оболочка перенаправляет свой вывод на T через дескриптор 4.
  • ftruncate вызывает ftruncate (2) на дескрипторе 4, устанавливая длину равной текущему смещению (именно там, где grep оставил его).

Подоболочка завершается, закрывая дескриптор 4. Вот ftruncate :

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int
main( int argc, char *argv[] ) {
  off_t i, fd=1, len=0;
  off_t *addrs[2] = { &fd, &len };

  for( i=0; i < argc-1; i++ ) {
    if( sscanf(argv[i+1], "%lu", addrs[i]) < 1 ) {
      err(EXIT_FAILURE, "could not parse %s as number", argv[i+1]);
    }
  }

  if( argc < 3 && (len = lseek(fd, 0, SEEK_CUR)) == -1 ) {
    err(EXIT_FAILURE, "could not ftell fd %d as number", (int)fd);
  }


  if( 0 != ftruncate((int)fd, len) ) {
    err(EXIT_FAILURE, argc > 1? argv[1] : "stdout");
  }

  return EXIT_SUCCESS;
}

Обратите внимание, ftruncate (2) непереносится при таком использовании. Для абсолютной общности прочтите последний записанный байт, повторно откройте файл O_WRONLY, выполните поиск, запишите байт и закройте.

Учитывая, что этому вопросу 5 лет, я скажу, что это решение неочевидно.Он использует преимущества exec для открытия нового дескриптора и оператора <> , оба из которых являются загадочными. Я не могу придумать стандартную утилиту, которая манипулирует индексом дескриптора файла. (Синтаксис может быть ftruncate> & 4 , но я не уверен, что это улучшение.) Это значительно короче, чем компетентный исследовательский ответ camh. Это просто немного яснее, чем у Стефана, ИМО, если только вам не нравится Perl больше, чем мне. Надеюсь, кому-то это пригодится.

Другой способ сделать то же самое - исполняемая версия lseek (2), которая сообщает текущее смещение; вывод может быть использован для / usr / bin / truncate , который предоставляют некоторые Linuxi.

9
27.01.2020, 19:33

ed вероятно, правильный выбор отредактировать оперативный файл:

ed my_big_file << END_OF_ED_COMMANDS
g/foo:/d
w
q 
END_OF_ED_COMMANDS
5
27.01.2020, 19:33
  • 1
    мне нравится идея, но, если не отличающийся 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. –  Peter.O 14.04.2011, 20:34
  • 2
    @fred, если Вы подразумеваете, что сохранение изменений не будет влиять на именованный файл, Вы являетесь неправильными. Я интерпретирую ту кавычку, чтобы сказать, что Ваши изменения не отражаются, пока Вы не сохраняете их. Я действительно признаю это ed не gool решение для редактирования файлов на 35 ГБ, так как файл читается в буфер. –  glenn jackman 14.04.2011, 22:07
  • 3
    я думал, что это означало полный файл, будет загружен в буфер.. но возможно только раздел (разделы), в котором это нуждается, загружается в буфер.. Мне было любопытно на предмет редактора некоторое время... Я думал, что это могло сделать на месте редактирование... Я должен буду просто попробовать большой файл... Если это работает, это - разумное решение, но поскольку я пишу, я начинаю думать, что это может быть тем, что вдохновило sed (освобожденный от работы с большими блоками данных... Я заметил, что 'редактор' может на самом деле принять переданный потоком вход из сценария (снабженный префиксом ! ), таким образом, это может иметь несколько более интересных приемов его рукав. –  Peter.O 14.04.2011, 23:49
  • 4
    , в котором я вполне уверен операция записи ed усекает файл и переписывает его. Таким образом, это не изменит данные по диску, оперативному как требования OP. Кроме того, это не может работать, если файл является слишком большим, чтобы быть загруженным в памяти. –  Nick Matteo 14.04.2017, 20:08

Можно использовать дескриптор файла чтения-записи удара для открытия файла (для перезаписи его на месте), затем sed и truncate ... но конечно, никогда не позволяйте Вашим изменениям быть больше, чем чтение объема данных до сих пор.

Вот сценарий (использование: колотите переменный $BASHPID),

# Create a test file
  echo "going abc"  >junk
  echo "going def" >>junk
  echo "# ORIGINAL file";cat junk |tee >( wc=($(wc)); echo "# ${wc[0]} lines, ${wc[2]} bytes" ;echo )
#
# Assign file to fd 3, and open it r/w
  exec 3<> junk  
#
# Choose a unique filename to hold the new file size  and the pid 
# of the semi-asynchrounous process to which 'tee' streams the new file..  
  [[ ! -d "/tmp/$USER" ]] && mkdir "/tmp/$USER" 
  f_pid_size="/tmp/$USER/pid_size.$(date '+%N')" # %N is a GNU extension: nanoseconds
  [[ -f "$f_pid_size" ]] && { echo "ERROR: Work file already exists: '$f_pid_size'" ;exit 1 ; }
#
# run 'sed' output to 'tee' ... 
#  to modify the file in-situ, and to count the bytes  
  <junk sed -e "s/going //" |tee >(echo -n "$BASHPID " >"$f_pid_size" ;wc -c >>"$f_pid_size") >&3
#
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# The byte-counting process is not a child-process, 
# so 'wait' doesn't work... but wait we must...  
  pid_size=($(cat "$f_pid_size")) ;pid=${pid_size[0]}  
  # $f_pid_size may initially contain only the pid... 
  # get the size when pid termination is assured
  while [[ "$pid" != "" ]] ; do
    if ! kill -0 "$pid" 2>/dev/null; then
       pid=""  # pid has terminated. get the byte count
       pid_size=($(cat "$f_pid_size")) ;size=${pid_size[1]}
    fi
  done
  rm "$f_pid_size"
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#
  exec 3>&- # close fd 3.
  newsize=$(cat newsize)
  echo "# MODIFIED file (before truncating)";cat junk |tee >( wc=($(wc)); echo "# ${wc[0]} lines, ${wc[2]} bytes" ;echo )  cat junk
#
 truncate -s $newsize junk
 echo "# NEW (truncated) file";cat junk |tee >( wc=($(wc)); echo "# ${wc[0]} lines, ${wc[2]} bytes" ;echo )  cat junk
#
exit

Вот тестовый вывод

# ORIGINAL file
going abc
going def
# 2 lines, 20 bytes

# MODIFIED file (before truncating)
abc
def
c
going def
# 4 lines, 20 bytes

# NEW (truncated) file
abc
def
# 2 lines, 8 bytes
5
27.01.2020, 19:33

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

3
27.01.2020, 19:33
  • 1
    +1, но только потому, что широко распространенная доступность 64-разрядных центральных процессоров и Ose позволяет сделать это с файлом на 35 ГБ теперь. Те, которые все еще в 32-разрядных системах (подавляющее большинство даже аудитории этого сайта, я подозреваю) не смогут использовать это решение. –  Warren Young 14.04.2011, 19:21

echo -e "$(grep pattern bigfile)" >bigfile

-3
27.01.2020, 19:33
  • 1
    Это не работает, если файл является большим и grepped данные превышают длину того, что позволяет командная строка. это затем повреждает данные –  Anthon 15.12.2013, 13:30

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

# Reduce size from ~35Gb to ~6Gb
$ gzip MyFile

# Edit file, creating another ~6Gb file
$ gzip -dc <MyFile.gz | sed -e '/foo/d' | gzip -c >MyEditedFile.gz
2
27.01.2020, 19:33

Для тех, кто ищет в Google вопрос "как изменить файл в -месте?", правильный ответ в обычном случае будет остановить в поисках неясного функции оболочки, которые рискуют повредить ваш файл для незначительного повышения производительности, и вместо этого используют некоторые варианты этого шаблона:

grep "foo" file > file.new && mv file.new file

Только в чрезвычайно необычной ситуации , когда это по какой-то причине невозможно, следует серьезно рассмотреть любой из других ответов на этой странице (, хотя их, безусловно, интересно читать ). Я соглашусь, что загадка OP, связанная с отсутствием места на диске для создания второго файла, является именно такой ситуацией. Хотя даже тогда есть и другие варианты, например. предоставлено @Ed Randall и @Basile Starynkevitch.

3
27.01.2020, 19:33

Теги

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