sed отредактируйте файл на месте

300

Я пытаюсь выяснить, возможно ли отредактировать файл в одной команде sed без потоковой передачи отредактированного содержимого вручную в новый файл, а затем переименовать новый файл в исходное имя файла. Я попробовал -iвариант, но моя система Solaris сказала, что -iэто незаконный вариант. Есть ли другой способ?

amphibient
источник
7
-iэто опция в GNU SED, но не в стандартном SED. Тем не менее, он передает содержимое в новый файл, а затем переименовывает файл, так что это не то, что вы хотите.
Уильям Перселл
2
на самом деле, это то, что я хочу, я просто хочу, чтобы мне не пришлось выполнять обычную задачу переименования нового файла в исходное имя
амфибия
3
Тогда вам нужно переформулировать вопрос.
Уильям Перселл
3
@amphibient: Вы не возражаете против добавления названия вопроса к слову «Solaris»? Ценность вашего вопроса теряется. Пожалуйста, смотрите комментарии ниже моего ответа. Спасибо.
Стив
1
@Steve: я снова удалил префикс Solaris из заголовка, потому что он ни в коем случае не является эксклюзивным для Solaris.
tripleee

Ответы:

477

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

Пример:

sed -i 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

и

sed -i '' 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

на macOS .

choroba
источник
3
по крайней мере, он делает это для меня, так что мне не нужно
амфибия
2
Тогда вы можете попытаться скомпилировать GNU sed в своей системе.
Чороба
3
пример:sed -i "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>
Thales Ceolin
2
Это лучше, чем решение Perl. Каким-то образом это не создает .swap файл .... спасибо!
математика
3
@ maths: Да, но, может быть, где-то еще или на более короткое время?
Чороба
72

В системе, где sedнет возможности редактировать файлы, я думаю, что лучшим решением было бы использовать perl:

perl -pi -e 's/foo/bar/g' file.txt

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

Стив
источник
14
Он не только создает временный файл, он также разрывает жесткие ссылки (например, вместо изменения содержимого файла он заменяет файл новым файлом с тем же именем). Иногда это желаемое поведение, иногда допустимо, но когда есть несколько ссылок на файл, это почти всегда неправильное поведение.
Уильям Перселл
3
@DwightSpencer: Я считаю, что все системы без Perl сломаны. Одна команда превосходит цепочку команд, когда требуется повышенное разрешение и ее необходимо использовать sudo. На мой взгляд, вопрос заключается в том, как избежать создания и переименования временного файла вручную без необходимости устанавливать последнюю версию GNU или BSD sed. Просто используйте Perl.
Стив
4
@PetrPeller: Действительно? Если вы внимательно прочитаете вопрос, вы поймете, что ОП пытается избежать чего-то вроде sed 's/foo/bar/g' file.txt > file.tmp && mv file.tmp file.txt. То, что редактирование на месте выполняет переименование с использованием временного файла, не означает, что он / она должен выполнить переименование вручную, когда опция недоступна. Существуют и другие инструменты, которые могут делать то, что он / она хочет, и Perl - очевидный выбор. Как это не ответ на оригинальный вопрос?
Стив
2
@a_arias: Ты прав; он сделал. Но он не может достичь того, чего хочет, используя Solaris sed. Вопрос был на самом деле очень хорошим, но его ценность уменьшилась людьми, которые либо не умеют читать справочные страницы, либо не могут быть обеспокоены фактическим чтением вопросов здесь, на SO.
Стив
4
@EdwardGarson: IIRC, Solaris поставляется с Perl, но без Python или Ruby. И, как я уже говорил, OP не может достичь желаемого результата с помощью Solaris sed.
Стив
56

Обратите внимание, что в OS X вы можете получить странные ошибки, такие как «неверный код команды» или другие странные ошибки при выполнении этой команды. Чтобы решить эту проблему, попробуйте

sed -i '' -e "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>

Это происходит потому , что на версию OSX из sed, то -iвариант ожидает extensionаргумент , так ваша команда на самом деле разобрана в качестве extensionаргумента , и путь к файлу интерпретируются как код команды. Источник: https://stackoverflow.com/a/19457213

diadyne
источник
1
Это еще одна странность OS X. К счастью, a brew install gnu-sedвместе с a PATHпозволит вам использовать GNU-версию sed. Спасибо за упоминание; определенный скребок головы.
Дуг
23

Следующее отлично работает на моем Mac

sed -i.bak 's/foo/bar/g' sample

Мы заменяем foo на bar в файле примера. Резервная копия исходного файла будет сохранена в sample.bak

Для редактирования inline без резервного копирования, используйте следующую команду

sed -i'' 's/foo/bar/g' sample
minhas23
источник
Любой способ, как предотвратить сохранение файлов резервных копий?
Петр Пеллер
@PetrPeller, Вы можете использовать упомянутую команду для того же самого ... sed -i '' s / foo / bar / g 'sample
minhas23
18

