удаление строки на месте в полной файловой системе?

11

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

Я написал простую sedкоманду, чтобы проверить ошибочно добавленные строки и удалить их, и протестировал их на локальной копии файла. Это сработало как задумано.

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

sed: couldn't flush /path/to/file/sed8923ABC: No space left on deviceServerHostname

Конечно, я знаю, что места не осталось. Вот почему я пытаюсь удалить материал! (Команда, sedкоторую я использую, уменьшит размер файла 4000+ примерно до 90 строк.)

Моя sedкоманда простоsed -i '/myregex/d' /path/to/file/filename

Есть ли способ применить эту команду, несмотря на полный диск?

(Это должно быть автоматизировано, поскольку мне нужно быстро применить его к нескольким сотням серверов.)

(Очевидно, что ошибка приложения должна быть диагностирована, но в то же время серверы работают неправильно ....)


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

/tmpэто не ходят; это в той же файловой системе.

Прежде чем освободить место на диске, я проверил и обнаружил, что могу удалить строки vi, открыв файл и запустив его, :g/myregex/dа затем успешно сохранив изменения с помощью :wq. Похоже, можно автоматизировать это, не прибегая к отдельной файловой системе для хранения временного файла .... (?)

Wildcard
источник
Связанный: unix.stackexchange.com/q/75889/135943
Wildcard
1
sed -iсоздает временную копию для работы. Я подозреваю, что edбыло бы лучше для этого, хотя я не достаточно знаком, чтобы назначить фактическое решение
Эрик Ренуф
2
Когда edвы запустите: printf %s\\n g/myregex/d w q | ed -s infileно имейте в виду, что некоторые реализации также используют временные файлы, как sed(вы можете попробовать busybox ed - afaik, он не создает временный файл)
don_crissti
1
@Wildcard - ненадежно с / echo. использовать printf. и sedдобавьте несколько символов, которые вы уронили в последнюю строку, чтобы избежать потери пробелов. Кроме того, ваша оболочка должна иметь возможность обрабатывать весь файл в одной командной строке. это твой риск - сначала проверь. bashособенно плохо в этом (я думаю, что это делать с пространством стека?) и может надоесть вам в любое время. два sedрекомендованных si будут по крайней мере использовать буфер канала ядра для хорошего эффекта между ними, но метод довольно похож. ваша команда sub будет также усекать file, успешно ли sed w / in.
mikeserv
1
@Wildcard - попробуйте, sed '/regex/!H;$!d;x' <file|{ read v && cat >file;}и если это сработает, прочитайте остаток моего ответа.
mikeserv

Ответы:

10

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

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

Кроме того, если вы просто удаляете строки, которые соответствуют регулярному выражению, вы можете использовать grepвместо sed.

grep -v 'myregex' /path/to/filename > /tmp/filename && mv /tmp/filename /path/to/filename

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

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

file=$(< /path/to/filename)
echo "$file" | grep -v 'myregex' > /path/to/filename
Barmar
источник
1
Сохраняет ли он разрешения, права собственности и временные метки? Может быть , rsync -a --no-owner --no-group --remove-source-files "$backupfile" "$destination"от сюда
Хастура
@Hastur - ты имеешь в виду, что sed -iэто сохраняет?
mikeserv
2
@Hastur sed -iне сохраняет ничего из этого. Я просто попытался сделать это с файлом, который мне не принадлежит, но находится в каталоге, которым я владею, и он позволил мне заменить файл. Замена принадлежит мне, а не первоначальному владельцу.
Бармар
1
@ RalphRönnquist Чтобы быть уверенным, вам нужно сделать это в два этапа:var=$(< FILE); echo "$FILE" | grep '^"' > FILE
Barmar
1
@ Бармар - у вас ничего не получается - вы даже не знаете, что успешно открыли ввод. Очень крайней мере , вы могли бы сделать, v=$(<file)&& printf %s\\n "$v" >fileно вы даже не использовать &&. Аскер говорит о запуске его в сценарии - автоматизации перезаписи файла частью себя. Вы должны по крайней мере подтвердить, что вы можете успешно открыть ввод и вывод. Также оболочка может взорваться.
mikeserv
4

