alat yang tidak berorientasi garis untuk penggantian string?

13

Baru-baru ini saya mengajukan pertanyaan tentang cara menghapus karakter baris baru jika itu terjadi setelah karakter tertentu lainnya.

Alat pemrosesan teks Unix sangat kuat, tetapi hampir semuanya berhubungan dengan baris teks, yang sebagian besar baik-baik saja ketika input sesuai dengan memori yang tersedia.

Tetapi apa yang harus saya lakukan jika saya ingin mengganti urutan teks dalam file besar yang tidak mengandung baris baru?

Misalnya ganti <foobar>dengan \n<foobar>tanpa membaca input baris demi baris? (Karena hanya ada satu baris dan panjangnya 2.5G karakter).

MattBianco
sumber
1
Apakah Anda terbuka untuk menggunakan perlatau python?
iruvar
Perl baik-baik saja. Saya baru saja menemukan gsar( home.online.no/~tjaberg ) yang akan saya coba.
MattBianco

Jawaban:

12

Hal pertama yang terjadi pada saya ketika menghadapi masalah jenis ini adalah mengubah pemisah catatan. Di sebagian besar alat, ini diatur \nsecara default tetapi itu bisa diubah. Sebagai contoh:

  1. Perl

    perl -0x3E -pe 's/<foobar>/\n$&/' file
    

    Penjelasan

    • -0: ini mengatur pemisah rekaman input ke karakter yang diberi nilai heksadesimalnya . Dalam hal ini, saya mengaturnya ke >nilai hex yang mana 3E. Format umum adalah -0xHEX_VALUE. Ini hanya trik untuk memecah garis menjadi potongan-potongan yang bisa dikelola.
    • -pe: cetak setiap baris input setelah menerapkan skrip yang diberikan oleh -e.
    • s/<foobar>/\n$&/: substitusi sederhana. Apa $&pun yang cocok, dalam hal ini <foobar>.
  2. awk

    awk '{gsub(/foobar>/,"\n<foobar>");printf "%s",$0};' RS="<" file
    

    Penjelasan

    • RS="<": atur pemisah rekaman input ke >.
    • gsub(/foobar>/,"\n<foobar>"): gantikan semua kasus foobar>dengan \n<foobar>. Perhatikan bahwa karena RStelah diatur ke <, semua <dihapus dari file input (begitulah cara awkkerjanya) sehingga kami harus mencocokkan foobar>(tanpa a <) dan menggantinya dengan \n<foobar>.
    • printf "%s",$0: cetak "garis" saat ini setelah substitusi. $0adalah catatan saat ini awksehingga akan menampung apa pun sebelum <.

Saya mengujinya pada 2,3 GB, file baris tunggal yang dibuat dengan perintah ini:

for i in {1..900000}; do printf "blah blah <foobar>blah blah"; done > file
for i in {1..100}; do cat file >> file1; done
mv file1 file

Baik awkdan perljumlah diabaikan digunakan memori.

terdon
sumber
Pernahkah Anda mencoba Tie::File perldoc.perl.org/Tie/File.html . Saya pikir itu fitur terbaik Perlketika berhadapan dengan file besar.
cuonglm
@ Gnouc saya sudah bermain sedikit, ya. Tapi i) OP telah menyatakan tidak suka Perl dalam pertanyaan lain jadi saya ingin membuatnya tetap sederhana ii) Saya cenderung menghindari menggunakan modul eksternal kecuali benar-benar diperlukan dan iii) Menggunakan modul Tie :: File akan membuat sintaksis jauh lebih sedikit bersih.
terdon
Setuju. Sedikit catatan yang Tie::Filemerupakan modul inti sejak itu v5.7.3.
cuonglm
9

gsar (pencarian umum dan ganti) adalah alat yang sangat berguna untuk tujuan ini.

Sebagian besar jawaban untuk pertanyaan ini menggunakan alat berbasis catatan dan berbagai trik untuk membuatnya beradaptasi dengan masalah, seperti mengalihkan karakter pemisah rekaman default ke sesuatu yang dianggap cukup sering terjadi pada input sehingga tidak membuat setiap rekaman terlalu besar untuk ditangani.

Dalam banyak kasus ini sangat bagus dan bahkan dapat dibaca. Saya lakukan seperti masalah yang dapat dengan mudah / efisien diselesaikan dengan alat di mana-mana-tersedia seperti awk, tr, seddan shell Bourne.

