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

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

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

В то время как это выполняется без проблем в 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 отрубает персонажа, но я не вижу решения.

Ответ 1

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

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

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

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

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

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

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

Ответ 2

Собственно.. делая

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

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

Для ОС подходит

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

Ответ 3

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

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

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

Более надежным тестом было бы просто проверить параметр --version, который можно найти только в версии sed.

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

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

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

sed -i -- "[email protected]"

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

sed -i "" "[email protected]"

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

sedi () {
    sed --version >/dev/null 2>&1 && sed -i -- "[email protected]" || sed -i "" "[email protected]"
}

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

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

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

Ответ 4

Это не совсем ответ на вопрос, но можно получить Linux-эквивалентное поведение через

brew install gnu-sed

# Add to .bashrc / .zshrc
export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"

(ранее была опция --with-default-names для brew install gnu-sed но недавно она была удалена)

Ответ 5

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

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, поэтому мне пришлось указать полный путь.

Ответ 6

полезный ответ martin clayton дает хорошее объяснение проблемы [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 script и другие параметры функции, совместимые с POSIX, следующее полностью портативное решение (обратите внимание, что -i не совместим с POSIX).

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

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


[1] Для более подробного объяснения см. этот ответ.

Ответ 7

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

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

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

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

Ответ 8

Обходной путь ниже может быть лучше:

sed '/foo/bar/' файл | tee file