Мне нужна моя команда sed -i для редактирования на месте, чтобы работать как с GNU sed, так и с BSD/OSX sed

У меня есть make-файл (разработанный для gmake под Linux), который я пытаюсь перенести на OSX, но похоже, что sed не хочет сотрудничать. Я использую GCC для автоматической генерации файлов зависимостей, а затем немного их настраиваю с помощью sed. Соответствующая часть make-файла:

$(OBJ_DIR)/%.d: $(SRC_DIR)/%.cpp
  $(CPPC) -MM -MD $< -o $@
  sed -i 's|\(.*\)\.o:|$(OBJ_DIR)/\1.o $(OBJ_DIR)/\1.d $(TEST_OBJ_DIR)/\1_utest.o:|' $@

Хотя это работает без проблем в GNU/Linux, я получаю ошибки, подобные следующим, когда пытаюсь собрать на OSX:

sed: 1: "test/obj/equipmentConta ...": undefined label 'est/obj/equipmentContainer_utest.d'
sed: 1: "test/obj/dice_utest.d": undefined label 'est/obj/dice_utest.d'
sed: 1: "test/obj/color-string_u ...": undefined label 'est/obj/color-string_utest.d'

Казалось бы, sed отбрасывает персонажа, но я не вижу решения.

8 ответов

Решение

OS X sed обрабатывает -i аргумент в отличие от версии для Linux.

Вы можете создать команду, которая может "работать" для обоих, добавив -e в этом случае:

#      vv
sed -i -e 's|\(.*\)\.o:|$(OBJ_DIR)/\1.o $(OBJ_DIR)/\1.d $(TEST_OBJ_DIR)/\1_utest.o:|' $@

OS X sed -i интерпретирует следующую вещь после -i в качестве расширения файла для резервной копии редактирования на месте. (Версия для Linux делает это только в том случае, если между -i и расширение.) Очевидно, что побочным эффектом использования этого является то, что вы получите файл резервной копии с -e как расширение, которое вы можете не хотеть. Пожалуйста, обратитесь к другим ответам на этот вопрос для получения более подробной информации и более чистых подходов, которые можно использовать вместо этого.

Поведение, которое вы видите, потому что OS X sed потребляет s||| поскольку расширение (!) затем интерпретирует следующий аргумент как команду - в этом случае оно начинается с t, который sed распознается как команда ветвления к метке, ожидающая целевую метку в качестве аргумента - отсюда и ошибку, которую вы видите.

Если вы создаете файл test Вы можете воспроизвести ошибку:

$ sed -i 's|x|y|' test
sed: 1: "test": undefined label 'est'

На самом деле.. делаю

sed -i -e "s/blah/blah/" files

не делает то, что вы ожидаете в OS X либо.. вместо этого он создает файлы резервных копий с расширением "-e".

Правильно для ОС является

sed -i "" -e "s/blah/blah/" files

В настоящее время принятый ответ имеет два недостатка.

  1. С BSD sed (версия OSX), -e опция интерпретируется как расширение файла и поэтому создает файл резервной копии с -eрасширение.

  2. Тестирование ядра darwin, как было предложено, не является надежным подходом к кроссплатформенному решению, поскольку GNU или BSD sed могут присутствовать в любом количестве систем.

Гораздо более надежным тестом было бы просто проверить --version опция, которая встречается только в GNU-версии sed.

sed --version >/dev/null 2>&1

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

Синтаксис GNU sed для опции -i:

sed -i -- "$@"

Синтаксис BSD sed для опции -i:

sed -i "" "$@"

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

sedi () {
    sed --version >/dev/null 2>&1 && sed -i -- "$@" || sed -i "" "$@"
}

Пример использования:

sedi 's/old/new/g' 'some_file.txt'

Это решение было протестировано на OSX, Ubuntu, Freebsd, Cygwin, CentOS, Red Hat Enterprise и Msys.

Полезный ответ Мартина Клейтона дает хорошее объяснение проблемы [1], но решение, которое, как он заявляет, имеет потенциально нежелательный побочный эффект.

Вот решения без побочных эффектов:

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


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

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

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

sed -i.bak 's/foo/bar/' file && rm file.bak

Очевидно, что если вы хотите сохранить резервную копию, просто опустите && rm file.bak часть.


Обходной путь, который является POSIX-совместимым, используя временный файл и mv:

Если на месте нужно редактировать только один файл, -i вариант можно обойти, чтобы избежать несовместимости.

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

sed 's/foo/bar' file > /tmp/file.$$ && mv /tmp/file.$$ file
  • Эта команда просто записывает изменения во временный файл и, если sed команда выполнена успешно (&&), заменяет исходный файл временным.

    • Если вы хотите сохранить исходный файл в качестве резервной копии, добавьте еще один mv Команда, которая переименовывает оригинал первым.
  • Предостережение: в основном это то, что -i делает то же самое, за исключением того, что пытается сохранить разрешения и расширенные атрибуты (macOS) исходного файла; однако, если исходный файл является символической ссылкой, как это решение, так и -i заменит символическую ссылку на обычный файл.
    Смотрите нижнюю половину этого моего ответа для деталей о том, как -i работает.


[1] Более подробное объяснение см. В моем ответе.

Это не совсем ответ на вопрос, но можно получить Linux-эквивалентное поведение через brew install gnu-sed --with-default-names

Я тоже наткнулся на эту проблему и подумал о следующем решении:

darwin=false;
case "`uname`" in
  Darwin*) darwin=true ;;
esac

if $darwin; then
  sedi="/usr/bin/sed -i ''"
else
  sedi="sed -i"
fi

$sedi 's/foo/bar/' /home/foobar/bar

у меня работает;-), YMMV

Я работаю в команде с несколькими ОС, где ppl собирают на Windows, Linux и OS X. Некоторые пользователи OS X жаловались, потому что они получили еще одну ошибку - у них был установлен GNU-порт sed, поэтому мне пришлось указать полный путь.

Я исправил решение, опубликованное @thecarpy:

Вот правильное кроссплатформенное решение для sed -i:

sedi() {
  case $(uname) in
    Darwin*) sedi=('-i' '') ;;
    *) sedi='-i' ;;
  esac

  LC_ALL=C sed "${sedi[@]}" "$@"
}

Я избегаю использования при написании скриптов и придумал простое решение:

      printf '%s' "$(sed 's/foo/bar' file)" > file

очень совместим и совместим с POSIX. Он делает почти то же самое, что иsed -i, но этот не создает временные файлы, он напрямую перенаправляет изменения в файл.

Как нуб, я знаю, в чем минусы этого, единственное, что имеет значение, это «это работает»

Другие вопросы по тегам