Вот как это sedработает. При использовании с -i(на месте редактирования) sedсоздает временный файл с новым содержимым обработанного файла. По окончании sedзаменяет текущий рабочий файл временным. Утилита не редактирует файл на месте . Это точное поведение каждого редактора.

Это похоже на выполнение следующей задачи в оболочке:

sed 'whatever' file >tmp_file
mv tmp_file file

В этот момент sedпытается сбросить буферизованные данные в файл, указанный в сообщении об ошибке, с помощью fflush()системного вызова:

Для выходных потоков fflush()- принудительная запись всех буферизованных данных пользовательского пространства для данного выходного потока или потока обновления с помощью базовой функции записи потока.


Для вашей проблемы я вижу решение в монтировании отдельной файловой системы (например, a tmpfs, если у вас достаточно памяти или внешнего устройства хранения данных) и перемещении туда некоторых файлов, их обработке и перемещении назад.

хаос
источник
3

С момента публикации этого вопроса я узнал, что exэто POSIX-совместимая программа. Это почти универсальная символическая ссылка vim, но в любом случае следующее (я думаю) является ключевым моментом exв отношении файловых систем (взято из спецификации POSIX):

В этом разделе термин « буфер редактирования» используется для описания текущего рабочего текста. Никакой конкретной реализации не подразумевается этим термином. Все изменения редактирования выполняются в буфере редактирования, и никакие изменения в нем не должны влиять на любой файл, пока команда редактора не запишет файл.

«... повлияет на любой файл ...» Я считаю, что помещение чего-либо в файловую систему (вообще, временный файл) будет считаться «воздействием на любой файл». Может быть?*

Тщательное изучение спецификаций POSIX дляex указания некоторых «ошибок» в отношении его предполагаемого переносимого использования по сравнению с обычными сценариями использования exнайденных в сети (которые усеяны специальными vimкомандами).

  1. Реализация +cmdявляется необязательной в соответствии с POSIX.
  2. Разрешение нескольких -cвариантов также необязательно.
  3. Глобальная команда :g«съедает» все до следующей неэкранированной новой строки (и, следовательно, запускает ее после каждого совпадения, найденного для регулярного выражения, а не один раз в конце). Таким образом, -c 'g/regex/d | x'удаляется только один экземпляр, а затем выходит из файла.

Итак, в соответствии с тем, что я исследовал, POSIX-совместимый метод для редактирования на месте файла в полной файловой системе для удаления всех строк, соответствующих определенному регулярному выражению:

ex -sc 'g/myregex/d
x' /path/to/file/filename

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

* Если вы найдете что-то, что указывает на обратное, пожалуйста, укажите это в комментариях.

Wildcard
источник
2
но ex пишет в tmpfiles ... всегда. его спецификация периодически записывает свои буферы на диск. Существуют даже специальные команды для поиска буферов файлов tmp на диске.
mikeserv
@Wildcard Спасибо за то, что поделились, я ссылался на аналогичный пост в SO . Я полагаю ex +g/match/d -scx file, POSIX-совместимый?
Кенорб
@kenorb, не совсем, согласно моим прочтениям спецификаций - см. мой пункт 1 в ответе выше. Точная цитата из POSIX: «Утилита ex должна соответствовать Руководству по синтаксису утилиты XBD, за исключением неуказанного использования« - », и что « + » может распознаваться как разделитель опций, так и« - »."
Wildcard
1
Я не могу доказать это, кроме как путем обращения к здравому смыслу, но я полагаю, что вы читаете больше в это утверждение из спецификации, чем на самом деле. Я полагаю, что более безопасная интерпретация заключается в том, что никакие изменения в буфере редактирования не должны влиять ни на один файл, который существовал до начала сеанса редактирования, или на имя пользователя. Смотрите также мои комментарии на мой ответ.
G-Man говорит: «Восстановите Монику»
@ G-Man, я действительно думаю, что ты прав; моя первоначальная интерпретация была, вероятно, желаемое за действительное. Тем не менее, поскольку редактирование файла vi работало на полной файловой системе, я считаю, что в большинстве случаев оно также будет работать, exхотя, возможно, не для огромного файла. sed -iне работает на полной файловой системе независимо от размера файла.
Wildcard
2

