Wie kann ich mit sed -i (In-Place-Bearbeitung) Portabilität erreichen?

40

Ich schreibe Shell-Skripte für meinen Server, bei dem es sich um ein Shared Hosting handelt, auf dem FreeBSD ausgeführt wird. Ich möchte sie auch lokal auf meinem PC mit Linux testen können. Daher versuche ich, sie portabel zu schreiben, sedsehe aber keine Möglichkeit, dies zu tun.

Ein Teil meiner Website verwendet generierte statische HTML-Dateien. Diese Sed-Line fügt nach jeder Regeneration den korrekten DOCTYPE ein:

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

Es funktioniert mit GNU sedunter Linux, aber FreeBSD sederwartet , dass das erste Argument nach der -iOption eine Erweiterung für die Sicherungskopie ist. So würde es aussehen:

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

GNU sederwartet jedoch wiederum, dass der Ausdruck unmittelbar danach folgt -i. (Es sind auch Korrekturen für die Behandlung von Zeilenumbrüchen erforderlich, die hier bereits beantwortet wurden. )

Natürlich kann ich diese Änderung in meine Serverkopie des Skripts aufnehmen, aber das würde z. B. meine Verwendung von VCS für die Versionierung durcheinander bringen. Gibt es eine Möglichkeit, dies mit sed vollständig portabel zu erreichen?

rot
quelle
Die beiden Sed-Snippets, die Sie bereitgestellt haben, sind identisch. Sind Sie sicher, dass es keinen Tippfehler gibt? Außerdem kann ich GNU sed ausführen und die Backup-Erweiterung direkt nach-i
iruvar 29.09.13
Danke, dass du das gesehen hast. Ich habe meine Frage behoben. Die zweite Zeile führt zu einem Fehler in meinem sed. Sie erwartet, dass '1s / ^ / <! DOCTYPE html> \ n /' eine Datei ist, und beschwert sich, dass sie nicht gefunden werden kann.
Red

Antworten:

39

GNU sed akzeptiert eine optionale Erweiterung nach -i. Die Erweiterung muss sich im selben Argument befinden und darf kein Leerzeichen enthalten. Diese Syntax funktioniert auch bei BSD sed.

sed -i.bak -e '…' SOMEFILE

Beachten Sie, dass sich unter BSD -iauch das Verhalten ändert, wenn mehrere Eingabedateien vorhanden sind: Sie werden unabhängig voneinander verarbeitet (dh sie stimmen z. B. $mit der letzten Zeile jeder Datei überein). Auch dies funktioniert nicht auf BusyBox.

Wenn Sie keine Sicherungsdateien verwenden möchten, können Sie überprüfen, welche Version von sed verfügbar ist.

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

Alternativ können Sie eine Funktion definieren, um ein Überfrachten der Positionsparameter zu vermeiden.

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

Wenn Sie sich nicht darum kümmern möchten, verwenden Sie Perl.

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

Wenn Sie ein portables Skript schreiben möchten, verwenden -iSie es nicht - es ist nicht in POSIX enthalten. Mach manuell, was sed unter der Haube macht - es ist nur noch eine Codezeile.

sed -e '…' "$file" >"$file.new"
mv -- "$file.new" "$file"
Gilles 'SO - hör auf böse zu sein'
quelle
2
GNU sed -iimpliziert auch -s. Und der einfachste Weg, nach einer GNU zu suchen, sedist der sed vBefehl, der für GNU ein gültiger Noop ist, aber an allen anderen Stellen fehlschlägt.
mikeserv
Inspiriert von den oben genannten Tipps, hier ist eine einzeilige (wenn hässlich) portable Version für diejenigen , die wirklich haben wollen, obwohl es tut Laich eine Subshell: sed -i$(sed v < /dev/null 2> /dev/null || echo -n " ''") -e '...' "$file"Wenn es nicht GNU sed, ein Leerzeichen eingefügt durch zwei Seng Anführungszeichen gefolgt nach , -iso dass es arbeitet an BSD. GNU sed bekommt nur -i.
Ivan X
2
@IvanX Ich bin vorsichtig, wenn ich den vBefehl benutze , um auf GNU sed zu testen. Was wäre, wenn FreeBSD sich entschließen würde, es zu implementieren?
Gilles 'SO- hör auf böse zu sein'
@ Gilles Guter Punkt, aber die Manpage für GNU sed beschreibt v als genau für diesen Zweck (es wird festgestellt, dass es GNU sed ist und nicht etwas anderes), also würde man hoffen, dass * BSD dies honoriert. Ich kann mir keinen anderen Test aus dem Nichts vorstellen, der auf GNU sed keine Aktion ausführt und auf BSD sed (oder umgekehrt) einen Fehler verursacht, außer -i selbst, aber dafür müsste zuerst eine Dummy-Datei erstellt werden. Ihr Test für sed oben ist OK, aber unhandlich für Inline. -I ganz zu vermeiden, wie Sie meinen, scheint sicherlich die sicherste Wette zu sein, aber ich bin damit einverstanden, sed vvorausgesetzt, das ist der Zweck, zu existieren.
Ivan X
1
@lapo Eine Alternative ist das Definieren einer Funktion, siehe meine Bearbeitung.
Gilles 'SO - hör auf böse zu sein'
10

Wenn Sie keinen Trick finden, um das sedSpiel schön zu machen , können Sie versuchen:

  1. Verwenden Sie nicht -i:

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

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

ed

Sie können immer verwenden ed, um einer vorhandenen Datei eine Zeile voran zu stellen.

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

