Мне нужна моя команда 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
В настоящее время принятый ответ имеет два недостатка.
С BSD sed (версия OSX),
-e
опция интерпретируется как расширение файла и поэтому создает файл резервной копии с-e
расширение.Тестирование ядра 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
, но этот не создает временные файлы, он напрямую перенаправляет изменения в файл.
Как нуб, я знаю, в чем минусы этого, единственное, что имеет значение, это «это работает»