Mengapa sed tidak mengenali \ t sebagai tab?

105
sed "s/\(.*\)/\t\1/" $filename > $sedTmpFile && mv $sedTmpFile $filename

Saya mengharapkan sedskrip ini untuk menyisipkan tabdi depan setiap baris $filenametetapi sebenarnya tidak. Untuk beberapa alasan itu memasukkan sebagai tgantinya.

sixtyfootersdude
sumber
1
Karena sed dapat bervariasi antar platform (khususnya, BSD / MacOSX versus Linux), mungkin berguna untuk menentukan platform tempat Anda menggunakan sed.
Isaac
sed "s / (. *) / # \ 1 /" $ namafile | tr '#' '\ t'> $ sedTmpFile && mv $ sedTmpFile $ filename.
pengguna2432405
Untuk pengguna OS X (macOS), lihat pertanyaan ini .
Franklin Yu

Jawaban:

129

Tidak semua versi sedmengerti \t. Cukup masukkan tab literal sebagai gantinya (tekan Ctrl- Vlalu Tab).

Mark Byers
sumber
2
Ah iya; untuk memperjelas: tidak semua versi sed mengerti \tdi bagian pengganti ekspresi (itu dikenali \tdi bagian pencocokan pola dengan baik)
John Weldon
3
awwwwwwwwwwwwwwwwwww, ok itu cukup menarik. Dan aneh. Mengapa Anda membuatnya mengenalinya di satu tempat tetapi tidak di tempat lain ...?
sixtyfootersdude
2
Dipanggil dari skrip, itu tidak akan berfungsi: tab akan diabaikan oleh sh. Misalnya, kode berikut dari skrip shell akan menambahkan $ TEXT_TO_ADD, tanpa membuatnya terlebih dahulu dengan tabulasi: sed "$ {LINE} a \\ $ TEXT_TO_ADD" $ FILE.
Dereckson
2
@Dereckson dan lainnya - lihat jawaban ini: stackoverflow.com/a/2623007/48082
Cheeso
2
Dereckson s / can / can't /?
Douglas Diadakan
41

Menggunakan Bash Anda dapat memasukkan karakter TAB secara terprogram seperti ini:

TAB=$'\t' 
echo 'line' | sed "s/.*/${TAB}&/g" 
echo 'line' | sed 's/.*/'"${TAB}"'&/g'   # use of Bash string concatenation
sedit
sumber
Ini sangat membantu.
Cheeso
1
Anda berada di jalur yang benar dengan $'string'penjelasan yang kurang. Sebenarnya saya curiga, karena penggunaan yang sangat canggung sehingga Anda mungkin memiliki pemahaman yang tidak lengkap (seperti yang kebanyakan kita lakukan dengan bash). Lihat penjelasan saya di bawah ini: stackoverflow.com/a/43190120/117471
Bruno Bronosky
1
Ingatlah bahwa BASH tidak akan memperluas variabel seperti $TABdi dalam tanda kutip tunggal, jadi Anda harus menggunakannya tanda kutip ganda.
nealmcb
Berhati-hatilah saat menggunakan *tanda kutip ganda di dalam ... ini akan diperlakukan sebagai bola, bukan sebagai ekspresi reguler yang Anda inginkan.
levigroker
27

@sedit berada di jalur yang benar, tetapi agak canggung untuk mendefinisikan variabel.

Solusi (khusus pesta)

Cara untuk melakukan ini di bash adalah dengan meletakkan tanda dolar di depan string kutipan tunggal Anda.

$ echo -e '1\n2\n3'
1
2
3

$ echo -e '1\n2\n3' | sed 's/.*/\t&/g'
t1
t2
t3

$ echo -e '1\n2\n3' | sed $'s/.*/\t&/g'
    1
    2
    3

Jika string Anda perlu menyertakan ekspansi variabel, Anda dapat menggabungkan string yang dikutip seperti ini:

$ timestamp=$(date +%s)
$ echo -e '1\n2\n3' | sed "s/.*/$timestamp"$'\t&/g'
1491237958  1
1491237958  2
1491237958  3

Penjelasan

Dalam bash $'string'menyebabkan "ekspansi ANSI-C". Dan itulah yang sebagian besar dari kita harapkan ketika kita menggunakan hal-hal seperti \t, \r, \n, dll Dari: https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html#ANSI_002dC-Quoting

Kata-kata dalam bentuk $ 'string' diperlakukan secara khusus. Kata tersebut diperluas menjadi string , dengan karakter pelolosan garis miring terbalik diganti seperti yang ditentukan oleh standar ANSI C. Urutan escape dengan garis miring terbalik, jika ada, didekodekan ...

Hasil yang diperluas adalah kutipan tunggal, seolah-olah tanda dolar tidak ada.

Solusi (jika Anda harus menghindari pesta)

Menurut saya pribadi, sebagian besar upaya untuk menghindari bash itu konyol karena menghindari bashisme TIDAK * membuat kode Anda portabel. (Kode Anda akan kurang rapuh jika Anda shebang ke bash -eudaripada jika Anda mencoba menghindari bash dan penggunaan sh[kecuali Anda adalah ninja POSIX mutlak].) Tetapi daripada memiliki argumen religius tentang itu, saya hanya akan memberikan yang TERBAIK * jawaban.

$ echo -e '1\n2\n3' | sed "s/.*/$(printf '\t')&/g"
    1
    2
    3