Стоит отметить, что sedнельзя создавать файлы самостоятельно, поскольку единственная цель sed - выступать в качестве редактора «потока» (т. Е. Конвейеров stdin, stdout, stderr и других >&nбуферов, сокетов и т. П.). Имея это в виду, вы можете использовать другую команду, teeчтобы записать вывод обратно в файл. Другим вариантом является создание патча из конвейера контента diff.

Тройник метод

sed '/regex/' <file> | tee <file>

Патч метод

sed '/regex/' <file> | diff -p <file> /dev/stdin | patch

ОБНОВИТЬ:

Также обратите внимание, что patch исправит файл из строки 1 вывода diff:

Патчу не нужно знать, к какому файлу обращаться, так как он находится в первой строке вывода diff:

$ echo foobar | tee fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin
*** fubar   2014-03-15 18:06:09.000000000 -0500
--- /dev/stdin  2014-03-15 18:06:41.000000000 -0500
***************
*** 1 ****
! foobar
--- 1 ----
! fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin | patch
patching file fubar
Дуайт Спенсер
источник
2
/dev/stdin 2014-03-15, ответил 7 января - ты путешественник во времени?
Адриан Фрювирт
В Windows, использующей msysgit, /dev/stdinне существует, поэтому вы должны заменить /dev/stdinна '-', один дефис без кавычек, поэтому должны работать следующие команды: $ sed 's/oo/u/' fubar | diff -p fubar -и$ sed 's/oo/u/' fubar | diff -p fubar - | patch
Jim Raden
@ AdrianFrühwirth У меня есть синяя полицейская коробка где-то рядом, и по какой-то причине они называют меня Доктором на работе. Но, честно говоря, похоже, что в то время в системе действительно дрейфуют часы.
Дуайт Спенсер
Эти решения смотрят на меня, полагаясь на определенное поведение буферизации. Я подозреваю, что для больших файлов они могут сломаться, так как во время чтения sed файл может начать меняться снизу командой patch, или, что еще хуже, командой tee, которая усекает файл. На самом деле я не уверен, если patchне будет усекать также. Я думаю, что сохранение вывода в другой файл и затем cat-ing в оригинал будет безопаснее.
Акостадинов
реальный ответ на месте от @ william-pursell
akostadinov
11

Невозможно делать то, что вы хотите с sed. Даже версии, sedкоторые поддерживают -iвозможность редактирования файла на месте, делают именно то, что вы явно заявили, что вы не хотите: они записывают во временный файл, а затем переименовывают файл. Но, возможно, вы можете просто использовать ed. Например, чтобы изменить все вхождения fooв barв файле file.txt, вы можете сделать:

echo ',s/foo/bar/g; w' | tr \; '\012' | ed -s file.txt

Синтаксис похож на sed, но, конечно, не совсем то же самое.

Но, возможно, вам следует подумать, почему вы не хотите использовать переименованный временный файл. Даже если у вас нет -iподдержки sed, вы можете легко написать сценарий, который сделает всю работу за вас. Вместо этого sed -i 's/foo/bar/g' fileвы могли бы сделать inline file sed 's/foo/bar/g'. Такой сценарий тривиально написать. Например:

#!/bin/sh -e
IN=$1
shift
trap 'rm -f $tmp' 0
tmp=$( mktemp )
<$IN "$@" >$tmp && cat $tmp > $IN  # preserve hard links

должно быть достаточно для большинства применений.

Уильям Перселл
источник
1
Так, например, как бы вы назвали этот сценарий и как бы вы назвали его?
амфибия
2
Я бы назвал это inlineи вызвал это, как описано выше:inline inputfile sed 's/foo/bar/g'
Уильям Перселл
1
вау, edтолько ответ, который действительно на месте. Первый раз, когда мне нужно ed. Спасибо. Небольшая оптимизация заключается в использовании echo -e ",s/foo/bar/g\012 w"или echo $',s/foo/bar/g\012 w'там, где shell позволяет избежать лишних trвызовов.
Акостадинов
2
edна самом деле не делает модификации на месте. Исходя из straceвывода, GNU edсоздает временный файл, записывает изменения во временный файл, а затем перезаписывает весь исходный файл, сохраняя жесткие ссылки. Исходя из trussвывода, Solaris 11 edтакже использует временный файл, но после сохранения с помощью wкоманды переименовывает временный файл в исходное имя файла , уничтожая любые жесткие ссылки.
Эндрю Хенле
@AndrewHenle Это обескураживает. Тот, edчто поставляется с текущими макосами (Mojave, что бы ни значил этот маркетинговый жаргон), все еще сохраняет жесткие ссылки. YMMV
Уильям Перселл
10

