Penggantian tab sederhana dan gagal secara misterius

44

Ini seharusnya sangat sederhana, tetapi untuk beberapa alasan itu tidak berfungsi:

sed -i.bak -E 's/\t/  /' file.txt

Alih-alih mengganti karakter tab, itu menggantikan tkarakter. Saya telah mencoba setiap variasi dalam hal ini yang dapat saya pikirkan, bermain dengan mengutip, dll. Saya telah mencari di Google dan menemukan orang lain menggunakan ekspresi yang sangat mirip dan mereka sepertinya bekerja untuk mereka.

Ini -Eadalah hal OS X. Saya pikir kegagalan itu mungkin hasil dari beberapa kekhasan aneh OS X sed, jadi saya mencobanya dengan Ruby juga (tanpa -i), dan mendapat hasil yang sama:

ruby -pe '$_.gsub!(/\t/,"  ")' < file.txt > file.new

Saya menggunakan Bash 3.2.51 pada OS X, dan iTerm, meskipun saya tidak dapat melihat bagaimana hal itu sangat relevan. Saya belum menetapkan variabel lingkungan aneh, meskipun saya dapat memposting apa pun yang menurut Anda mungkin relevan.

Apa yang salah?

UPDATE : Saya harus telah membuat beberapa kesalahan lain atau salah ketik ketika saya mencoba versi Ruby, karena Gilles menunjukkan bahwa itu tidak bekerja (dan saya tidak pernah memiliki dia mengarahkan saya salah!). Saya tidak yakin apa yang terjadi, tetapi saya cukup yakin itu pasti kesalahan saya.

iconoclast
sumber
5
Mungkin Anda harus mencoba untuk mengganti \tdalam sedpernyataan dengan CTRL-V<TAB>mana <TAB>adalah kunci tab dan CTRL-Vmerupakan kunci kontrol dan vditekan bersama-sama.
unxnut
jika ruby ​​juga mendapatkan jawaban yang salah, maka itu bisa menjadi perpustakaan regexp Anda. (Saya telah menguji kedua perintah Anda, dan keduanya mengganti tab dengan 2 spasi.) Dengan demikian semoga jika Anda menginstal Gnu sed juga akan menginstal pustaka yang benar.
ctrl-alt-delor

Jawaban:

64

Sintaks \tuntuk karakter tab di sed tidak standar. Pelarian itu adalah ekstensi sed GNU . Anda menemukan banyak contoh online yang menggunakannya karena banyak orang menggunakan GNU sed (ini adalah implementasi sed pada Linux yang tidak tertanam). Tapi OS X sed , seperti sed * BSD lainnya, tidak mendukung \ttab dan malah memperlakukan \tsebagai backslash yang diikuti oleh t.

Ada banyak solusi, seperti:

  • Gunakan karakter tab literal.

    sed -i.bak 's/  /  /' file.txt
    
  • Gunakan tratau printfuntuk menghasilkan karakter tab.

    sed -i.bak "s/$(printf '\t')/  /" file.txt
    sed -i.bak "s/$(echo a | tr 'a' '\t')/  /" file.txt
    
  • Gunakan sintaks string bash yang memungkinkan backslash lolos .

    sed -i.bak $'s/\t/  /' file.txt
    
  • Gunakan Perl, Python atau Ruby. Cuplikan Ruby yang Anda poskan tidak berfungsi.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Untuk skrip sed yang terdapat dalam ...sedskrip (digunakan melalui -fopsi), karakter tab literal sepertinya satu-satunya kemungkinan bagi saya. Saat mengedit ini dengan vim, set noexpandtabpenting.
Tobias
Peringatan: Hanya gunakan teknik "karakter tab literal" jika Anda ingin rekan kerja Anda kembali di belakang Anda dan memecahkan skrip Anda nanti. Hanya gunakan trteknik itu jika Anda ingin rekan kerja Anda menusuk wajah Anda ketika mereka membaca naskah Anda.
Bruno Bronosky
Apakah tanda kutip ganda kedua salah tempat di blok kode kedua? Saya harus memindahkannya ke tempat kutipan tunggal saat ini berada.
Ellen Spertus
Terima kasih atas tautan ke sintaks string bash ... Saya tidak tahu (dan ini adalah pilihan terbaik, IMHO).
levigroker
sed $'s/<regex>/\t/' file.txtberfungsi untuk menyisipkan, tetapi $tampaknya melanggar skrip saya ketika saya mencoba untuk memasukkan bagian dari regex dalam substitusi saya, yaitu sed $'s,\(ontology/[0-9]\+\),\t\txxx\1xxx\t\t,'memberikan `xxxxxx` dengan nilai kecocokan yang diharapkan diganti dengan` `. Apakah ada yang setara dengan \1saat menggunakan sintaks string bash? Sunting: seharusnya ada karakter unicode U + 231C di tengah xxx <U + 231C> xxx.
Josh
14

