sed in-place Flag, das sowohl unter Mac (BSD) als auch unter Linux funktioniert

237

Gibt es einen Aufruf zur seddirekten Bearbeitung ohne Backups, der sowohl unter Linux als auch unter Mac funktioniert? Während das sedmit OS X gelieferte BSD anscheinend benötigt wird sed -i '' …, sedinterpretieren die GNU Linux-Distributionen die Anführungszeichen normalerweise als leeren Eingabedateinamen (anstelle der Sicherungserweiterung) und benötigen sed -i …stattdessen.

Gibt es eine Befehlszeilensyntax, die mit beiden Varianten funktioniert, sodass ich auf beiden Systemen dasselbe Skript verwenden kann?

dnadlinger
quelle
Ist Perl keine Option? Müssen Sie sed verwenden?
Noufal Ibrahim
Vielleicht installieren Sie die GNU-Version und verwenden Sie diese! topbug.net/blog/2013/04/14/… ? Ich würde das zuerst versuchen. Andererseits ist diese Art auch scheiße.
Dmitry Minkovsky
2
@dimadima: Könnte für einige andere Leute interessant sein, die diese Frage durchsuchen und persönliche Skripte haben, die auf ihrem OS X-Computer kaputt gehen. In meinem Fall brauchte ich es jedoch für das Build-System eines Open-Source-Projekts, bei dem die Aufforderung an Ihren Benutzer, zuerst GNU sed zu installieren, den ursprünglichen Zweck dieser Übung zunichte gemacht hätte (einige Dateien "überall funktionieren" patchen). .
Dnadlinger
@klickverbot ja, macht Sinn. Ich habe den Kommentar zuerst als Antwort hinzugefügt und ihn dann gelöscht, wobei mir klar wurde, dass er keine Antwort auf Ihre Frage war :).
Dmitry Minkovsky
1
Querverweis: Wie erreicht man Portabilität mit sed -i (In-Place-Bearbeitung)? auf dem Unix & Linux Stack Exchange.
MvG

Antworten:

212

Wenn Sie wirklich nur sed -idie "einfache" Methode verwenden möchten, funktioniert Folgendes sowohl auf GNU als auch auf BSD / Mac sed:

sed -i.bak 's/foo/bar/' filename

Beachten Sie den Platzmangel und den Punkt.

Beweis:

# GNU sed
% sed --version | head -1
GNU sed version 4.2.1
% echo 'foo' > file
% sed -i.bak 's/foo/bar/' ./file
% ls
file  file.bak
% cat ./file
bar

# BSD sed
% sed --version 2>&1 | head -1
sed: illegal option -- -
% echo 'foo' > file
% sed -i.bak 's/foo/bar/' ./file
% ls
file  file.bak
% cat ./file
bar

Natürlich können Sie dann einfach die .bakDateien löschen .

kine
quelle
2
Dies ist der richtige Weg. Fügen Sie ein paar Zeichen hinzu, und es ist portabel. Das Löschen der Sicherungsdatei ist trivial (Sie haben bereits den Dateinamen beim Aufrufen sed)
slezica
Dies funktionierte auf dem Mac, aber unter Ubuntu 16.04 überschrieb es die Originaldatei auf 0 Bytes und erstellt die .bak-Datei auch mit 0 Bytes?
Haus9
1
Bemerkenswert ist, dass unter macOS Sierra (und möglicherweise früher, ich habe keine Maschine zur Hand) beim Aufrufen mit sedkein Positionsargument akzeptiert wird - es muss mit einem anderen Flag versehen werden . Somit wird der Befehl:command-i-esed -i.bak -e 's/foo/bar/' filename
ELLIOTTCABLE
5
Dadurch werden Sicherungen erstellt, während das OP speziell nach einer direkten Bearbeitung fragt.
Marc-André Lafortune
8
Die vollständige Lösung lautet also:sed -i.bak 's/foo/bar/' file && rm file.bak
Joeytwiddle
111

