công cụ không định hướng để thay thế chuỗi?


13

Gần đây tôi đã hỏi một câu hỏi về cách loại bỏ ký tự dòng mới nếu nó xuất hiện sau một ký tự cụ thể khác.

Các công cụ xử lý văn bản Unix rất mạnh mẽ, nhưng hầu hết tất cả chúng đều xử lý các dòng văn bản, điều này hầu hết đều ổn khi đầu vào vừa với bộ nhớ khả dụng.

Nhưng tôi nên làm gì nếu tôi muốn thay thế một chuỗi văn bản trong một tệp lớn không chứa bất kỳ dòng mới nào?

Chẳng hạn, thay thế <foobar>bằng cách \n<foobar>không đọc từng dòng đầu vào? (vì chỉ có một dòng và nó dài 2,5G ký tự).


1
Bạn đang mở để sử dụng perlhay python?
iruvar

Perl là tốt. Tôi vừa tìm thấy gsar( home.online.no/~tjaberg ) mà tôi sẽ thử.
MattBianco

Câu trả lời:


12

Điều đầu tiên xảy ra với tôi khi gặp phải loại vấn đề này là thay đổi dấu tách bản ghi. Trong hầu hết các công cụ, điều này được đặt thành \nmặc định nhưng có thể thay đổi. Ví dụ:

  1. Perl

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

    Giải trình

    • -0: cái này đặt dấu tách bản ghi đầu vào thành một ký tự cho giá trị thập lục phân của nó . Trong trường hợp này, tôi đang đặt nó thành >giá trị hex 3E. Các định dạng chung là -0xHEX_VALUE. Đây chỉ là một mẹo để chia dòng thành các phần có thể quản lý.
    • -pe: in từng dòng đầu vào sau khi áp dụng tập lệnh được cung cấp bởi -e.
    • s/<foobar>/\n$&/: một sự thay thế đơn giản. Đây $&là bất cứ điều gì đã được khớp, trong trường hợp này <foobar>.
  2. ôi

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

    Giải trình

    • RS="<": đặt dấu tách bản ghi đầu vào thành >.
    • gsub(/foobar>/,"\n<foobar>"): thay thế tất cả các trường hợp foobar>bằng \n<foobar>. Lưu ý rằng vì RSđã được đặt thành <, tất cả <được xóa khỏi tệp đầu vào (đó là cách awkhoạt động) vì vậy chúng tôi cần khớp foobar>(không có a <) và thay thế bằng \n<foobar>.
    • printf "%s",$0: in "dòng" hiện tại sau khi thay thế. $0là bản ghi hiện tại awkvì vậy nó sẽ giữ bất cứ thứ gì trước đó <.

Tôi đã thử nghiệm những thứ này trên một tệp đơn, 2,3 GB được tạo bằng các lệnh sau:

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

Cả awkperlmột lượng không đáng kể đã qua sử dụng của bộ nhớ.


Bạn đã bao giờ thử Tie::File perldoc.perl.org/Tie/File.html . Tôi nghĩ đó là tính năng tốt nhất Perlkhi xử lý các tệp lớn.
cuonglm

@Gnouc Tôi đã chơi với nó một chút, vâng. Nhưng i) OP đã tuyên bố không thích Perl trong một câu hỏi khác nên tôi muốn giữ cho nó đơn giản ii) Tôi có xu hướng tránh sử dụng các mô-đun bên ngoài trừ khi thực sự cần thiết và iii) Sử dụng mô-đun Tie :: File sẽ giúp cú pháp ít hơn đáng kể thông thoáng.
terdon

Đồng ý. Một lưu ý nhỏ đó Tie::Filelà một mô-đun cốt lõi kể từ đó v5.7.3.
cuonglm

9

gsar (tìm kiếm chung và thay thế) là một công cụ rất hữu ích cho chính xác mục đích này.

Hầu hết các câu trả lời cho câu hỏi này sử dụng các công cụ dựa trên bản ghi và các thủ thuật khác nhau để làm cho chúng thích ứng với vấn đề, chẳng hạn như chuyển ký tự phân tách bản ghi mặc định sang một thứ được cho là xảy ra đủ thường xuyên trong đầu vào để không làm cho mỗi bản ghi quá lớn để xử lý.

