Bisakah saya menggunakan `sed` untuk menerjemahkan karakter seperti dengan` tr`?

14

Saya ingin mengganti satu set karakter dengan karakter yang sesuai dari set lain, sesuatu seperti ini:

original set: ots
"target" set: u.x

foobartest → fuubar.ex.

Terjemahan / transliterasi seperti ini adalah spesialisasi dari trperintah:

$ echo 'foobartest' | tr 'ots' 'u.x'
fuubar.ex.

Sayangnya trtidak mendukung mengubah file di tempat seperti sedhalnya.
Saya ingin menggunakan sedjadi saya tidak perlu menemukan kembali roda juggling file temp.

n.st
sumber
Menjawab sendiri pertanyaan ini karena sepertinya saya tidak dapat menemukan hasil untuk "sed translate karakter". Kata kunci ajaib akhirnya menjadi "transliterate", tapi saya pikir layak membuat fitur ini semudah mungkin ditemukan.
n.st
Sesuatu yang perlu diingat ketika mencoba menerapkan solusi untuk ini: tr(dengan benar) mengabaikan rekursi dalam set pengganti: echo 'abc' | tr ab bxbxc. Solusi primitif mungkin memotong itu xxckarena menerapkan kembali terjemahan ke karakter yang telah diterjemahkan.
n.st
Terkait: tr analog untuk karakter unicode? (GNU sedbertentangan dengan GNU trdapat transliterasi karakter multi-byte)
Stéphane Chazelas
Jika Anda menginginkan kemungkinan lain: perl dapat menerjemahkan, dan -i, dan (kecuali kuno) multibyte. Bukan POSIX, tetapi sangat umum.
dave_thompson_085

Jawaban:

24

sedmemiliki yperintah yang berfungsi seperti tr:

$ echo 'foobartest' | sed 'y/ots/u.x/'
fuubar.ex.

The yperintah bagian POSIX sedspesifikasi , sehingga harus bekerja pada hampir platform apapun.

Dan karena itu sed, Anda dapat memilikinya mengganti file dengan versi yang diedit, membuat Anda tidak perlu repot dengan bisnis file temp (asalkan implementasi Anda sedmendukung -iopsi, yang tidak ditentukan oleh POSIX):

$ sed -i 'y/ots/u.x/' some-file.txt
n.st
sumber
@ StéphaneChazelas Terima kasih telah menunjukkan itu; Saya tidak menyadari pekerjaan batin sampai sekarang. Saya sudah mengedit jawaban saya untuk menyebutkan itu.
n.st
Terima kasih, ini sangat berguna! Saya mengharapkannya untuk bekerja di VIM (8.0.1092 pada CentOS 7.3) tetapi tidak. Tidakkah seharusnya sesuatu dilakukan, VIM lakukan?
dotancohen
1
@dotancohen Hanya karena fungsi substitusi Vim dimodelkan setelah seditu tidak berarti fungsi lain juga. ;) Milis Vim memiliki utas tentang menemukan yang y/abc/def/setara; pilihan terbaik tampaknya :%call setline(".", tr(getline("."),"abc","def")).
n.st
8

Jika seperti dalam kasus Anda, Anda mentransiterasi karakter tanpa mengubah ukurannya (bagaimanapun, beberapa implementasi seperti GNU trhanya mendukung karakter byte tunggal), Anda dapat melakukan:

tr 'ots' 'u.x' < file 1<> file

Artinya, trtimpa file itu sendiri.

Itu lebih baik daripada sed -idi beberapa akun:

  • tidak memerlukan ruang disk tambahan (kecuali untuk beberapa file jarang, kasus khusus salin-tulis)
  • itu mempertahankan nomor inode, kepemilikan, izin, ACL ...
  • berfungsi baik dengan symlink, itu tidak merusak tautan keras
  • itu tidak meninggalkan file sementara berbohong tentang kapan dibunuh.

Salah satu kelemahannya adalah jika itu terputus, file akan berakhir setengah diterjemahkan (dalam hal ini, Anda dapat menjalankannya lagi untuk menyelesaikannya). Beberapa sedimplementasi akan menanganinya dengan benar dengan memastikan file asli tetap tidak berubah kecuali perintah berhasil.

Stéphane Chazelas
sumber
3
Hati-hati menjalankan kembali terjemahan jika Anda memiliki rekursi dalam set terjemahan, mis echo 'abc' | tr ab bx.
n.st
1
@ n.st, ya, itu sebabnya saya katakan dalam kasus ini , meskipun saya setuju itu layak dieja.
Stéphane Chazelas
Pada akhirnya, saya harus bekerja dengan file temp setelah semua: gist.github.com/n-st/048facd0c12f105ac122030fb58b962f - Karakter multibyte membuat tidak mungkin untuk menggunakan GNU trdan dalam lingkungan PXE symlink-berat kami, sed -iadalah menunggu menunggu. terjadi ...: /
n.st
@ n.st, iconv -t cp437tampaknya lebih tepat untuk itu.
Stéphane Chazelas
iconvrusak ketika file input sudah mengandung byte yang di-encode cp437, atau campuran dari beberapa encoding. Jadi sementara itu lebih disukai dalam kasus umum, itu lebih kuat untuk melakukan penggantian manual pada kasus ini.
n.st
4

Sebagai alternatif lain, jika masalah utama Anda adalah kurangnya dukungan untuk mengubah file di tempat, Anda mungkin tertarik pada spongealat dari paket moreutils :

tr 'ots' 'u.x' < file | sponge file

akan menulis file, tetapi hanya terbuka fileuntuk menulis setelah input selesai. Dari halaman manual :

spongemembaca input standar dan menuliskannya ke file yang ditentukan. Tidak seperti pengalihan shell, spons menyerap semua inputnya sebelum membuka file output. Ini memungkinkan membangun saluran pipa yang membaca dari dan menulis ke file yang sama.

Kecuali jika Anda memiliki file yang sangat besar yang tidak dapat disimpan dalam memori, ini spongedapat bekerja untuk Anda.

mindriot
sumber
2
Satu masalah dengan spongeitu masih menimpa filejika trgagal (misalnya jika Anda telah menulis tetapi tidak membaca akses ke file)
Stéphane Chazelas
Oh, memang benar; Saya tidak mengharapkan itu. Terima kasih.
mindriot
Lihat cat file >; fileoperator ksh93 yang menulis output ke tempfile yang diubah namanya menjadi tujuan hanya jika perintah berhasil (tetapi seperti sed -i, yang membuat file baru alih-alih menimpa yang asli).
Stéphane Chazelas