* Jawaban Terbaik? Ya, karena salah satu contoh kesalahan kebanyakan skrip shell anti-bash dalam kodenya adalah penggunaan echo '\t'seperti dalam jawaban @ robrecord . Itu akan bekerja untuk GNU echo, tapi tidak BSD echo. Itu dijelaskan oleh The Open Group di http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16 Dan ini adalah contoh mengapa mencoba menghindari bashism biasanya gagal.

Bruno Bronosky
sumber
8

Saya telah menggunakan sesuatu seperti ini dengan shell Bash di Ubuntu 12.04 (LTS):

Untuk menambahkan baris baru dengan tab, kedua saat pertama cocok:

sed -i '/first/a \\t second' filename

Untuk mengganti pertama dengan tab, kedua :

sed -i 's/first/\\t second/g' filename
Thomas Bratt
sumber
4
Pelarian ganda adalah kuncinya, yaitu gunakan \\tdan tidak \t.
zamnuts
Saya juga harus menggunakan tanda kutip ganda daripada tanda kutip tunggal di Ubuntu 16.04 dan Bash 4.3.
gak
4

Gunakan $(echo '\t'). Anda membutuhkan tanda kutip di sekitar pola.

Misalnya. Untuk menghapus tab:

sed "s/$(echo '\t')//"
robrecord
sumber
5
Lucu sekali Anda menggunakan fitur khusus "GNU echo" (menafsirkan \ t sebagai karakter tab) untuk memecahkan bug khusus "BSD sed" (menafsirkan \ t sebagai 2 karakter terpisah). Agaknya, jika Anda memiliki "GNU echo", Anda juga akan memiliki "GNU sed". Dalam hal ini Anda tidak perlu menggunakan echo. Dengan BSD echo echo '\t'akan mengeluarkan 2 karakter terpisah. Cara portabel POSIX adalah dengan menggunakan printf '\t'. Inilah sebabnya saya katakan: Jangan mencoba membuat kode Anda portabel dengan tidak menggunakan bash. Ini lebih sulit dari yang Anda pikirkan. Menggunakan bashadalah hal paling portabel yang dapat dilakukan kebanyakan dari kita.
Bruno Bronosky
3

Anda tidak perlu menggunakan seduntuk melakukan substitusi padahal sebenarnya Anda hanya ingin menyisipkan tab di depan baris. Penggantian untuk kasus ini adalah operasi yang mahal dibandingkan dengan hanya mencetaknya, terutama saat Anda bekerja dengan file besar. Lebih mudah dibaca juga karena bukan regex.

misalnya menggunakan awk

awk '{print "\t"$0}' $filename > temp && mv temp $filename
anjing hantu74
sumber
0

sedtidak mendukung \t, atau urutan pelarian lainnya seperti \ndalam hal ini. Satu-satunya cara yang saya temukan untuk melakukannya adalah dengan benar-benar memasukkan karakter tab dalam skrip menggunakan sed.

Meskipun demikian, Anda mungkin ingin mempertimbangkan untuk menggunakan Perl atau Python. Berikut skrip Python singkat yang saya tulis yang saya gunakan untuk semua ekspresi reguler aliran:

#!/usr/bin/env python
import sys
import re

def main(args):
  if len(args) < 2:
    print >> sys.stderr, 'Usage: <search-pattern> <replace-expr>'
    raise SystemExit

  p = re.compile(args[0], re.MULTILINE | re.DOTALL)
  s = sys.stdin.read()
  print p.sub(args[1], s),

if __name__ == '__main__':
  main(sys.argv[1:])
Roman Nurik
sumber
2
Dan versi Perl adalah shell one-liner "perl -pe 's / a / b /' filename" atau "sesuatu | perl -pe 's / a / b /'"
tiftik
0

Alih-alih BSD sed, saya menggunakan perl:

ct@MBA45:~$ python -c "print('\t\t\thi')" |perl -0777pe "s/\t/ /g"
   hi
Cees Timmerman
sumber
0

Saya pikir orang lain telah menjelaskan ini cukup untuk pendekatan lain ( sed, AWK, dll). Namun, bashjawaban spesifik saya (diuji pada macOS High Sierra dan CentOS 6/7) mengikuti.

1) Jika OP ingin menggunakan metode cari-dan-ganti yang mirip dengan yang mereka usulkan semula, maka saya akan menyarankan penggunaan perluntuk ini, sebagai berikut. Catatan: garis miring terbalik sebelum tanda kurung untuk regex seharusnya tidak diperlukan, dan baris kode ini mencerminkan cara $1yang lebih baik untuk digunakan daripada \1dengan perloperator substitusi (misalnya per dokumentasi Perl 5 ).

perl -pe 's/(.*)/\t$1/' $filename > $sedTmpFile && mv $sedTmpFile $filename

2) Namun, seperti yang ditunjukkan oleh ghostdog74 , karena operasi yang diinginkan sebenarnya hanya menambahkan tab di awal setiap baris sebelum mengubah file tmp ke file input / target ( $filename), saya akan merekomendasikan perllagi tetapi dengan modifikasi berikut (s):

perl -pe 's/^/\t/' $filename > $sedTmpFile && mv $sedTmpFile $filename
## OR
perl -pe $'s/^/\t/' $filename > $sedTmpFile && mv $sedTmpFile $filename

3) Tentu saja, file tmp tidak berguna , jadi lebih baik melakukan semuanya 'di tempat' (menambahkan -itanda) dan menyederhanakan semuanya menjadi satu baris yang lebih elegan dengan

perl -i -pe $'s/^/\t/' $filename
justincbagley
sumber