Trong nhiều trường hợp, điều này là rất tốt và thậm chí có thể đọc được. Tôi làm như vấn đề mà có thể dễ dàng / giải quyết một cách hiệu quả với các công cụ ở khắp mọi nơi-có sẵn như awk, tr, sedvà vỏ bourne.

Thực hiện tìm kiếm nhị phân và thay thế trong một tệp khổng lồ tùy ý với nội dung ngẫu nhiên không phù hợp lắm cho các công cụ unix tiêu chuẩn này.

Một số bạn có thể nghĩ rằng điều này là gian lận, nhưng tôi không thấy cách sử dụng công cụ phù hợp cho công việc có thể sai. Trong trường hợp này, đó là một chương trình C được gọi gsarlà được cấp phép theo GPL v2 , vì vậy tôi khá ngạc nhiên khi không có gói nào cho công cụ rất hữu ích này trong cả gentoo , redhat , hay ub Ubuntu .

gsarsử dụng một biến thể nhị phân của thuật toán tìm kiếm chuỗi Boyer-Moore .

Cách sử dụng rất đơn giản:

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

trong đó -Fcó nghĩa là chế độ "bộ lọc", tức là đọc stdinghi vào stdout. Có các phương pháp để hoạt động trên các tập tin là tốt. -schỉ định chuỗi tìm kiếm và -rthay thế. Ký hiệu dấu hai chấm có thể được sử dụng để xác định các giá trị byte tùy ý.

Chế độ không phân biệt chữ hoa chữ thường được hỗ trợ ( -i), nhưng không hỗ trợ cho các biểu thức thông thường, vì thuật toán sử dụng độ dài của chuỗi tìm kiếm để tối ưu hóa tìm kiếm.

Công cụ này cũng có thể được sử dụng chỉ để tìm kiếm, giống như một chút grep. gsar -bxuất ra các giá trị byte của chuỗi tìm kiếm phù hợp và gsar -lin tên tệp và số lượng trùng khớp nếu có, giống như kết hợp grep -lvới wc.

Công cụ này được viết bởi Tormod Tjaberg (ban đầu) và Hans Peter Verne (cải tiến).


Nếu đó là GPL, bạn sẽ cân nhắc việc đóng gói nó cho một bản phân phối :)
Rqomey

1
Trong thực tế, tôi đang suy nghĩ khá nghiêm túc về việc tạo ra một ebuild gentoo cho nó. Có lẽ một vòng / phút là tốt. Nhưng tôi chưa bao giờ xây dựng gói .deb trước đây, vì vậy tôi hy vọng ai đó sẽ đánh bại tôi với nó (vì nó sẽ khiến tôi mất một thời gian).
MattBianco

Tôi nghi ngờ đây là sự an ủi nhiều nhưng homebrew của OS X có công thức cho gsar.
crazysim

5

Trong trường hợp hẹp khi các chuỗi mục tiêu và chuỗi thay thế có cùng độ dài, ánh xạ bộ nhớ có thể được giải cứu. Điều này đặc biệt hữu ích nếu việc thay thế cần được thực hiện tại chỗ. Về cơ bản, bạn đang ánh xạ tệp vào bộ nhớ ảo của quy trình và không gian địa chỉ cho địa chỉ 64 bit là rất lớn. Lưu ý rằng tệp không nhất thiết phải được ánh xạ vào bộ nhớ vật lý cùng một lúc , do đó, các tệp có kích thước nhiều lần của bộ nhớ vật lý có sẵn trên máy có thể được xử lý.

Đây là một ví dụ Python thay thế foobarbằngXXXXXX

#! /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)

4

Có nhiều công cụ cho việc này:

ddlà những gì bạn muốn sử dụng nếu bạn muốn chặn một tệp - chỉ đọc một số byte nhất định trong một số lần nhất định. Nó có thể xử lý chặn và bỏ chặn các luồng tệp:

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

Tôi cũng sử dụng trở trên vì nó có thể xử lý chuyển đổi bất kỳ byte ASCII nào sang bất kỳ byte nào khác (hoặc, trong trường hợp này, xóa bất kỳ byte ASCII nào không phải là ký tự có thể in không gian). Đó là những gì tôi đã sử dụng để trả lời cho câu hỏi khác của bạn sáng nay, trên thực tế, khi tôi đã làm:

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

