macos - Necesito mi comando sed-i para la edición in situ para que funcione tanto con sed de GNU como con BSD/OSX sed
inplace-editing (7)
Tengo un makefile (desarrollado para gmake en Linux) que estoy tratando de portar a OSX, pero parece que sed no quiere cooperar. Lo que hago es usar GCC para autogenerar archivos de dependencia, y luego ajustarlos un poco usando sed. La parte relevante del archivo 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:|'' $@
Aunque esto funciona sin problemas bajo GNU / Linux, recibo errores como los siguientes cuando intento construir en 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''
Parece que sed está cortando un personaje, pero no veo la solución.
Actally ... haciendo
sed -i -e "s/blah/blah/" files
tampoco hace lo que espera en OS X ... sino que crea archivos de respaldo con la extensión "-e".
El adecuado para OS es
sed -i "" -e "s/blah/blah/" files
Esta no es toda una respuesta a la pregunta, pero uno puede obtener un comportamiento equivalente a Linux a través de brew install gnu-sed --with-default-names
He corregido la solución publicada por @thecarpy:
Aquí hay una solución adecuada de plataforma cruzada para sed -i
:
sedi() {
case $(uname) in
Darwin*) sedi=(''-i'' '''') ;;
*) sedi=''-i'' ;;
esac
LC_ALL=C sed "${sedi[@]}" "$@"
}
La respuesta actualmente aceptada es defectuosa de dos maneras muy importantes.
Con BSD sed (la versión OSX), la opción
-e
se interpreta como una extensión de archivo y, por lo tanto, crea un archivo de copia de seguridad con una extensión-e
.Las pruebas para kernel darwin como se sugiere no son un enfoque confiable para una solución multiplataforma ya que GNU o BSD sed podrían estar presentes en cualquier cantidad de sistemas.
Una prueba mucho más confiable sería simplemente probar la opción de --version
que solo se encuentra en la versión GNU de sed.
sed --version >/dev/null 2>&1
Una vez que se determina la versión correcta de sed, podemos ejecutar el comando en su sintaxis adecuada.
Sintaxis de GNU sed para la opción -i:
sed -i -- "$@"
Sintaxis BSD sed para la opción -i:
sed -i "" "$@"
Finalmente, colóquelo todo junto en una función de plataforma cruzada para ejecutar un in situ edit sed commend:
sedi () {
sed --version >/dev/null 2>&1 && sed -i -- "$@" || sed -i "" "$@"
}
Ejemplo de uso:
sedi ''s/old/new/g'' ''some_file.txt''
Esta solución ha sido probada en OSX, Ubuntu, Freebsd, Cygwin, CentOS, Red Hat Enterprise y Msys.
Me encontré con este problema también y pensé en la siguiente solución:
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
funciona para mí ;-), YMMV
Trabajo en un equipo multi-OS donde ppl construyó Windows, Linux y OS X. Algunos usuarios de OS X se quejaron porque obtuvieron otro error: tenían el puerto GNU de sed instalado así que tuve que especificar la ruta completa.
OS X sed
maneja el argumento -i
diferente a la versión de Linux .
Puede generar un comando que pueda "funcionar" para ambos agregando -e
de esta manera:
# vv
sed -i -e ''s|/(.*/)/.o:|$(OBJ_DIR)//1.o $(OBJ_DIR)//1.d $(TEST_OBJ_DIR)//1_utest.o:|'' $@
OS X sed -i
interpreta lo siguiente después de -i
como una extensión de archivo para una copia de seguridad de la edición in situ. (La versión de Linux solo hace esto si no hay espacio entre -i
y la extensión.) Obviamente, un efecto secundario de usar esto es que obtendrá un archivo de copia de seguridad con -e
como una extensión, que puede que no desee. Consulte otras respuestas a esta pregunta para obtener más detalles y enfoques más limpios que se pueden usar en su lugar.
El comportamiento que ves es porque OS X sed
consume el s|||
como la extensión (!) interpreta el siguiente argumento como un comando, en este caso comienza con t
, que sed
reconoce como un comando de rama a etiqueta esperando la etiqueta de destino como un argumento, de ahí el error que ve.
Si crea una test
archivo, puede reproducir el error:
$ sed -i ''s|x|y|'' test
sed: 1: "test": undefined label ''est''
La útil respuesta de Martin Clayton proporciona una buena explicación del problema [1] , pero una solución que, como afirma, tiene un efecto secundario potencialmente no deseado.
Aquí hay soluciones libres de efectos secundarios :
Advertencia : Resolver el problema de sintaxis -i
solo, como se muestra a continuación, puede no ser suficiente, porque hay muchas otras diferencias entre GNU sed
y BSD / macOS sed
(para una discusión exhaustiva, vea esta respuesta mía).
Solución con -i
: crea un archivo de copia de seguridad temporalmente y luego límpialo:
Con un argumento opcional de sufijo no vacío (extensión de archivo de nombre de archivo de reserva) (un valor que no es la cadena vacía ), puede usar -i
de una manera que funcione tanto con BSD / macOS sed
como con GNU sed
, añadiendo directamente el sufijo a la opción -i
.
Esto se puede utilizar para crear un archivo de copia de seguridad de forma temporal que puede limpiar de inmediato:
sed -i.bak ''s/foo/bar/'' file && rm file.bak
Obviamente, si desea mantener la copia de seguridad, simplemente omita la parte && rm file.bak
.
Solución alternativa que es POSIX-compatible, usando un archivo temporal y mv
:
Si solo se va a editar un solo archivo in situ, se puede eludir la opción -i
para evitar la incompatibilidad.
Si restringe su script sed
y otras opciones a las características que cumplen con POSIX , la siguiente es una solución totalmente portátil (tenga en cuenta que -i
no es compatible con POSIX ).
sed ''s/foo/bar'' file > /tmp/file.$$ && mv /tmp/file.$$ file
Este comando simplemente escribe las modificaciones en un archivo temporal y, si el comando
sed
tiene éxito (&&
), reemplaza el archivo original por el temporal.- Si desea conservar el archivo original como copia de seguridad, agregue otro comando
mv
que renombre primero el original.
- Si desea conservar el archivo original como copia de seguridad, agregue otro comando
Advertencia : Fundamentalmente, esto es lo que
-i
también, excepto que trata de conservar los permisos y atributos extendidos (macOS) del archivo original; sin embargo, si el archivo original es un enlace simbólico , tanto esta solución como-i
reemplazarán el enlace simbólico con un archivo común.
Consulte la mitad inferior de esta respuesta mía para obtener detalles sobre cómo funciona-i
.
[1] Para una explicación más detallada, mira esta respuesta mía.