Dies funktioniert mit GNU sed, jedoch nicht unter OS X:

sed -i -e 's/foo/bar/' target.file
sed -i'' -e 's/foo/bar/' target.file

Dies funktioniert unter OS X, jedoch nicht mit GNU sed:

sed -i '' -e 's/foo/bar/' target.file

Unter OS X Sie

  • kann nicht verwendet werden, sed -i -eda die Erweiterung der Sicherungsdatei auf gesetzt wäre-e
  • kann sed -i'' -eaus den gleichen Gründen nicht verwendet werden - es benötigt ein Leerzeichen zwischen -iund ''.
dnadlinger
quelle
1
@AlexanderMills Sagt sed, dass das nächste Argument ein Befehl ist - könnte auch hier übersprungen werden.
Benjamin W.
4
Beachten Sie, dass -iund -i''zum Zeitpunkt der Shell-Analyse identisch sind. sed kann sich bei diesen beiden ersten Aufrufen nicht anders verhalten, da es genau die gleichen Argumente erhält.
wchargin
44

Unter OSX installiere ich die GNU sed-Version immer über Homebrew, um Probleme mit Skripten zu vermeiden, da die meisten Skripte für GNU sed-Versionen geschrieben wurden.

brew install gnu-sed --with-default-names

Dann wird Ihr BSD sed durch GNU sed ersetzt.

Alternativ können Sie ohne Standardnamen installieren, aber dann:

  • Ändern Sie Ihre PATHAnweisungen nach der Installationgnu-sed
  • Checken Sie Ihre Skripte ein, um zwischen gsedoder sedabhängig von Ihrem System zu wählen
Lewy
quelle
4
Das --with-default-nameswurde aus dem Homebrew-Core entfernt , mehr Infos in dieser Antwort. bei der Installation gnu-sednun, geben Sie den Installationsanweisungen , die Sie hinzufügen müssen , gnubinum Ihre PATH:PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"
pgericson
18

Wie Noufal Ibrahim fragt, warum kannst du Perl nicht benutzen? Jeder Mac verfügt über Perl, und es gibt nur sehr wenige Linux- oder BSD-Distributionen, die keine Perl-Version im Basissystem enthalten. Eine der wenigen Umgebungen, in denen Perl möglicherweise tatsächlich fehlt, ist BusyBox (die wie GNU / Linux funktioniert -i, außer dass keine Backup-Erweiterung angegeben werden kann).

Wie ismail empfiehlt,

Da Perl überall verfügbar ist, mache ich es einfach perl -pi -e s,foo,bar,g target.file

und dies scheint in fast jedem Fall eine bessere Lösung zu sein als Skripte, Aliase oder andere Problemumgehungen, um die grundlegende Inkompatibilität sed -izwischen GNU / Linux und BSD / Mac zu beheben.

Alex Dupuy
quelle
Ich liebe diese Lösung. perlist seit dem 1. Mai 2009 Teil der Linux Standard Base-Spezifikation. refspecs.linuxfoundation.org/LSB_4.0.0/LSB-Languages/…
OregonTrail
1
Dies ist leicht die beste Lösung für Portabilität
ecnepsnai
17

Es gibt keine Möglichkeit, es zum Laufen zu bringen.

Eine Möglichkeit besteht darin, eine temporäre Datei wie die folgende zu verwenden:

TMP_FILE=`mktemp /tmp/config.XXXXXXXXXX`
sed -e "s/abc/def/" some/file > $TMP_FILE
mv $TMP_FILE some/file

Dies funktioniert bei beiden

analog
quelle
Gibt es einen Grund, warum SOME_FILE = "$ (sed -s" s / abc / def / "some / file)"; echo "$ SOME_FILE"> some / file; wird nicht stattdessen funktionieren
Jason Gross
Sieht so aus, als wären Sie durch die maximale Größe einer Bash-Variablen begrenzt, oder? Ich bin mir nicht sicher, ob es mit GBs-Dateien funktioniert.
Analog
3
Wenn Sie eine temporäre Datei erstellen, geben Sie einfach eine Erweiterung an, um eine Sicherungsdatei zu erstellen (die Sie dann entfernen können), wie von @kine unten vorgeschlagen.
Alex Dupuy
13

