¿Cómo puedo lograr la portabilidad con sed -i (edición in situ)?

Estoy escribiendo scripts de shell para mi server, que es un alojamiento compartido que ejecuta FreeBSD. También quiero poder probarlos localmente, en mi PC con Linux. Por lo tanto, estoy tratando de escribirlos de manera portátil, pero con sed no veo forma de hacerlo.

Parte de mi website utiliza files html estáticos generados, y esta línea sed inserta el DOCTYPE correcto después de cada regeneración:

 sed -i '1s/^/<!DOCTYPE html> \n/' ${file_name.html} 

Funciona con GNU sed en Linux, pero FreeBSD espera que el primer argumento después de -i sea la extensión para la copy de security. Así es como sería:

 sed -i '' '1s/^/<!DOCTYPE html> \n/' ${file_name.html} 

Sin embargo, GNU sed a su vez espera que la expresión siga inmediatamente después de -i. (También requiere correcciones con el event handling nueva línea, pero eso ya está respondido aquí )

Por supuesto, puedo include este cambio en mi copy del script en el server, pero eso sería un desastre, es decir, mi uso de VCS para el control de versiones. ¿Hay alguna manera de lograr esto con sed de una manera totalmente portátil?

GNU sed acepta una extensión opcional después de -i . La extensión debe estar en el mismo argumento sin espacio intermedio. Esta syntax también funciona en BSD sed.

 sed -i.bak -e '…' SOMEFILE 

Tenga en count que en BSD, -i también cambia el comportamiento cuando hay múltiples files de input: se procesan de forma independiente (por ejemplo, $ coincide con la última línea de cada file). Además, esto no funcionará en BusyBox.

Si no desea utilizar files de copy de security, puede verificar qué versión de sed está disponible.

 case $(sed --help 2>&1) in *GNU*) set sed -i;; *) set sed -i '';; esac "$@" -e '…' "$file" 

O, como alternativa, para evitar que los parameters posicionales se bloqueen, defina una function.

 case $(sed --help 2>&1) in *GNU*) sed_i () { sed -i "$@"; };; *) sed_i () { sed -i '' "$@"; };; esac sed_i -e '…' "$file" 

Si no quieres molestarte, usa Perl.

 perl -i -pe '…' "$file" 

Si desea escribir un script portátil, no use -i – no está en POSIX. Haz manualmente lo que sed hace debajo del capó, es solo una línea más de código.

 sed -e '…' "$file" >"$file.new" mv -- "$file.new" "$file" 

Si no encuentra un truco para hacer que sed juegue bien, podría intentar:

  1. No use -i :

     sed '1s/^/<!DOCTYPE html> \n/' "${file_name.html}" > "${file_name.html}.tmp" && mv "${file_name.html}.tmp" "${file_name.html}" 
  2. Use Perl

     perl -i -pe 'print "<!DOCTYPE html> \n" if $.==1;' "${file_name.html}" 

ed

Siempre puede usar ed para anteponer una línea a un file existente.

 $ printf '0a\n<!DOCTYPE html>\n.\nw\n' | ed my.html 

Detalles

Los bits que se encuentran alnetworkingedor de <!DOCTYPE html> son commands para que se le my.html que agregue esa línea al file my.html .

sed

Creo que este command en sed también se puede usar:

 $ sed -i '1i<!DOCTYPE html>\n` testfile.csv 

También puede hacer manualmente lo que perl -i hace bajo el capó:

 { rm -f file && { echo '<!DOCTYPE html>'; cat; } > file;} < file 

Como perl -i , no hay respaldo, y al igual que la mayoría de las soluciones dadas aquí, tenga en count que puede afectar los permissions, la propiedad del file y puede convertir un enlace simbólico en un file normal.

Con:

 sed '1i\ <!DOCTYPE html>' file 1<> file 

sed sobrescribirá el file sobre sí mismo, por lo que no afectará la propiedad y los permissions o enlaces simbólicos. Funciona con GNU sed porque sed normalmente habrá leído un búfer lleno de datos del file (4k en mi caso) antes de sobrescribirlo con el command i . Eso no funcionaría si el file fuera más de 4k, excepto por el hecho de que sed también almacena su salida.

Básicamente sed trabaja en bloques de 4k para leer y escribir. Si la línea a insert es menor que 4k, sed nunca sobrescribirá un bloque que aún no ha leído.

Sin embargo, no lo contaría.

FreeBSD sed , que también se usa en Mac OS X, necesita la opción -e después del -i para definir y reconocer el siguiente command (regular) correctamente y sin ambigüedades.

En otras palabras, sed -i -e ... debería funcionar con FreeBSD y GNU sed .

En términos más generales, al omitir la extensión de copy de security después de que FreeBSD sed -i requiera una opción de sed explícita o un cambio después de -i para evitar confusiones en parte de FreeBSD sed al analizar sus arguments de command-line.

(Tenga en count, sin embargo, que las ediciones de files sed in situ conducen a cambios de inodo de files, consulte la edición de files "in situ" ).

(Como una pista general, las versiones recientes de FreeBSD sed tienen el -r para boost la compatibilidad con GNU sed ).

 echo a > testfile.txt ls -li testfile.txt #gsed -i -e 's/a/A/' testfile.txt #bsdsed -i 's/a/A/' testfile.txt # does not work bsdsed -i -e 's/a/A/' testfile.txt ls -li testfile.txt cat testfile.txt 

Puedes usar Vim en modo Ex:

 ex -sc '1i|<!DOCTYPE html>' -cx file 
  1. 1 selecciona la primera línea

  2. inserto text y nueva línea

  3. x save y cerrar

Para emular sed -i para un solo file de forma portátil, evitando las condiciones de carrera tanto como sea posible:

 sed 'script' <<FILE >file $(cat file) FILE 

Por cierto, esto también maneja el posible problema que introduce sed -i en que, dependiendo de los permissions de directory y file, sed -i podría permitir a un usuario sobrescribir un file que ese usuario no tiene permissions para editar.

También puede hacer copys de security como:

 sed 'script' <<FILE >file $(tee file.old <file) FILE