Как я могу добиться переносимости с помощью sed -i (редактирование на месте)?


42

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

Часть моего сайта использует сгенерированные статические HTML-файлы, и эта строка sed вставляет правильный DOCTYPE после каждой регенерации:

sed -i '1s/^/<!DOCTYPE html> \n/' ${file_name.html}

Он работает с GNU sedв Linux, но FreeBSD sedожидает, что первым аргументом после -iопции будет расширение для резервной копии. Вот как это будет выглядеть:

sed -i '' '1s/^/<!DOCTYPE html> \n/' ${file_name.html}

Однако GNU, sedв свою очередь, ожидает, что выражение последует сразу после -i. (Это также требует исправлений с обработкой новой строки, но здесь уже есть ответ )

Конечно, я могу включить это изменение в свою серверную копию скрипта, но это может испортить то, что я использую VCS для управления версиями. Есть ли способ добиться этого с помощью Sed в полностью портативном виде?


Предоставленные вами два фрагмента кода sed идентичны. Вы уверены, что опечатки нет? Кроме того, я могу выполнить GNU Sed, поставляя расширение для резервного копирования сразу после-i
iruvar

Да, спасибо, что заметили это. Я исправил свой вопрос. Вторая строка приводит к ошибке в моем sed, она ожидает, что '1s / ^ / <! DOCTYPE html> \ n /' будет файлом, и жалуется, что не может его найти.
Красный,

1
Перекрестная ссылка: флаг sed-in-place, который работает как на Mac (BSD), так и на Linux при переполнении стека.
MvG

Ответы:


41

GNU sed принимает необязательное расширение после -i. Расширение должно быть в том же аргументе без пробела. Этот синтаксис также работает на BSD sed.

sed -i.bak -e '…' SOMEFILE

Обратите внимание, что в BSD -iтакже изменяется поведение при наличии нескольких входных файлов: они обрабатываются независимо (например, $соответствуют последней строке каждого файла). Также это не будет работать на BusyBox.

Если вы не хотите использовать файлы резервных копий, вы можете проверить, какая версия sed доступна.

case $(sed --help 2>&1) in
  *GNU*) set sed -i;;
  *) set sed -i '';;
esac
"$@" -e '…' "$file"

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

case $(sed --help 2>&1) in
  *GNU*) sed_i () { sed -i "$@"; };;
  *) sed_i () { sed -i '' "$@"; };;
esac
sed_i -e '…' "$file"

Если вы не хотите беспокоиться, используйте Perl.

perl -i -pe '…' "$file"

Если вы хотите написать переносимый скрипт, не используйте -iего - его нет в POSIX. Делайте вручную то, что sed делает под капотом - это всего лишь еще одна строка кода.

sed -e '…' "$file" >"$file.new"
mv -- "$file.new" "$file"

2
GNU sed -iтакже подразумевает -s. И самый простой способ проверить наличие GNU sed- это sed vкоманда, которая является допустимым noop для GNU, но не работает везде.
mikeserv

Вдохновленный приведенными выше советами, вот однострочная (если некрасивая) портативная версия для тех, кто действительно хочет ее, хотя она порождает подоболочку: sed -i$(sed v < /dev/null 2> /dev/null || echo -n " ''") -e '...' "$file"если это не GNU sed, она вставляет пробел, за которым следуют две одинарные кавычки, -iтак что работает на BSD. Достается только GNU sed -i.
Иван Икс

2
@IvanX Я опасаюсь использовать наличие vкоманды для тестирования на GNU sed. Что если FreeBSD решит это реализовать?
Жиль "ТАК - перестань быть злым"

@ Жиль Справедливо, но справочная страница для GNU sed описывает v как предназначенное именно для этой цели (обнаруживая, что это GNU sed, а не что-то еще), поэтому можно надеяться, что * BSD выполнит это. Я не могу придумать другого теста, не требующего никаких действий, для GNU sed, но вызывающего ошибку в BSD sed (или наоборот), кроме использования самого -i, но для этого сначала нужно создать фиктивный файл. Ваш тест на sed выше в порядке, но громоздкий для inline. Исключение -i полностью, как вы предлагаете, определенно кажется самой безопасной ставкой, но я согласен с тем, sed vчто это его цель для существования.
Иван Икс

1
@lapo Альтернативой является определение функции, см. мое редактирование.
Жиль "ТАК - перестань быть злым"

10