Вы могли бы использовать vi

vi -c '%s/foo/bar/g' my.txt -c 'wq'
Mench
источник
9

sed поддерживает редактирование на месте. От man sed:

-i[SUFFIX], --in-place[=SUFFIX]

    edit files in place (makes backup if extension supplied)

Пример :

Допустим, у вас есть файл hello.txtс текстом:

hello world!

Если вы хотите сохранить резервную копию старого файла, используйте:

sed -i.bak 's/hello/bonjour' hello.txt

В итоге вы получите два файла: hello.txtс содержимым:

bonjour world!

и hello.txt.bakсо старым содержанием.

Если вы не хотите сохранять копию, просто не передавайте параметр extension.

Серхио А.
источник
3
ОП особо упоминает, что его платформа не поддерживает эту (популярную, но) нестандартную опцию.
tripleee
3

Вы не указали, какую оболочку вы используете, но с помощью zsh вы можете использовать =( )конструкцию для достижения этой цели. Что-то вроде:

cp =(sed ... file; sync) file

=( )аналогично, >( )но создает временный файл, который автоматически удаляется при cpзавершении.

Тор
источник
3
mv file.txt file.tmp && sed 's/foo/bar/g' < file.tmp > file.txt

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

JJM
источник
3

Если вы заменяете одинаковое количество символов и после внимательного прочтения редактирования файлов на месте ...

Вы также можете использовать оператор перенаправления, <>чтобы открыть файл для чтения и записи:

sed 's/foo/bar/g' file 1<> file

Смотрите это в прямом эфире:

$ cat file
hello
i am here                           # see "here"
$ sed 's/here/away/' file 1<> file  # Run the `sed` command
$ cat file
hello
i am away                           # this line is changed now

Из Справочного руководства Bash → 3.6.10 Открытие файловых дескрипторов для чтения и записи :

Оператор перенаправления

[n]<>word

заставляет файл, имя которого является расширением слова, открываться как для чтения, так и для записи в файловом дескрипторе n или в файловом дескрипторе 0, если n не указано. Если файл не существует, он создается.

Федорки "ТАК прекратить вредить"
источник
Это повреждено файлом. Я не уверен в причине этого. paste.fedoraproject.org/462017
Нехал Дж. Вани
Смотрите строки [43-44], [88-89], [135-136]
Нехал Дж. Вани
2
Этот пост объясняет, почему этот ответ не должен использоваться. backreference.org/2011/01/29/in-place-editing-of-files
Nehal J WANI
@NehalJWani Я буду долго читать к статье, спасибо за заголовок! Что я не упомянул в ответе, так это то, что это работает, если он меняет одинаковое количество символов.
Федорки 'ТАК прекрати вредить'
@NehalJПри этом, извините, если мой ответ нанес вред вашим файлам. Я буду обновлять его с исправлениями (или удалять при необходимости) после прочтения предоставленных вами ссылок.
Федорки 'ТАК прекрати вредить'
2

Как Moneypenny сказал в Skyfall: «Иногда старые способы лучше». Кинкейд сказал нечто подобное позже.

$ printf ',s/false/true/g\nw\n' | ed {YourFileHere}

Приятного редактирования на месте. Добавлен \ nw \ n для записи файла. Извиняюсь за задержку ответа на запрос.

jlettvin
источник
Можете ли вы объяснить, что это делает?
Мэтт Монтег
1
Он вызывает ed (1), текстовый редактор с некоторыми символами, записанными в stdin. Как человек в возрасте до 30 лет, я думаю, что можно сказать, что ed (1) для динозавров, как для нас. В любом случае, вместо того, чтобы открывать ed и вводить этот материал, jlettvin использует символы для использования |. @MattMontag
Ари Свидлер
Если вы спрашиваете, что делают команды, есть две из них, оканчивающиеся на \n. [range] s / <old> / <new> / <flag> - это форма команды замены - парадигма, все еще встречающаяся во многих других текстовых редакторах (хорошо, vim, прямой потомок ed). В любом случае, gфлаг обозначает «глобальный» и означает «каждый экземпляр в каждой строке, на которую вы смотрите». Диапазон 3,5смотрит на линии 3-5. ,5смотрит на StartOfFile-5, 3,смотрит на строки 3-EndOfFile и ,смотрит на StartOfFile-EndOfFile. Глобальная команда подстановки в каждой строке, которая заменяет «false» на «true». Затем wвводится команда записи .
Ари Свидлер
1

Очень хорошие примеры. У меня была проблема с редактированием множества файлов, и опция -i, кажется, единственное разумное решение, использующее его в команде find. Вот скрипт для добавления «version:» перед первой строкой каждого файла:

find . -name pkg.json -print -exec sed -i '.bak' '1 s/^/version /' {} \;
Роберт
источник