nhiều cái tương tự . Danh sách đó sẽ cung cấp một tập hợp mẫu số chung thấp nhất mà bạn có thể làm quen.

Nhưng, nếu tôi định xử lý văn bản trên 2,5gbs tệp nhị phân, tôi có thể bắt đầu với od. Nó có thể cung cấp cho bạn một octal dumphoặc bất kỳ định dạng nào khác. Bạn có thể chỉ định tất cả các loại tùy chọn - nhưng tôi sẽ chỉ thực hiện một byte trên mỗi dòng theo \Cđịnh dạng thoát:

Dữ liệu bạn sẽ nhận được từ od sẽ đều đặn ở bất kỳ khoảng thời gian nào bạn chỉ định - như tôi hiển thị bên dưới. Nhưng trước tiên - đây là câu trả lời cho câu hỏi của bạn:

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}'

Đó là một chút trên các phân định trên \newlines, \0null, \tabs và <spaces>trong khi bảo tồn \Cchuỗi thoát cho dấu phân cách. Lưu ý Hvà các xchức năng được sử dụng - mỗi khi sedgặp một dấu phân cách, nó sẽ hoán đổi nội dung của bộ đệm bộ nhớ. Theo cách nàysed chỉ giữ lại nhiều thông tin nhất định để phân định tệp một cách đáng tin cậy và không chịu thua bộ đệm tràn ngập - không, nghĩa là, miễn là nó thực sự gặp các dấu phân cách của nó. Trong bao lâu, nó sedsẽ tiếp tục xử lý đầu vào của nó và odsẽ tiếp tục cung cấp cho đến khi nó gặp phải EOF.

Như là, đầu ra của nó trông như thế này:

first
\nnewline
\ttab
 spacefoobar
\0null

Vì vậy, nếu tôi muốn foobar :

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

###OUTPUT###

first
\nnewline
\ttab
 space
foobar

\0null

Bây giờ nếu bạn muốn sử dụng các Clối thoát thì khá dễ dàng - vì sedđã có \\dấu gạch chéo ngược kép thoát khỏi tất cả các dấu gạch chéo đầu vào duy nhất của nó, do đó, việc thực printfthi xargssẽ không có vấn đề gì tạo ra đầu ra cho đặc tả của bạn. Nhưng xargs ăn báo giá vỏ nên bạn sẽ cần phải trích dẫn lại lần nữa:

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

###OUTPUT###

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

Điều đó có thể dễ dàng được lưu vào một biến shell và xuất ra sau đó theo cách giống hệt nhau. Cuối cùngsed chèn một \dấu gạch chéo ngược trước mỗi ký tự trong đầu vào của nó, và đó là tất cả.

Và đây là tất cả những gì nó trông giống như trước đây từng sednắm giữ nó:

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

2

Awk hoạt động trên các hồ sơ liên tiếp. Nó có thể sử dụng bất kỳ ký tự nào làm dấu tách bản ghi (ngoại trừ byte null trên nhiều cài đặt). Một số triển khai hỗ trợ các biểu thức chính quy tùy ý (không khớp với chuỗi trống) làm trình phân tách bản ghi, nhưng điều này có thể khó sử dụng vì trình phân tách bản ghi bị cắt từ cuối mỗi bản ghi trước khi được xếp vào $0(GNU awk đặt biến RTthành dấu phân cách bản ghi đã bị tước từ cuối bản ghi hiện tại). Lưu ý rằng printchấm dứt đầu ra của nó bằng dấu tách bản ghi đầu ra ORStheo mặc định là một dòng mới và được đặt độc lập với dấu tách bản ghi đầu vào RS.

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

Bạn có thể chọn một ký tự khác một cách hiệu quả làm dấu tách bản ghi cho các công cụ khác ( sort,, sed) bằng cách hoán đổi dòng mới với ký tự đó với tr.

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

Nhiều tiện ích văn bản GNU hỗ trợ sử dụng byte rỗng thay vì dòng mới làm dấu phân cách.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.