Antwort: Nein.

Die ursprünglich akzeptierte Antwort macht tatsächlich nicht das, was angefordert wird (wie in den Kommentaren angegeben). (Ich habe diese Antwort gefunden, als ich nach dem Grund gesucht habe, warum a file-e"zufällig" in meinen Verzeichnissen angezeigt wurde.)

Es gibt anscheinend keine Möglichkeit, sed -isowohl unter MacOS als auch unter Linuces konsequent zu arbeiten.

Meine Empfehlung lautet, nicht direkt zu aktualisieren sed(was komplexe Fehlermodi aufweist), sondern neue Dateien zu generieren und diese anschließend umzubenennen. Mit anderen Worten: vermeiden -i.

Steve Powell
quelle
9

Die -iOption ist nicht Teil von POSIX Sed . Eine portablere Methode wäre die Verwendung von Vim im Ex-Modus:

ex -sc '%s/alfa/bravo/|x' file
  1. % Wählen Sie alle Zeilen aus

  2. s ersetzen

  3. x speichern und schließen

Steven Penny
quelle
Dies ist die richtige Antwort. Ein wesentliches Merkmal von POSIX ist die Portabilität.
Kajukenbo
9

Hier ist eine weitere Version, die unter Linux und MacOS funktioniert, evalohne Sicherungsdateien zu verwenden und ohne sie löschen zu müssen. Es verwendet Bash-Arrays zum Speichern der sedParameter, was sauberer ist als die Verwendung von eval:

# Default case for Linux sed, just use "-i"
sedi=(-i)
case "$(uname)" in
  # For macOS, use two parameters
  Darwin*) sedi=(-i "")
esac

# Expand the parameters in the actual call to "sed"
sed "${sedi[@]}" -e 's/foo/bar/' target.file

Dadurch wird weder eine Sicherungsdatei noch eine Datei mit angehängten Anführungszeichen erstellt.

nwinkler
quelle
2
Das ist die richtige Antwort. Ich würde es ein bisschen vereinfachen, aber die Idee funktioniert perfekt sedi=(-i) && [ "$(uname)" == "Darwin" ] && sedi=(-i '') sed "${sedi[@]}" -e 's/foo/bar/' target.file
Alex Skrypnyk
Gutes Zeug! Aus Gründen der Lesbarkeit bevorzuge ich jedoch die längere Version.
Nwinkler
1
Dies ist der beste Weg für mich, mit verschiedenen Systemen kompatibel zu sein.
Victor Choy
6

Steve Powells Antwort ist ganz richtig. Auf der MAN-Seite für sed unter OSX und Linux (Ubuntu 12.04) wird die Inkompatibilität innerhalb der "in-place" sed-Nutzung zwischen den beiden Betriebssystemen hervorgehoben.

JFYI, zwischen dem -i und Anführungszeichen (die eine leere Dateierweiterung bezeichnen) sollte unter Verwendung der Linux-Version von sed kein Leerzeichen stehen

sed Linux Man Page

#Linux
sed -i"" 

und

sed OSX Manpage

#OSX (notice the space after the '-i' argument)
sed -i "" 

Ich habe dies in einem Skript umgangen, indem ich einen Alias-Befehl und die OS-Namensausgabe von ' uname ' innerhalb einer Bash 'if' verwendet habe. Der Versuch, betriebssystemabhängige Befehlszeichenfolgen in Variablen zu speichern, war bei der Interpretation der Anführungszeichen ein Hit und Miss. Die Verwendung von ' shopt -s expand_aliases ' ist erforderlich, um die in Ihrem Skript definierten Aliase zu erweitern / verwenden. Die Verwendung von shopt wird hier behandelt .