Einzelheiten

Die Bits um das <!DOCTYPE html>sind Befehle, edum es anzuweisen, diese Zeile der Datei hinzuzufügen my.html.

sed

Ich glaube, dieser Befehl in sedkann auch verwendet werden:

$ sed -i '1i<!DOCTYPE html>\n` testfile.csv
slm
quelle
Ich habe schließlich auf Perl zurückgegriffen, aber die Verwendung von ed ist eine gute Alternative, die bei Unix-ähnlichen Benutzern nicht so beliebt ist, wie es sein sollte.
Red
@ Red - freut sich zu hören, dass Sie Ihr Problem gelöst haben. Ja, ich hatte das noch nie gesehen, googeln hat es aufgedreht und es schien tatsächlich die tragbarste und treffendste Möglichkeit zu sein, dies zu tun.
slm
7

Sie können auch manuell tun, was perl -iunter der Haube geschieht:

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

Wie perl -i, es gibt keine Sicherung, und wie die meisten Lösungen , die hier gegeben, passen sie die Berechtigungen beeinträchtigen können, das Eigentum an der Datei und kann eine Symlink in eine reguläre Datei drehen.

Mit:

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

sedwürde die Datei über sich selbst überschreiben, so dass Eigentumsrechte und Berechtigungen oder Symlinks nicht beeinträchtigt würden. Es funktioniert mit GNU, sedda sednormalerweise ein Puffer voller Daten gelesen wurde file(in meinem Fall 4 KB), bevor er mit dem iBefehl überschrieben wurde . Das würde nicht funktionieren, wenn die Datei mehr als 4 KB groß wäre, außer dass sedauch die Ausgabe gepuffert wird.

Arbeitet grundsätzlich sedmit 4k-Blöcken zum Lesen und Schreiben. Wenn die einzufügende Zeile kleiner als 4 KB ist, sedwird ein noch nicht gelesener Block niemals überschrieben.

Ich würde mich aber nicht darauf verlassen.

Stéphane Chazelas
quelle
Sollte echo '<!DOCTYPE html>'oder sollte ohne Anführungszeichen "" geflüchtet werden.
AD
@AD Guter Punkt. Ich neige dazu, diesen Fehler zu vergessen, da ich ihn im Allgemeinen für mich selbst deaktiviere.
Stéphane Chazelas
Dies ist einer der sehr wenige Antworten hier , die nicht ein großen offenes Sicherheitsloch hat mit einem statischen zu einer „temporären Datei“ umlenken, vorhersagbare Namen , ohne zu überprüfen , ob es bereits vorhanden ist . Ich glaube, das sollte die akzeptierte Antwort sein. Sehr schöne Demonstration der Verwendung von Gruppenbefehlen.
Wildcard
3

FreeBSD sed , das auch unter Mac OS X verwendet wird, benötigt die -eOption nach dem -iWechsel, um den folgenden (regulären) Befehl korrekt und eindeutig zu definieren und zu erkennen.

Mit anderen Worten, sed -i -e ...sollte mit FreeBSD & GNU funktionieren sed.

Im Allgemeinen sed -ierfordert das Weglassen der Backup-Erweiterung nach FreeBSD eine explizite sedOption oder einen Schalter -i, um Verwirrung auf Seiten von FreeBSD sedbeim Parsen der Befehlszeilenargumente zu vermeiden .

(Beachten Sie jedoch, dass die Bearbeitung von Dateien an Ort undsed Stelle zu Änderungen des Dateieingangs führt, siehe Bearbeiten von Dateien an Ort und Stelle. )

(Generell haben neuere Versionen von FreeBSD sedden -rSchalter, um die Kompatibilität mit GNU zu erhöhen 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
carlo
quelle
5
Nein, die Bearbeitung bsdsed -i -e 's/a/A/'erfolgt nicht direkt, sondern durch Speichern des Originals mit dem Suffix "-e" ( testfile.txt-e).
Stéphane Chazelas
Beachten Sie, dass GNU sed aus Kompatibilitätsgründen mit FreeBSD auch -E (zusätzlich zu -r) unterstützt. -E wird wahrscheinlich in der nächsten POSIX-Version angegeben, daher sollten wir alle darüber hinwegsehen, dass -r unsinnig ist, und so tun, als ob es nie existiert hätte.
Stéphane Chazelas
2

Um sed -ieine einzelne Datei portabel zu emulieren und dabei die Rennbedingungen so weit wie möglich zu vermeiden:

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

Übrigens wird hiermit auch das mögliche Problem sed -ibehoben, das je nach Verzeichnis- und Dateiberechtigungen dazu führen kann, sed -idass ein Benutzer eine Datei überschreibt, die dieser Benutzer nicht bearbeiten darf.

Sie könnten auch Backups machen wie:

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

Sie können Vim im Ex-Modus verwenden:

ex -sc '1i|<!DOCTYPE html>' -cx file
  1. 1 erste Zeile auswählen

  2. i Text und Zeilenumbruch einfügen

  3. x speichern und schließen

Oder auch, wie in den Kommentaren, einfach alter Standard ex :

printf '%s\n' 1i '<!DOCTYPE html>' . x | ex file 
Steven Penny
quelle
Hier gibt es nichts Vim-spezifisches. Dies entspricht vollständig den POSIX-Spezifikationen fürex , mit der Ausnahme, dass keine Implementierungen erforderlich sind , um mehrere -cFlags zu unterstützen . Für bestimmte Portabilität würde ichprintf '%s\n' 1i '<!DOCTYPE html>' . x | ex file
Wildcard