Melakukan pencarian biner dan mengganti dalam file besar sembarang dengan konten acak tidak cocok untuk alat unix standar ini.

Beberapa dari Anda mungkin berpikir ini curang, tetapi saya tidak melihat bagaimana menggunakan alat yang tepat untuk pekerjaan itu bisa salah. Dalam hal ini adalah program C yang disebut gsardilisensikan di bawah GPL v2 , jadi saya sedikit terkejut bahwa tidak ada paket untuk alat yang sangat berguna ini di gentoo , redhat , atau ubuntu .

gsarmenggunakan varian biner dari algoritma pencarian string Boyer-Moore .

Penggunaan mudah:

gsar -F '-s<foobar>' '-r:x0A<foobar>'

di mana -Fberarti mode "filter", yaitu baca stdintulis stdout. Ada metode untuk beroperasi pada file juga. -smenentukan string pencarian dan -rpenggantinya. Notasi titik dua dapat digunakan untuk menentukan nilai byte sewenang-wenang.

Mode case-insensitive didukung ( -i), tetapi tidak ada dukungan untuk ekspresi reguler, karena algoritma menggunakan panjang string pencarian untuk mengoptimalkan pencarian.

Alat ini juga dapat digunakan hanya untuk pencarian, sedikit mirip grep. gsar -bmenampilkan byte byte dari string pencarian yang cocok, dan gsar -lmencetak nama file dan jumlah kecocokan jika ada, sedikit seperti digabungkan grep -ldengan wc.

Alat ini ditulis oleh Tormod Tjaberg (awal) dan Hans Peter Verne (perbaikan).

MattBianco
sumber
Jika GPL akan Anda pertimbangkan untuk mengemasnya untuk sebuah distro :)
Rqomey
1
Sebenarnya saya berpikir agak serius tentang membuat ebuild gentoo untuk itu. Mungkin satu rpm juga. Tapi saya belum pernah membangun paket deb. Sebelumnya, jadi saya berharap seseorang mengalahkan saya untuk itu (karena itu akan memakan waktu lama).
MattBianco
Saya ragu ini banyak penghiburan tetapi homebrew OS X memiliki formula untuk gsar.
crazysim
5

Dalam kasus sempit di mana string target dan penggantian memiliki panjang yang sama, pemetaan memori dapat dilakukan untuk menyelamatkan. Ini sangat berguna jika penggantian harus dilakukan di tempat. Anda pada dasarnya memetakan file ke dalam memori virtual proses, dan ruang alamat untuk pengalamatan 64-bit sangat besar. Perhatikan bahwa file tersebut tidak harus dipetakan ke memori fisik sekaligus , sehingga file yang beberapa kali ukuran memori fisik yang tersedia pada mesin dapat ditangani.

Berikut adalah contoh Python yang menggantikan foobardenganXXXXXX

#! /usr/bin/python
import mmap
import contextlib   
with open('test.file', 'r+') as f:
 with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE)) as m:
   pos = 0
   pos = m.find('foobar', pos)
   while pos > 0:
    m[pos: pos+len('XXXXXX')] = 'XXXXXX'
    pos = m.find('foobar', pos)
iruvar
sumber
4

Ada banyak alat untuk ini:

ddadalah apa yang ingin Anda gunakan jika Anda ingin memblokir file - andal hanya baca sejumlah byte saja beberapa kali saja. Ini dengan mudah menangani memblokir dan membuka blokir aliran file:

tr -dc '[:graph:]' </dev/urandom | dd bs=32 count=1 cbs=8 conv=unblock,sync 2>/dev/null

###OUTPUT###