Big Rich
quelle
1

Wenn Sie sedin einem bashSkript direkt vor Ort arbeiten müssen und NICHT möchten, dass das In-Place-Ergebnis zu .bkp-Dateien führt und Sie eine Möglichkeit haben, das Betriebssystem zu erkennen (z. B. mit ostype.sh ), - dann das Der folgende Hack mit der basheingebauten Shell evalsollte funktionieren:

OSTYPE="$(bash ostype.sh)"

cat > myfile.txt <<"EOF"
1111
2222
EOF

if [ "$OSTYPE" == "osx" ]; then
  ISED='-i ""'
else # $OSTYPE == linux64
  ISED='-i""'
fi

eval sed $ISED 's/2222/bbbb/g' myfile.txt
ls 
# GNU and OSX: still only myfile.txt there

cat myfile.txt
# GNU and OSX: both print:
# 1111
# bbbb

# NOTE: 
# if you just use `sed $ISED 's/2222/bbbb/g' myfile.txt` without `eval`,
# then you will get a backup file with quotations in the file name, 
# - that is, `myfile.txt""`
sdaau
quelle
0

Sie können Schwamm verwenden. Sponge ist ein altes Unix-Programm, das im Paket moreutils enthalten ist (sowohl in Ubuntu als auch wahrscheinlich in Debian und in Homebrew auf Mac).

Es puffert den gesamten Inhalt der Pipe, wartet, bis die Pipe geschlossen ist (was wahrscheinlich bedeutet, dass die Eingabedatei bereits geschlossen ist) und überschreibt dann:

Von der Manpage :

Zusammenfassung

sed '...' Datei | grep '...' | Schwammfeile

Guillermo
quelle
3
Netter Ansatz - hilft in der ursprünglichen Situation leider nicht wirklich, da der Grund für den Rückgriff auf sed darin bestand, etwas in einem plattformübergreifenden Hilfsskript zu verwenden, das auf Client-Computern verwendet wird, auf die Sie sich nur auf das System verlassen können Werkzeuge installiert werden. Könnte aber für jemand anderen hilfreich sein.
Dnadlinger
0

Folgendes funktioniert für mich unter Linux und OS X:

sed -i' ' <expr> <file>

zB für eine Datei fmitaaabbaaba

sed -i' ' 's/b/c/g' f

ergibt aaaccaacasowohl auf Linux als auch auf Mac. Hinweis : Es gibt eine Zeichenfolge in Anführungszeichen ist ein Leerzeichen enthält , mit keinem Abstand zwischen dem -iund dem String. Einfache oder doppelte Anführungszeichen funktionieren beide.

Unter Linux verwende ich bashVersion 4.3.11 unter Ubuntu 14.04.4 und unter Mac Version 3.2.57 unter OS X 10.11.4 El Capitan (Darwin 15.4.0).

David G.
quelle
1
Wow, ich bin froh, dass ich nicht allen oben zugehört habe, die gesagt haben, es sei unmöglich und habe weiter gelesen! Das funktioniert wirklich einfach. Vielen Dank!
Personman
4
Funktioniert nicht für mich unter Mac OS ... neue Datei mit Leerzeichen am Ende des Dateinamens wird erstellt
Frédéric
Funktioniert auch nicht auf einem Mac (/ usr / bin / sed) - Ich habe jetzt eine zusätzliche Sicherungsdatei mit einem an den Dateinamen angehängten Leerzeichen :-(
paul_h
Versuchen Sie, den Platz aus den einfachen Anführungszeichen zu entfernen, es funktioniert für mich.
dps
0

Ich bin auf dieses Problem gestoßen. Die einzige schnelle Lösung bestand darin, das sed in mac durch die gnu-Version zu ersetzen:

brew install gnu-sed
vikrantt
quelle
Dies dupliziert eine bestehende Antwort mit viel mehr Details aus dem Jahr 2015.
Tripleee