Если вы не нашли способ сделать sedигру приятной, вы можете попробовать:

  1. Не используйте -i:

    sed '1s/^/<!DOCTYPE html> \n/' "${file_name.html}" > "${file_name.html}.tmp" &&
      mv "${file_name.html}.tmp" "${file_name.html}"
  2. Используйте Perl

    perl -i -pe 'print "<!DOCTYPE html> \n" if $.==1;' "${file_name.html}"

8

издание

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

$ printf '0a\n<!DOCTYPE html>\n.\nw\n' | ed my.html

Детали

Биты вокруг <!DOCTYPE html>являются командами, которые дают ему edкоманду добавить эту строку в файл my.html.

СЭД

Я считаю, что эту команду sedтакже можно использовать:

$ sed -i '1i<!DOCTYPE html>\n` testfile.csv

В конце концов я прибег к Perl, но использование ed - хорошая альтернатива, которая не так популярна среди пользователей, подобных Unix, как и должно быть.
Красный

@Red - рад слышать, что вы решили свою проблему. Да, я этого раньше не видел, прибегая к помощи поисковиков, он казался мне самым портативным и удачным способом сделать это.
СЛМ

7

Вы также можете сделать вручную, что perl -iделает под капотом:

{ rm -f file && { echo '<!DOCTYPE html>'; cat; } > file;} < file

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

С:

sed '1i\
<!DOCTYPE html>' file 1<> file

sedперезаписывает файл поверх самого себя, поэтому не влияет на владение и разрешения или символические ссылки. Он работает с GNU, sedпотому sedчто обычно будет читать буфер, полный данных file(в моем случае 4k), перед тем как перезаписать его iкомандой. Это не сработало бы, если бы файл был больше 4k, за исключением того факта, что он sedтакже буферизировал его вывод.

В основном sedработает на блоках по 4к для чтения и записи. Если строка для вставки меньше, чем 4 КБ, sedникогда не будет перезаписывать блок, который он еще не прочитал.

Я бы на это не рассчитывал.


Должны быть echo '<!DOCTYPE html>'или экранированы без "" кавычек.
AD

@ Хорошая мысль. Я склонен забывать об этой ошибке ^ Wfeature интерактивного bash / zsh, так как я обычно отключаю его для себя.
Стефан Шазелас

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

3

Для FreeBSD sed , которая также используется в Mac OS X, -eпосле параметра требуется опция, -iпозволяющая правильно и однозначно определить и распознать следующую (regex) команду.

Другими словами, sed -i -e ...должен работать как с FreeBSD, так и с GNU sed.

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

(Однако обратите внимание, что sedредактирование файлов на месте приводит к изменениям inode файла, см. «Редактирование файлов на месте» ).

(Как общий совет, последние версии FreeBSD sedимеют -rпереключатель для повышения совместимости с GNU sed).

echo a > testfile.txt
ls -li testfile.txt
#gsed -i -e 's/a/A/' testfile.txt
#bsdsed -i 's/a/A/' testfile.txt  # does not work
bsdsed -i -e 's/a/A/' testfile.txt
ls -li testfile.txt
cat testfile.txt

5
Нет, bsdsed -i -e 's/a/A/'это не редактирование на месте, это редактирование с сохранением оригинала с суффиксом "-e" ( testfile.txt-e).
Стефан Шазелас

обратите внимание, что GNU sed также поддерживает -E (в дополнение к -r) для совместимости с FreeBSD. -E, вероятно, будет указано в следующей версии POSIX, поэтому мы все должны забыть об этом -r бессмысленно и делать вид, что его никогда не было.
Стефан Шазелас

2

Чтобы эмулировать sed -iодин файл для переноса, максимально избегая условий гонки:

sed 'script' <<FILE >file
$(cat file)
FILE

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

Вы могли бы также сделать резервные копии как:

sed 'script' <<FILE >file
$(tee file.old <file)
FILE

2

Вы можете использовать Vim в режиме Ex:

ex -sc '1i|<!DOCTYPE html>' -cx file
  1. 1 выберите первую строку

  2. i вставить текст и перевод строки

  3. x сохранить и закрыть

Или даже, как в комментариях, простой старый стандарт ex :

printf '%s\n' 1i '<!DOCTYPE html>' . x | ex file 

Здесь нет ничего специфичного для Vim. Это полностью соответствует спецификациям POSIX, заex исключением того, что реализации не требуют поддержки нескольких -cфлагов. Для определенной переносимости я бы использовалprintf '%s\n' 1i '<!DOCTYPE html>' . x | ex file
Wildcard
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.