Команда sed с опцией -i (редактирование на месте) отлично работает на Ubuntu, но не Mac

Я ничего не знаю о Sed, но мне нужна эта команда (которая отлично работает на Ubuntu) для работы с Mac OSX:

sed -i "/ $domain .*#drupalpro/d" /etc/hosts

Я получаю:

sed: 1: "/etc/hosts": extra characters at the end of h command

Ответ 1

Ubuntu поставляется с GNU sed, где суффикс для опции -i не является обязательным. OS X поставляется с BSD sed, где суффикс является обязательным. Попробуйте sed -i ''

Ответ 2

Чтобы дополнить микротерий полезный, точный ответ:

  • с портативным решением
  • с исходной информацией

TL;DR:

Эквивалент этого GNU sed (стандартный для большинства Linux):

sed -i    's/foo/bar/' file

- это команда BSD/macOS sed:

sed -i '' 's/foo/bar/' file  # Note the '' as a *separate argument*

С BSD/macOS sed следующие команды не работают вообще или не так, как предполагалось:

sed -i    's/foo/bar/' file  # Breaks; script is misinterpreted as backup-file suffix
sed -i''  's/foo/bar/' file  # Ditto
sed -i -e 's/foo/bar/' file  # -e is misinterpreted as backup-file suffix

Для обсуждения всех различий между GNU sed и BSD/macOS sed см. этот ответ.

Портативный подход:

Примечание. Portable здесь означает, что команда работает с обсуждаемыми реализациями. Он не переносится в смысле POSIX, потому что параметр -i не совместим с POSIX.

# Works with both GNU and BSD/macOS Sed, due to a *non-empty* option-argument:
# Create a backup file *temporarily* and remove it on success.
sed -i.bak 's/foo/bar/' file && rm file.bak

Для пояснения см. ниже; для альтернативных решений, включая POSIX-совместимый, см. этот связанный ответ.


Фоновая информация

В GNU sed (стандартный для большинства дистрибутивов Linux) и BSD/macOS sed, параметр -i, который выполняет обновление на месте [1]  его входных файлов принимает аргумент option, который указывает, какой суффикс (расширение имени файла) используется для файла резервной копии обновляемого файла.

Например, в обеих реализациях сохраняется исходный файл file в качестве файла резервной копии file.bak:

sed -i.bak 's/foo/bar/' file  # Keep original as 'file.bak'; NO SPACE between -i and .bak

Даже если с GNU sed аргумент суффикса является необязательным, тогда как BSD/macOS sed это обязательный параметр, приведенный выше синтаксис работает с обеими реализациями, потому что непосредственно примыкает аргумент option (.bak) к опции (-i) - -i.bak, в отличие от -i .bak - работает как необязательный, так и обязательный параметр-аргумент:

  • Синтаксис -i.bak - это единственная форма, которая работает для необязательного параметра-аргумента.
  • Синтаксис -i.bak также работает как обязательный параметр-аргумент в качестве альтернативы -i .bak, то есть задает параметр и его аргумент отдельно.

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

  • С GNU sed не указывая суффикс означает просто используя -i самостоятельно.

  • С BSD/macOS sed, не указывая суффикс, указывающий пустую строку как обязательный суффикс и по техническим причинам пустую строку можно передать только в качестве отдельного аргумента: то есть -i '' не -i''.

-i'' не работает, потому что для sed он неотличим от простого -i, потому что оболочка эффективно удаляет пустые кавычки (она объединяет -i и '' и удаляет кавычки с синтаксической функцией), и в обоих случаях проходит только -i.

С помощью (эффективно) только -i указанного, это следующий аргумент, который интерпретируется как параметр-аргумент:

sed -i 's/foo/bar/' file # BREAKS with BSD/macOS Sed

's/foo/bar/' - предназначенный для Sed script (команда) - теперь интерпретируется как суффикс, а слово file интерпретируется как script.
Интерпретация такого слова, как script, приводит к неясному сообщению об ошибке, например, sed: 1: "file": invalid command code f,
потому что f интерпретируется как команда Sed (функция).

Аналогично, с:

sed -i -e 's/foo/bar/' file # CREATES BACKUP FILE 'file-e'

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

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

То, что случайное создание этих резервных файлов легко остается незамеченным, является наиболее вероятным объяснением неверного ответа Crt и этот неправильный ответ к аналогичному вопросу, получив так много голосов (на момент написания этой статьи).


[1] Строго говоря, временный файл создается за кулисами, который затем заменяет исходный файл; этот подход может быть проблематичным: см. нижнюю половину этого answer.

Ответ 3

Человек - ваш друг.

OS X

 -i extension
         Edit files in-place, saving backups with the specified extension.
         If a zero-length extension is given, no backup will be saved.  It
         is not recommended to give a zero-length extension when in-place
         editing files, as you risk corruption or partial content in situ-
         ations where disk space is exhausted, etc.

Ответ 4

В OS X вы можете использовать версию sed для sed: gsed.

# if using brew
brew install gnu-sed

#if using ports
sudo port install gsed

Затем, если ваш script должен быть портативным, в зависимости от вашей ОС вы можете определить, какую команду использовать.

SED=sed
unamestr=`uname`
if [[ "$unamestr" == "Darwin" ]] ; then
    SED=gsed
    type $SED >/dev/null 2>&1 || {
        echo >&2 "$SED it not installed. Try: brew install gnu-sed" ;
        exit 1;
    }
fi
# here your sed command, e.g.:
$SED -i "/ $domain .*#drupalpro/d" /etc/hosts