Используй трубу, Люк!

Читать файл | фильтр | написать обратно

sed 's/PATTERN//' BIGFILE | dd of=BIGFILE conv=notrunc

в этом случае sedне создает новый файл, а просто отправляет вывод по каналу, в ddкотором открывается тот же файл . Конечно, можно использовать grepв конкретном случае

grep -v 'PATTERN' BIGFILE | dd of=BIGFILE conv=notrunc

затем обрежьте оставшиеся.

dd if=/dev/null of=BIGFILE seek=1 bs=BYTES_OF_SED_OUTPUT
Лебен Глебен
источник
1
Заметили ли вы часть вопроса "полная файловая система" ?
Уайлдкарт
1
@Wildcard, sedвсегда использует временные файлы? grepво всяком случае не будет
Лебен Глебен
Это кажется альтернативой spongeкоманде. Да, sedс -ililke всегда создает файлы "seduyUdmw" с 000 прав.
Пабло А
1

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

Попробуйте это (в приглашении вашей интерактивной оболочки):

$ ed / path / to / file / filename
п
г / мирегекс / д
вес
Q

P(Который является столицей P) не является строго необходимым. Включает подсказку; без этого вы работаете в темноте, и некоторые люди находят это сбивающим с толку. wИ qявляются ж обрядовым и д ПИФ.

edславится загадочной диагностикой. Если в какой-то момент он отображает что-либо иное, чем подсказка (которая есть *) или что-то, что явно является подтверждением успешной операции ( особенно если оно содержит ?), не пишите файл (с помощью w). Просто выйдите ( q). Если вас это не отпустит, попробуйте qповторить.

Если ваш /tmpкаталог находится в файловой системе, которая заполнена (или если его файловая система также заполнена), попробуйте найти где-нибудь место. хаос упомянул монтирование tmpfs или внешнего запоминающего устройства (например, флешки); но если у вас есть несколько файловых систем, и они не все заполнены, вы можете просто использовать одну из других существующих. хаос предлагает скопировать файл (ы) в другую файловую систему, отредактировать их там (с помощью sed) и затем скопировать обратно. На данный момент, это может быть самым простым решением. Но альтернативой может быть создание доступного для записи каталога в файловой системе, в которой имеется некоторое свободное пространство, установка переменной среды TMPDIRдля указания на этот каталог и последующее выполнение ed. (Раскрытие: я не уверен, будет ли это работать, но это не повредит.)

Как только вы начнете edработать, вы можете автоматизировать это, выполнив

ред файла << EOF
г / мирегекс / д
вес
Q
EOF

в сценарии. Или , как предложено don_crissti.printf '%s\n' 'g/myregex/d' w q | ed -s filename

G-Man говорит: «Восстанови Монику»
источник
Хммм. Можно ли сделать то же самое (с помощью edили с ex), чтобы использовать память, а не отдельную файловую систему? Это то, к чему я действительно стремился (и причина, по которой я не принял ответ.)
Wildcard
Хм. Это может быть сложнее, чем я думал. Я изучал источник edмного лет назад. Были еще такие вещи, как 16-разрядные компьютеры, на которых процессы были ограничены адресным пространством размером 64 КБ (!), Поэтому идея редактора, считывающего весь файл в память, была неискусной. С тех пор, конечно, объем памяти увеличился - но и диски, и файлы тоже. Поскольку диски такие большие, люди не чувствуют необходимости иметь дело с нехваткой /tmpместа. Я просто быстро взглянул на исходный код недавней версии ed, и он все еще кажется… (продолжение)
G-Man говорит: «Восстановите Монику»
(Продолжение)… безоговорочно реализовать «буфер редактирования» как временный файл, и я не могу найти никаких указаний на то, что какая-либо версия ed(или exили vi) предлагает возможность сохранить буфер в памяти.  С другой стороны, Редактирование текста с помощью ed и vi - Глава 11: Обработка текста - Часть II: Изучение Red Hat Linux - Профессиональные секреты Red Hat Linux 9 - Системы Linux говорят, что edбуфер редактирования находится в памяти,… (Продолжение )
G-Man говорит: «Восстановите Монику»
(Продолжение)… и обработка документов и набор документов в UNIX Баласубраманиам Сринивасан говорит о том же vi( о том же, что и программа ex). Я считаю, что они просто используют неаккуратную, неточную формулировку - но, если она есть в Интернете (или в печати), это должно быть правдой, верно? Вы платите свои деньги и делаете выбор.
G-Man говорит: «Восстановите Монику»
Но в любом случае я добавил новый ответ.
G-Man говорит: «Восстановите Монику»
1

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

