sed in-place flag yang berfungsi baik pada Mac (BSD) dan Linux

237

Apakah ada permintaan untuk sedmelakukan pengeditan di tempat tanpa cadangan yang berfungsi baik di Linux dan Mac? Sementara BSD yang seddikirimkan dengan OS X tampaknya diperlukan sed -i '' …, seddistribusi GNU Linux biasanya datang dengan menafsirkan kutipan sebagai nama file input kosong (bukan ekstensi cadangan), dan sed -i …sebagai gantinya perlu .

Apakah ada sintaks baris perintah yang berfungsi dengan kedua rasa, sehingga saya dapat menggunakan skrip yang sama pada kedua sistem?

dnadlinger
sumber
Apakah perl bukan pilihan? Haruskah Anda menggunakan sed?
Noufal Ibrahim
Mungkin instal versi GNU dan gunakan itu! topbug.net/blog/2013/04/14/… ? Saya akan mencobanya dulu. Kemudian lagi, jenis itu menyebalkan juga.
Dmitry Minkovsky
2
@dimadima: Mungkin menarik bagi sebagian orang lain yang meramban pertanyaan ini yang memiliki skrip pribadi yang terpecah pada mesin OS X mereka. Namun, dalam kasus saya, saya membutuhkannya untuk sistem pembangunan proyek open source, di mana memberi tahu pengguna Anda untuk menginstal GNU sed terlebih dahulu akan mengalahkan tujuan asli latihan ini (menambal beberapa file dengan mode "bekerja di mana saja") .
dnadlinger
@klickverbot ya, masuk akal. Saya pertama kali menambahkan komentar sebagai jawaban, dan kemudian menghapusnya, menyadari itu bukan jawaban untuk pertanyaan Anda :).
Dmitry Minkovsky
1
Referensi silang: Bagaimana mencapai portabilitas dengan sed -i (in-place editing)? di Unix & Linux Stack Exchange.
MvG

Jawaban:

212

Jika Anda benar-benar hanya ingin menggunakan sed -icara 'mudah', berikut ini TIDAK berfungsi pada GNU dan BSD / Mac sed:

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

Perhatikan kurangnya ruang dan titik.

Bukti:

# 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

Jelas Anda kemudian bisa menghapus .bakfile.

Kine
sumber
2
Ini cara untuk pergi. Tambahkan beberapa karakter, dan ini portabel. Menghapus file cadangan itu sepele (Anda sudah memiliki nama file saat memohon sed)
slezica
Ini berfungsi pada mac, tetapi pada ubuntu 16.04 ia menimpa file asli ke 0 byte dan membuat file .bak juga dengan 0 byte?
house9
1
Sebagai catatan, pada macOS Sierra (dan mungkin sebelumnya, saya tidak memiliki mesin), sedtidak menerima commandargumen posisi ketika dipanggil -i- harus dilengkapi dengan flag lain -e,. Dengan demikian, perintahnya menjadi:sed -i.bak -e 's/foo/bar/' filename
ELLIOTTCABLE
5
Ini membuat cadangan, sementara OP secara khusus meminta pengeditan di tempat.
Marc-André Lafortune
8
Jadi solusi lengkapnya adalah:sed -i.bak 's/foo/bar/' file && rm file.bak
joeytwiddle
112

Ini berfungsi dengan GNU sed, tetapi tidak pada OS X:

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

Ini berfungsi pada OS X, tetapi tidak dengan GNU sed:

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

Di OS X Anda

  • tidak dapat digunakan sed -i -ekarena ekstensi file cadangan akan diatur ke-e
  • tidak dapat digunakan sed -i'' -ekarena alasan yang sama — ia membutuhkan ruang antara -idan ''.
dnadlinger
sumber
1
@AlexanderMills Memberitahu sed bahwa argumen berikutnya adalah perintah - juga bisa dilewati di sini.
Benjamin W.
4
Catat itu -idan -i''identik pada waktu penguraian shell; sed tidak bisa berperilaku berbeda pada dua doa pertama, karena mendapat argumen yang sama persis.
wchargin
44

Ketika di OSX, saya selalu menginstal versi sed GNU melalui Homebrew, untuk menghindari masalah dalam skrip, karena sebagian besar skrip ditulis untuk versi sed GNU.

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

