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


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. Здається, це має бути можливо автоматизувати, не вдаючись до окремої файлової системи, щоб вмістити тимчасовий файл .... (?)



1
sed -iстворює тимчасову копію для роботи. Я підозрюю, що edдля цього було б краще, хоча я недостатньо знайомий, щоб передбачити фактичне рішення
Ерік Реноф

2
З edвами запустили: printf %s\\n g/myregex/d w q | ed -s infileале майте на увазі, що деякі реалізації також використовують тимчасові файли так само sed(ви можете спробувати
зайнятий

1
@Wildcard - не надійно w / echo. використання printf. і sedдодайте до останнього рядка якийсь шар, який ви скинете, щоб уникнути втрати проміжків. також ваша оболонка повинна вміти обробляти весь файл в одному командному рядку. це ваш ризик - спершу тестуйте. bashособливо погано в цьому (я думаю, це робити з / укладають простір?) і може захворіти на вас у будь-який час. два sedрекомендованих si принаймні використовувати буфер ядра труби для хорошого ефекту між ними, але метод досить схожий. ваша підкоманда команд також уріже, 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

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
@Barmar - у вас це не працює - ви навіть не знаєте, що ви успішно відкрили вхід. Дуже крайней мере , ви могли б зробити, v=$(<file)&& printf %s\\n "$v" >fileале ви навіть не використовувати &&. Розпитувач розмовляє про запуск його в сценарії - автоматизація перезапису файла з частиною самого себе. вам слід принаймні підтвердити, що ви можете успішно відкривати введення та вихід. Також оболонка може вибухнути.
mikeserv

4

Ось як sedпрацює. Якщо використовується з -i(in edit edit), sedстворюється тимчасовий файл з новим вмістом оброблюваного файлу. Закінчивши sed, замінює поточний робочий файл тимчасовим. Утиліта не редагує файл на місці . Саме така поведінка кожного редактора.

Це так, як ви виконуєте таке завдання в оболонці:

sed 'whatever' file >tmp_file
mv tmp_file file

У цей момент sedнамагається передати завантажені дані у файл, згаданий у повідомленні про помилку, із fflush()системним викликом:

Для вихідних потоків fflush()примушує записувати всі буферизовані дані в просторі користувача для даного виводу або потоку оновлення через основну функцію запису потоку.


Для вашої проблеми я бачу рішення в монтажі файлової системи separte (наприклад 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

Це повинно працювати, якщо у вас є достатня кількість пам'яті для завантаження файлу в буфер.

* Якщо ви знайдете щось, що вказує на інше, будь ласка, зазначте це у коментарях.


2
але екс пише в tmpfiles ... завжди. його специфіка періодично записує свої буфери на диск. Є навіть спеціальні команди для розміщення буферів файлів tmp на диску.
mikeserv

@Wildcard Дякую за те, що я поділився, я зв’язався ще на подібній публікації в SO . Я припускаю ex +g/match/d -scx file, що сумісний з POSIX?
kenorb

@kenorb, не зовсім, згідно з моїм читанням специфікацій - див. мою точку 1 у відповіді вище. Точна цитата POSIX - "Колишня утиліта повинна відповідати Правилам синтаксису XBD Utility Sync, за винятком не визначеного використання" - ", і " + " може бути розпізнана як роздільник опцій, а також" - "."
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
Ви помітили частину питання "повна файлова система" ?
Wildcard

1
@Wildcard, чи sedзавжди використовуються тимчасові файли? grepвсе одно не буде
Лебен Глебен

Це здається альтернативою spongeкомандуванню. Так, sedз -iзавжди створює файли lilke «seduyUdmw» з 000 прав.
Пабло А

1

Як зазначається в інших відповідях, sed -iпрацює, копіюючи файл у новий файл у тому самому каталозі , вносячи зміни в процесі, а потім переміщуючи новий файл над оригіналом. Ось чому це не працює.  ed(оригінальний редактор рядків) працює дещо подібним чином, але останній раз, коли я перевірив, він використовується /tmpдля файлу скретчів. Якщо ваша система /tmpперебуває в іншій файловій системі, ніж у повній, вона edможе виконати роботу за вас.

Спробуйте це (за запитом інтерактивної оболонки):

$ ed / шлях / до / файл / ім'я файлу
П
г / мірегекс / д
ш
q

P(Який є столицею P) не є строго необхідним. Увімкнено запит; без цього ви працюєте в темряві, і деякі люди вважають це бентежною. wІ qє ж обрядовим і д ПІФ.

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

Якщо у вашому /tmpкаталозі повна файлова система (або файлова система також повна), спробуйте десь знайти місце. хаос, згаданий про встановлення tmpfs або зовнішнього запам'ятовуючого пристрою (наприклад, флешки); але, якщо у вас є декілька файлових систем, і вони не всі повні, ви можете просто використовувати одну з інших існуючих. хаос пропонує скопіювати файли (файли) в іншу файлову систему, відредагувати їх там (з sed), а потім скопіювати їх назад. На даний момент це може бути найпростішим рішенням. Але альтернативою може бути створення каталогу, що записується, у файловій системі, яка має вільний простір, встановлення змінної середовища TMPDIRдля вказівки на цей каталог та запуску ed. (Розкриття інформації: я не впевнений, чи спрацює це, але це не може зашкодити.)

Після edроботи ви можете автоматизувати це, зробивши це

ed filename << EOF
г / мірегекс / д
ш
q
EOF

у сценарії. Або , як пропонує don_crissti.printf '%s\n' 'g/myregex/d' w q | ed -s filename


Хммм. Чи можна зробити те саме (з 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буфер редагування знаходиться в пам'яті,… (Cont'd )
G-Man каже: "Відновіть Моніку"

(Продовження)… і UNIX Обробка та набір документів Balasubramaniam Шрінівасан говорить про те саме 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

Зізнаюся, я раніше не докладно читав вашу відповідь, тому що вона починається з непрацездатних (для мене) рішень, що передбачають кількість байтів (різний серед кожного з багатьох серверів) і /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 - якщо я теж дуже хотів цього в змінній, я думаю, що це зробити так, як: file=$(sed '/regex/!H;$!d;x' <file | read v && tee file) && cmp - file <<<"$file" || shiteвихідний файл і var будуть записані одночасно, що зробить або ефективну резервну копію, і це єдина причина, яку ви хочете зробити ускладнюйте речі далі, ніж вам потрібно.
mikeserv

@mikeserv: Я маю справу з тією самою проблемою, що і зараз, і вважаю, що ваше рішення дуже корисне. Але я не розумію використання read scriptта read vу вашій відповіді. Якщо ви зможете детальніше про це детальніше, я буду дуже вдячний, дякую!
sylye

1
@sylye - $scriptце sedсценарій, який ви використовуєте для націлювання на ту частину файлу, яку ви хотіли; його сценарій, який дає вам кінцевий результат, який ви хочете в потоці. vце просто заповнювач порожнього рядка. в bashоболонці це не потрібно, тому що bashвін автоматично використовуватиме $REPLYзмінну оболонки замість неї, якщо ви її не вкажете, але POSIXly завжди слід це робити. я радий вам, до речі, корисним. удачі з цим. im mikeserv @ gmail, якщо вам потрібно щось глибше. я повинен мати комп’ютер знову через кілька днів
mikeserv

0

Ця відповідь запозичує ідеї з цієї іншої відповіді та цієї іншої відповіді, але спирається на них, створюючи відповідь, яка більш загальноприйнятна:

num_bytes = $ (sed '/ myregex / d' / path / to / file / filename) | wc -c)
sed '/ myregex / d' / path / to / file / filename 1 <> / path / to / file / filename 
dd if = / dev / null of = / path / to / file / filename bs = "$ num_bytes" шукати = 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' / шлях / до / файл / ім'я файлу | dd of = / path / to / file / filename conv = notrunc

Також зауважте, що поки ви все, що ви робите, - це видалення ліній, все, що вам потрібно, - це grep -v myregex(на що вказував Бармар ).


-3

sed -i 'd' / шлях / до / файл / ім'я файлу


1
Привіт! Найкраще було б пояснити якомога детальніше, як це важливо, як працює ваше рішення та відповідає на питання.
даг

2
Це страшна невідповідь. (а) не вдасться виконати повну файлову систему, як і моя оригінальна команда; (b) Якщо це все вдалося, він видалить файл WHOLE, а не просто рядки, що відповідають моєму регулярному вираженню.
Wildcard
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.