o=$(sed -ne'/regex/q;p' <file|wc -c)
dd if=/dev/null of=file bs="$o" seek=1

Или, если вы ${TMPDIR:-/tmp}находитесь в другой файловой системе, возможно:

{   cut -c2- | sed "$script" >file
} <file <<FILE
$(paste /dev/null -)
FILE

Потому что (большинство) оболочки помещают свои документы здесь в удаленный временный файл. Это совершенно безопасно, если <<FILEдескриптор поддерживается от начала до конца и ${TMPDIR:-/tmp}имеет столько места, сколько вам нужно.

Оболочки, которые не используют временные файлы, используют каналы, и поэтому их использование небезопасно. Эти оболочки , как правило , ashпроизводные , такие как busybox, dash, BSD sh- zsh, bash, ksh, и Bourne оболочки, однако, все использовать временные файлы.

по-видимому, я написал небольшую программу оболочки в июле прошлого года, чтобы сделать что-то вроде этого


Если /tmpэто не жизнеспособно, то пока вы можете поместить файл в память что-то вроде ...

sed 'H;$!d;x' <file | { read v &&
sed "$script" >file;}

... в общем случае, по крайней мере, убедитесь, что файл был полностью буферизован первым sedпроцессом, прежде чем пытаться усечь файл ввода / вывода.

Более целенаправленным и эффективным решением может быть:

sed '/regex/!H;$!d;x' <file|{ read v && cat >file;}

... потому что это не будет беспокоить буферизацию строк, которые вы хотели удалить в любом случае.

Тест общего случая:

{   nums=/tmp/nums
    seq 1000000 >$nums
    ls -lh "$nums"
    wc -l  "$nums"
    sed 'H;$!d;x' <$nums | { read script &&  ### read always gets a blank
    sed "$script" >$nums;}
    wc -l  "$nums"
    ls -lh "$nums"
}

-rw-r--r-- 1 mikeserv mikeserv 6.6M Dec 22 20:26 /tmp/nums
1000000 /tmp/nums
1000000 /tmp/nums
-rw-r--r-- 1 mikeserv mikeserv 6.6M Dec 22 20:26 /tmp/nums
mikeserv
источник
Признаюсь, я раньше не читал ваш ответ подробно, потому что он начинается с неработоспособных (для меня) решений, которые включают количество байтов (различное для каждого из множества серверов) и /tmpкоторые находятся в одной файловой системе. Мне нравится ваша двойная sedверсия. Я думаю, что комбинация ответа Бармара и вашего ответа, вероятно, была бы лучшей, что-то вроде: myvar="$(sed '/myregex/d' < file)" && [ -n "$myvar" ] && echo "$myvar" > file ; unset myvar (Для этого случая меня не волнует сохранение завершающих строк.)
Wildcard
2
@Wildcard - это может быть. но вы не должны использовать оболочку как базу данных. sed| catвещь выше никогда не открывает вывод, если sedон уже не буферизовал весь файл и не готов начать запись всего этого для вывода. Если он пытается буфер файл и не - readне увенчались успехом , поскольку находки EOF на |трубе , прежде чем он читает свой первый символ новой строки и так cat >out никогда не бывает до своего времени , чтобы записать его из памяти целиком. переполнение или что-то подобное просто терпит неудачу. также весь конвейер каждый раз возвращает успех или неудачу. хранить его в переменной просто более рискованно.
mikeserv
@Wildcard - если бы я действительно хотел, чтобы это тоже было в переменной, я думаю, что id делает это так: file=$(sed '/regex/!H;$!d;x' <file | read v && tee file) && cmp - file <<<"$file" || shiteпоэтому выходной файл и переменная будут записываться одновременно, что создаст одно или эффективное резервное копирование, и это единственная причина, по которой вы хотите усложнять вещи дальше, чем нужно.
mikeserv
@mikeserv: Я сейчас занимаюсь той же проблемой, что и ОП, и считаю ваше решение действительно полезным. Но я не понимаю использование read scriptи read vв вашем ответе. Если вы можете подробнее рассказать об этом, я буду очень признателен, спасибо!
Sylye
1
@sylye - $scriptэто sedскрипт, который вы бы использовали для нацеливания на любую часть вашего файла, которую вы хотели; это скрипт, который дает вам конечный результат, который вы хотите в потоке. vэто просто заполнитель для пустой строки. в bashоболочке это не является необходимым, поскольку вместо него bashбудет автоматически использоваться $REPLYпеременная оболочки, если вы ее не указали, но POSIXly всегда следует делать так. я рад, что вы нашли это полезным, кстати. удачи с этим. Я mikeserv @ Gmail, если вам нужно что-нибудь в глубине. через несколько дней у меня должен снова быть компьютер
mikeserv
0