UI(#Q5\e BKX2?A:Z RAxGm:qv t!;/v!)N

Saya juga menggunakan di tratas karena dapat menangani konversi byte ASCII ke yang lain (atau, dalam hal ini, menghapus byte ASCII yang bukan karakter yang dapat dicetak tanpa spasi). Itulah yang saya gunakan untuk menjawab pertanyaan Anda yang lain pagi ini, pada kenyataannya, ketika saya melakukannya:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n' 

Ada banyak yang mirip . Daftar itu harus menyediakan subset common-denominator terendah yang dengannya Anda mungkin terbiasa.

Tapi, jika saya akan melakukan pemrosesan teks pada file biner 2.5gbs, saya mungkin mulai dengan od. Ini dapat memberi Anda octal dumpatau salah satu dari beberapa format lainnya. Anda dapat menentukan semua jenis opsi - tetapi saya hanya akan melakukan satu byte per baris dalam \Cformat yang diloloskan:

Data yang akan Anda dapatkan odakan teratur pada interval apa pun yang Anda tentukan - seperti yang saya tunjukkan di bawah ini. Tapi pertama-tama - inilah jawaban untuk pertanyaan Anda:

printf 'first\nnewline\ttab spacefoobar\0null' |
od -A n -t c -v -w1 |
sed 's/^ \{1,3\}//;s/\\$/&&/;/ /bd
     /\\[0nt]/!{H;$!d};{:d
    x;s/\n//g}'

Itu sedikit di atas \nbatas pada garis, \0nol, \tabs dan <spaces>sambil mempertahankan \Cstring yang lolos untuk pembatas. Perhatikan Hdan xfungsi yang digunakan - setiap kali sedbertemu pembatas itu menukar isi buffer memorinya. Dengan cara ini sedhanya menyimpan informasi sebanyak yang diperlukan untuk membatasi file secara andal dan tidak menyerah pada buffer overruns - tidak, yaitu, asalkan itu benar-benar bertemu dengan pembatasnya. Selama itu terjadi, sedakan terus memproses inputnya dan odakan terus menyediakannya sampai bertemu EOF.

Seperti apa, outputnya terlihat seperti ini:

first
\nnewline
\ttab
 spacefoobar
\0null

Jadi jika saya mau foobar:

printf ... | od ... | sed ... | 
sed 's/foobar/\
&\
/g'

###OUTPUT###

first
\nnewline
\ttab
 space
foobar

\0null

Sekarang jika Anda ingin menggunakan Cpelarian itu cukup mudah - karena backslash sedsudah berlipat ganda \\lolos dari semua backslash input tunggal, jadi printfdari yang xargssudah ada tidak akan ada masalah menghasilkan output dengan spesifikasi Anda. Tetapi xargs makanlah kutipan shell sehingga Anda harus menggandakan penawaran itu lagi:

printf 'nl\ntab\tspace foobarfoobar\0null' |
PIPELINE |
sed 's/./\\&/g' | 
xargs printf %b | 
cat -A

###OUTPUT###

nl$
tab^Ispace $
foobar$
$
foobar$
^@null%

Itu bisa dengan mudah disimpan ke variabel shell dan output nanti dengan cara yang sama. Yang terakhir sedmenyisipkan \garis miring terbalik sebelum setiap karakter dalam inputnya, dan itu saja.

Dan inilah yang terlihat seperti sebelumnya sed:

printf 'nl\ntab\tspace foobarfoobar\0null' |
od -A n -t c -v -w1

   n
   l
  \n
   t
   a
   b
  \t
   s
   p
   a
   c
   e

   f
   o
   o
   b
   a
   r
   f
   o
   o
   b
   a
   r
  \0
   n
   u
   l
   l
mikeserv
sumber
2

Awk beroperasi pada catatan yang berurutan. Ia dapat menggunakan karakter apa saja sebagai pemisah rekaman (kecuali byte nol pada banyak implementasi). Beberapa implementasi mendukung ekspresi reguler sewenang-wenang (tidak cocok dengan string kosong) sebagai pemisah rekaman, tetapi ini bisa sangat sulit karena pemisah rekaman dipotong dari akhir setiap rekaman sebelum dimasukkan ke dalam $0(AWK GNU menetapkan variabel RTke pemisah rekaman) yang dilucuti dari akhir catatan saat ini). Catatan yang printmengakhiri outputnya dengan pemisah catatan keluaran ORSyang merupakan baris baru secara default dan ditetapkan secara independen dari pemisah catatan input RS.

awk -v RS=, 'NR==1 {printf "input up to the first comma: %s\n", $0}'

Anda dapat secara efektif memilih karakter yang berbeda sebagai pemisah record alat bantu lainnya ( sort, sed, ...) dengan menukar baris baru dengan karakter dengan tr.

tr '\n,' ',\n' |
sed 's/foo/bar/' |
sort |
tr '\n,' ',\n'

Banyak utilitas teks GNU mendukung penggunaan byte nol alih-alih baris baru sebagai pemisah.

Gilles 'SANGAT berhenti menjadi jahat'
sumber