Gunakan kutipan spesifik Bash yang memungkinkan Anda menggunakan string seperti dalam C, sehingga karakter tab nyata diteruskan ke sed, bukan urutan escape:

sed -i.bak -E $'s/\t/  /' file.txt
Cristian Ciupitu
sumber
1
Disebut juga "ANSI-C" mengutip jika orang lain ingin mencari lebih banyak info tentangnya.
wisbucky
2
Tampaknya bekerja pada shell bourne apa pun, bekerja pada UNIX non-bash juga. Tidak bekerja pada varian csh.
jornane
3
sed -i $'s/\t/  /g' file.txt 

bekerja untuk saya di OS X dan merupakan perintah yang sama saya gunakan di linux sepanjang waktu.

pengguna193377
sumber
Perhatikan bahwa ini menggantikan semua tab pada setiap baris sedangkan OP hanya bermaksud mengganti yang pertama (menilai dari perintah yang mereka gunakan).
Kusalananda
1

Seperti dicatat, tidak semua sedimplementasi mendukung notasi \tsebagai tab horizontal.

Anda dapat dengan mudah mencapai substitusi dengan:

 perl -pi.old -e 's{\t+}{ }g' file.txt

Ini melakukan penggantian in situ yang menyimpan file asli Anda sebagai "* .old". Perl memungkinkan pembatas alternatif untuk klasik /membuat ekspresi jauh lebih mudah dibaca (yaitu tanpa sindrom "condong tusuk gigi").

The +mengatakan satu atau lebih pengulangan karakter tab harus diganti. The gpengubah memungkinkan penggantian global sepanjang akhir setiap baris.

JRFerguson
sumber
0

Anda juga dapat menggunakan echodi dalam sed:

sed -i "s/$(echo '\t')//g"

saulR
sumber
Perhatikan bahwa echo '\t'hanya akan menampilkan \timplementasi beberapa shell dari echo.
Kusalananda
0

Jika Anda ingin yang lebih kuat sed(mendukung \tdan lebih) daripada yang ada di OS X, instal GNU sed .

vinc17
sumber
Karena itu tidak bekerja dengan Ruby, saya tidak yakin mengapa saya akan menyimpulkan bahwa OS X sedadalah masalahnya. Apakah Anda punya alasan untuk percaya bahwa itulah masalahnya? Saya akan senang menginstal sed GNU jika saya punya alasan untuk percaya itu akan menyelesaikan masalah, tetapi sepertinya saya sudah cukup banyak mengesampingkan itu.
iconoclast
Dengan Ruby, Anda harus menggunakan hanya satu backslash:ruby -pe '$_.gsub!(/\t/," ")' < file.txt
vinc17
0

Jika tidak apa-apa untuk meminta bashatau zshsebagai shell, maka ini adalah solusi termudah yang dapat saya pikirkan:

sed "s/$(echo -n -e "\t")/ /" file.txt

Namun perlu dicatat bahwa echoflag ( -ndan -e) tidak terdefinisi dalam POSIX, sehingga shell sesuai POSIX tidak perlu memahami flag tesis ini, namun banyak yang akan karena alasan kompatibilitas.

Mecki
sumber
-1

Saya terkejut tidak ada yang menyarankan solusi yang sangat sederhana: sed -i.bak -E 's/\\\t/ /' file.txt Itu harus melakukan trik.

Anda perlu melarikan diri dari pelarian (karenanya 3 \ s) untuk memungkinkan sed memahami bahwa Anda mencoba menggunakan karakter dalam ekspresi reguler ketika semuanya diganti ...

Vas
sumber
Mengapa tiga backslash khusus?
Michael Homer
3
Jika saya menggunakan GNU sed, satu \ sudah cukup, karena tidak perlu melarikan diri. Masalahnya adalah bahwa BSD sedtidak mendukung sintaks ini untuk tab.
iconoclast
Tidak berfungsi pada El Capitan saya.
Franklin Yu
-4

Ini berhasil untuk saya.

sed -e 's / [\ t] / / g'

RChristensen
sumber
3
Ini karena Anda menggunakan GNU sed. Ini bukan yang digunakan OP.
Kusalananda