Этот ответ заимствует идеи из этого другого ответа и этого другого ответа, но основывается на них, создавая ответ, который более применим:

num_bytes = $ (sed '/ myregex / d' / path / to / file / filename | wc -c)
sed '/ myregex / d' / путь / к / файлу / имени файла 1 <> / путь / к / файлу / имени файла 
dd if = / dev / null of = / путь / к / файлу / имени файла bs = "$ num_bytes" seek = 1

Первая строка запускает sedкоманду с выводом, записанным в стандартный вывод (а не в файл); в частности, на трубу, wcчтобы считать персонажей. Во второй строке также выполняется sedкоманда с выводом, записанным в стандартный вывод, который в этом случае перенаправляется во входной файл в режиме чтения / записи с перезаписью (без усечения), который обсуждается здесь . Это довольно опасная вещь; это безопасно только тогда, когда команда фильтра никогда не увеличивает объем данных (текст); то есть для каждого n байтов, которые он читает, он пишет n или меньше байтов. Это, конечно, верно для sed '/myregex/d'команды; для каждой строки, которую он читает, он пишет ту же самую строку, или ничего. (Другие примеры:s/foo/fu/или s/foo/bar/было бы безопасно, но s/fu/foo/и s/foo/foobar/не будет.)

Например:

$ cat filename
It was
a dark and stormy night.
$ sed '/was/d' filename 1<> filename
$ cat filename
a dark and stormy night.
night.

потому что эти 32 байта данных:

I  t     w  a  s \n  a     d  a  r  k     a  n  d     s  t  o  r  m  y     n  i  g  h  t  . \n

был перезаписан этими 25 символами:

a     d  a  r  k     a  n  d     s  t  o  r  m  y     n  i  g  h  t  . \n

оставив семь байтов, night.\nоставшихся в конце.

Наконец, ddкоманда ищет до конца новые очищенные данные (байт 25 в этом примере) и удаляет остальную часть файла; то есть, он усекает файл в этой точке.


Если по какой-либо причине 1<>трюк не работает, вы можете сделать

sed '/ myregex / d' / path / to / file / filename | dd of = / путь / к / файлу / имени файла conv = notrunc

Кроме того, обратите внимание, что, пока все, что вы делаете, это удаление строк, все, что вам нужно - это grep -v myregex(как указал Barmar ).

G-Man говорит: «Восстанови Монику»
источник
-3

sed -i 'd' / путь / к / файлу / имени файла

Chiranjeeb
источник
1
Здравствуй! Было бы лучше объяснить как можно более подробно, насколько важно, как ваше решение работает и отвечает на вопрос.
Дхаг
2
Это ужасный не ответ. (а) он не будет работать в полной файловой системе, как моя первоначальная команда; (б) Если это действительно удастся, он очистит файл ВЕСЬ, а не только строки, соответствующие моему регулярному выражению.
Подстановочный