Kemudian sed BSD Anda akan digantikan oleh GNU sed.

Atau, Anda dapat menginstal tanpa nama default, tetapi kemudian:

  • Ubah PATHsesuai petunjuk setelah menginstalgnu-sed
  • Periksa skrip Anda untuk memilih di antara gsedatau sedtergantung pada sistem Anda
Lewy
sumber
4
yang --with-default-namestelah dihapus dari homebrew-core , info lebih lanjut di ini jawaban. ketika menginstal gnu-sedsekarang, petunjuk instalasi menentukan bahwa Anda perlu menambahkan gnubinke PATH:PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"
pgerics
18

Seperti yang ditanyakan oleh Noufal Ibrahim , mengapa Anda tidak bisa menggunakan Perl? Setiap Mac akan memiliki Perl, dan ada sangat sedikit distribusi Linux atau BSD yang tidak menyertakan beberapa versi Perl dalam sistem basis. Satu-satunya lingkungan yang mungkin benar-benar kekurangan Perl adalah BusyBox (yang berfungsi seperti GNU / Linux -i, kecuali tidak ada ekstensi cadangan yang dapat ditentukan).

Seperti yang direkomendasikan ismail ,

Karena perl tersedia di mana saja saya lakukan perl -pi -e s,foo,bar,g target.file

dan ini sepertinya solusi yang lebih baik dalam hampir semua kasus daripada skrip, alias, atau solusi lain untuk menangani ketidakcocokan mendasar sed -iantara GNU / Linux dan BSD / Mac.

Alex Dupuy
sumber
Saya suka solusi ini. perladalah bagian dari spesifikasi Basis Standar Linux sejak 1 Mei 2009. refspecs.linuxfoundation.org/LSB_4.0.0/LSB-Languages/…
OregonTrail
1
Ini dengan mudah solusi terbaik untuk portabilitas
ecnepsnai
17

Tidak ada cara untuk membuatnya bekerja.

Salah satu caranya adalah dengan menggunakan file sementara seperti:

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

Ini bekerja pada keduanya

analog
sumber
Apakah ada alasan bahwa SOME_FILE = "$ (sed -s" s / abc / def / "some / file)"; echo "$ SOME_FILE"> some / file; tidak akan bekerja sebagai gantinya
Jason Gross
Sepertinya Anda akan dibatasi oleh ukuran maksimum variabel bash, bukan? Tidak yakin itu akan berfungsi dengan file GB.
analog
3
Jika Anda membuat file sementara, mengapa tidak memberikan ekstensi untuk membuat file cadangan (yang kemudian dapat Anda hapus) seperti yang disarankan @kine di bawah ini?
Alex Dupuy
13

Jawab: Tidak.

Jawaban yang semula diterima sebenarnya tidak melakukan apa yang diminta (sebagaimana disebutkan dalam komentar). (Saya menemukan jawaban ini ketika mencari alasan a file-emuncul "secara acak" di direktori saya.)

Tampaknya tidak ada cara sed -iuntuk bekerja secara konsisten pada MacOS dan Linuces.

Rekomendasi saya, untuk apa nilainya, bukan untuk memperbarui di tempat dengan sed(yang memiliki mode kegagalan yang kompleks), tetapi untuk menghasilkan file baru dan mengganti nama mereka sesudahnya. Dengan kata lain: hindari -i.

Steve Powell
sumber
9

The -ipilihan adalah bukan bagian dari POSIX Sed . Metode yang lebih portabel adalah menggunakan Vim dalam mode Ex:

ex -sc '%s/alfa/bravo/|x' file
  1. % pilih semua baris

  2. s menggantikan

  3. x Simpan dan tutup

Steven Penny
sumber
Ini jawaban yang benar. Fitur utama dari POSIX adalah portabilitas.
Kajukenbo
9

Berikut ini versi lain yang berfungsi di Linux dan macOS tanpa menggunakan evaldan tanpa harus menghapus file cadangan. Ini menggunakan array Bash untuk menyimpan sedparameter, yang lebih bersih daripada menggunakan 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

Ini tidak membuat file cadangan, juga file dengan kutipan yang ditambahkan.

