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

У меня есть 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 отбрасывает персонажа, но я не вижу решения.

Ответы [ 7 ]

64 голосов
/ 24 февраля 2010

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||| в качестве расширения (!), А затем интерпретирует аргумент next как команду - в этом случае он начинается с t который sed распознает как команду перехода к метке, ожидающую метку назначения в качестве аргумента - отсюда и ошибка, которую вы видите.

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

$ sed -i 's|x|y|' test
sed: 1: "test": undefined label 'est'
49 голосов
/ 11 марта 2010

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

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

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

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

sed -i "" -e "s/blah/blah/" files
32 голосов
/ 26 июля 2016

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

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

  2. Тестирование ядра Дарвина в соответствии с рекомендациями не является надежным подход к кроссплатформенному решению, поскольку 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.

14 голосов
/ 22 декабря 2014

Это не совсем ответ на вопрос, но можно получить поведение, эквивалентное 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, но недавно она была удалена)

11 голосов
/ 06 января 2014

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

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

9 голосов
/ 03 июля 2017

полезный ответ Мартина Клейтона дает хорошее объяснение проблемы [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] Более подробное объяснение см. этого ответа моего.

2 голосов
/ 02 марта 2016

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

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

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

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