nwinkler
sumber
2
ini jawaban yang benar. Saya akan menyederhanakannya sedikit, tetapi idenya bekerja dengan sempurna sedi=(-i) && [ "$(uname)" == "Darwin" ] && sedi=(-i '') sed "${sedi[@]}" -e 's/foo/bar/' target.file
Alex Skrypnyk
Barang bagus! Saya lebih suka versi yang lebih panjang untuk dibaca.
nwinkler
1
Ini cara terbaik bagi saya untuk kompatibel dengan sistem yang berbeda.
Victor Choy
6

Jawaban Steve Powell cukup benar, merujuk pada halaman MAN untuk OSX dan Linux (Ubuntu 12.04) menyoroti ketidakcocokan dalam penggunaan 'in-place' di kedua sistem operasi.

JFYI, seharusnya tidak ada ruang antara -i dan tanda kutip (yang menunjukkan ekstensi file kosong) menggunakan versi sed Linux, sehingga

sed Linux Man Page

#Linux
sed -i"" 

dan

sed halaman OSX Man

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

Saya menyelesaikan ini dalam skrip dengan menggunakan perintah alias'd dan output nama OS dari ' uname ' dalam bash 'jika'. Mencoba menyimpan string perintah yang bergantung pada OS dalam variabel tidak dapat dilakukan ketika menafsirkan kutipan. Penggunaan ' shopt -s expand_aliases ' diperlukan untuk mengembangkan / menggunakan alias yang didefinisikan dalam skrip Anda. Penggunaan shopt dibahas di sini .

Kaya Besar
sumber
1

Jika Anda perlu melakukan sedin-place dalam bashskrip, dan Anda TIDAK ingin in-place dihasilkan dengan file .bkp, dan Anda memiliki cara untuk mendeteksi os (katakanlah, menggunakan ostype.sh ), - maka hack berikut dengan bashshell built-in evalharus bekerja:

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
sumber
0

Anda bisa menggunakan spons. Sponge adalah program unix lama, ditemukan dalam paket moreutils (baik di ubuntu dan mungkin debian, dan di homebrew di mac).

Ini akan buffer semua konten dari pipa, tunggu sampai pipa dekat (mungkin berarti bahwa file input sudah tutup) dan kemudian timpa:

Dari halaman manual :

Ringkasan

sed '...' file | grep '...' | file spons

Guillermo
sumber
3
Pendekatan yang bagus - sayangnya tidak benar-benar membantu dalam situasi asli karena alasan untuk menggunakan sed adalah untuk memiliki sesuatu untuk digunakan dalam skrip pembantu lintas platform yang digunakan pada mesin klien, di mana Anda tidak dapat bergantung pada hal lain selain sistem alat sedang diinstal. Akan sangat membantu orang lain.
dnadlinger
0

Berikut ini berfungsi untuk saya di Linux dan OS X:

sed -i' ' <expr> <file>

misal untuk file yang fberisiaaabbaaba

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

hasil aaaccaacapada Linux dan Mac. Perhatikan ada string yang dikutip berisi spasi , tanpa spasi di antara-i dan. Kutipan tunggal atau ganda keduanya berfungsi.

Di Linux saya menggunakan bashversi 4.3.11 di Ubuntu 14.04.4 dan di Mac versi 3.2.57 di OS X 10.11.4 El Capitan (Darwin 15.4.0).

David G
sumber
1
Wow, saya senang saya tidak mendengarkan semua orang di atas mengatakan itu tidak mungkin dan terus membaca! Ini benar-benar berfungsi. Terima kasih!
Personman
4
Tidak berfungsi untuk saya di Mac OS ... file baru dengan ruang yang ditambahkan ke akhir nama file dibuat
Frédéric
Juga tidak berfungsi untuk saya di Mac (/ usr / bin / sed) - Saya sekarang memiliki file cadangan tambahan dengan ruang yang ditambahkan ke nama file :-(
paul_h
Coba keluarkan ruang dari kutipan tunggal, itu berhasil untuk saya.
dps
0

Saya mengalami masalah ini. Satu-satunya solusi cepat adalah mengganti sed di mac ke versi gnu:

brew install gnu-sed
vikrantt
sumber
Ini menggandakan jawaban yang sudah ada dengan lebih detail dari 2015.
tripleee