Thay thế nhiều chuỗi trong một lần


11

Tôi đang tìm cách thay thế các chuỗi giữ chỗ trong tệp mẫu bằng các giá trị cụ thể, bằng các công cụ Unix phổ biến (bash, sed, awk, có thể perl). Điều quan trọng là việc thay thế được thực hiện trong một lần duy nhất, nghĩa là, những gì đã được quét / thay thế không được xem xét cho sự thay thế khác. Ví dụ: hai lần thử này đều thất bại:

echo "AB" | awk '{gsub("A","B");gsub("B","A");print}'
>> AA

echo "AB" | sed 's/A/B/g;s/B/A/g'
>> AA

Kết quả đúng trong trường hợp này là khóa học BA.

Nói chung, giải pháp nên tương đương với việc quét đầu vào từ trái sang phải để khớp lâu nhất với một trong các chuỗi thay thế đã cho và cho mỗi trận đấu, thực hiện thay thế và tiếp tục từ điểm đó trong đầu vào (không có gì trong số đó đã đọc đầu vào cũng như thay thế được thực hiện nên được xem xét cho phù hợp). Trên thực tế, các chi tiết không quan trọng, chỉ là kết quả của sự thay thế không bao giờ được xem xét cho sự thay thế khác, toàn bộ hoặc một phần.

LƯU Ý Tôi chỉ tìm kiếm các giải pháp chung chính xác. Vui lòng không đề xuất các giải pháp thất bại cho một số đầu vào nhất định (tệp đầu vào, tìm kiếm và thay thế các cặp), tuy nhiên dường như chúng không có vẻ.


Tôi cho rằng họ dài hơn một nhân vật? Đối với điều này, bạn có thể sử dụng tr AB BA.
Kevin

3
Và thẳng thắn, tôi sẽ không ngạc nhiên nếu ai đó coi ghi chú của bạn hơi thô lỗ.
peterph

1
Làm thế nào để bạn mong đợi "chỉ nhận được giải pháp chính xác" khi bạn chưa cung cấp đầu vào hoặc đầu ra mẫu?
jasonwryan

1
Tôi e rằng bạn sẽ cần phải làm chính xác như bạn đang mô tả - phân tích lại từ đầu và thay thế khi bạn đi - tức là không phải bằng các biểu thức thông thường.
peterph

2
Đây là một câu hỏi công bằng, nhưng câu trả lời là bạn cần một trình phân tích cú pháp máy trạng thái , đó là những gì câu trả lời của rici cung cấp (theo kiểu hacker thực sự, tôi nghĩ vậy). Nói cách khác, bạn đang đánh giá thấp sự phức tạp của nhiệm vụ, ala "Tôi muốn phân tích tổng quát (HT | X) ML bằng các biểu thức chính quy" -> Câu trả lời là KHÔNG. Bạn không thể (chỉ) sử dụng sed. Bạn không thể (chỉ) sử dụng awk. AFAIK không có công cụ hiện có sẽ làm điều này ra khỏi hộp. Khai thác của Sans rici, bạn cần viết một số mã.
goldilocks

Câu trả lời:


10

OK, một giải pháp chung. Hàm bash sau yêu cầu 2kđối số; mỗi cặp bao gồm một giữ chỗ và thay thế. Tùy thuộc vào bạn để trích dẫn các chuỗi thích hợp để chuyển chúng vào hàm. Nếu số lượng đối số là số lẻ, một đối số trống ẩn sẽ được thêm vào, điều này sẽ xóa hiệu quả các lần xuất hiện của trình giữ chỗ cuối cùng.

Không thể giữ chỗ hay thay thế có thể chứa các ký tự NUL, nhưng bạn có thể sử dụng các hình chữ C tiêu chuẩn \như \0nếu bạn cần NULs (và do đó bạn bắt buộc phải viết \\nếu bạn muốn a \).

Nó đòi hỏi các công cụ xây dựng tiêu chuẩn sẽ có mặt trên một hệ thống giống như posix (lex và cc).

replaceholder() {
  local dir=$(mktemp -d)
  ( cd "$dir"
    { printf %s\\n "%option 8bit noyywrap nounput" "%%"
      printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
      printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
    } | lex && cc lex.yy.c
  ) && "$dir"/a.out
  rm -fR "$dir"
}

Chúng tôi giả định rằng \đã được thoát nếu cần thiết trong các đối số nhưng chúng tôi cần thoát dấu ngoặc kép, nếu có. Đó là những gì đối số thứ hai cho printf thứ hai làm. Vì lexhành động mặc định là ECHO, chúng ta không cần phải lo lắng về nó.

Ví dụ chạy (với thời gian cho người hoài nghi; nó chỉ là một máy tính xách tay hàng hóa giá rẻ):

$ time echo AB | replaceholder A B B A
BA

real    0m0.128s
user    0m0.106s
sys     0m0.042s
$ time printf %s\\n AB{0000..9999} | replaceholder A B B A > /dev/null

real    0m0.118s
user    0m0.117s
sys     0m0.043s

Đối với các đầu vào lớn hơn, có thể hữu ích khi cung cấp cờ tối ưu hóa ccvà để tương thích với Posix hiện tại, sẽ tốt hơn khi sử dụng c99. Một triển khai thậm chí còn tham vọng hơn có thể cố gắng lưu trữ các tệp thực thi được tạo thay vì tạo chúng mỗi lần, nhưng chúng không thực sự tốn kém để tạo.

Biên tập

Nếu bạn có tcc , bạn có thể tránh những rắc rối khi tạo một thư mục tạm thời và tận hưởng thời gian biên dịch nhanh hơn sẽ giúp ích cho các đầu vào có kích thước bình thường:

treplaceholder () { 
  tcc -run <(
  {
    printf %s\\n "%option 8bit noyywrap nounput" "%%"
    printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
    printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
  } | lex -t)
}

$ time printf %s\\n AB{0000..9999} | treplaceholder A B B A > /dev/null

real    0m0.039s
user    0m0.041s
sys     0m0.031s

Tôi không chắc đây có phải là một trò đùa hay không;)
Ambroz Bizjak

3
@ambrozbizjak: Nó hoạt động, nó nhanh chóng cho đầu vào lớn và nhanh chóng chấp nhận cho đầu vào nhỏ. Nó có thể không sử dụng các công cụ bạn nghĩ đến nhưng chúng là các công cụ tiêu chuẩn. Tại sao nó sẽ là một trò đùa?
rici

4
+1 Không phải là một trò đùa! : D
goldilocks

Đó sẽ là POSIX di động như thế nào fn() { tcc ; } <<CODE\n$(gen code)\nCODE\n. Tôi có thể hỏi mặc dù - đây là một câu trả lời tuyệt vời và tôi đã nâng cấp nó ngay khi tôi đọc nó - nhưng tôi không hiểu điều gì đang xảy ra với mảng shell? Cái "${@//\"/\\\"}"này làm gì
mikeerv

@mikeerv: «Đối với mỗi đối số là giá trị được trích dẫn (" $ @ "), thay thế tất cả (//) lần xuất hiện của trích dẫn (\") bằng (/) dấu gạch chéo ngược (\) theo sau là trích dẫn (\ ") ». Xem phần Mở rộng tham số trong hướng dẫn bash.
rici

1
printf 'STRING1STRING1\n\nSTRING2STRING1\nSTRING2\n' |
od -A n -t c -v -w1 |
sed 's/ \{1,3\}//;s/\\$/&&/;H;s/.*//;x
     /\nS\nT\nR\nI\nN\nG\n1/s//STRING2/
     /\nS\nT\nR\nI\nN\nG\n2/s//STRING1/
     /\\n/!{x;d};s/\n//g;s/./\\&/g' |
     xargs printf %b

###OUTPUT###

STRING2STRING2

STRING1STRING2
STRING1

Một cái gì đó như thế này sẽ luôn thay thế mỗi lần xuất hiện của chuỗi mục tiêu của bạn một lần khi chúng xuất hiện sedtrong luồng tại một lần cắn trên mỗi dòng. Đây là cách nhanh nhất tôi có thể tưởng tượng bạn sẽ làm nó. Sau đó, một lần nữa, tôi không viết C. Nhưng điều này không xử lý đáng tin cậy delimiters null nếu bạn muốn nó. Xem câu trả lời này cho cách nó hoạt động. Điều này không có vấn đề với bất kỳ ký tự shell đặc biệt nào hoặc tương tự - nhưng nó đặc trưng của ngôn ngữ ASCII, hay nói cách khác, odsẽ không xuất ra các ký tự nhiều byte trên cùng một dòng và sẽ chỉ thực hiện một ký tự trên mỗi dòng. Nếu đây là một vấn đề bạn sẽ muốn thêm vào iconv.


+1 Tại sao bạn nói nó chỉ thay thế "sự xuất hiện sớm nhất của chuỗi mục tiêu của bạn"? Trong đầu ra, có vẻ như nó thay thế tất cả chúng. Tôi không yêu cầu xem nó, nhưng điều này có thể được thực hiện theo cách này mà không cần mã hóa các giá trị không?
goldilocks

@goldilocks - Có - nhưng chỉ ngay khi chúng xảy ra. Có lẽ tôi nên điều chỉnh lại nó. Và vâng - bạn có thể chỉ cần thêm một trung gian sedvà lưu tối đa thành null hoặc một cái gì đó sau đó sedviết kịch bản này; hoặc đặt nó vào một hàm shell và cung cấp cho nó các giá trị trong một lần cắn trên mỗi dòng như "/$1/"... "/$2/"- có lẽ tôi cũng sẽ viết các hàm đó ...
mikeerv

Điều này dường như không hoạt động trong trường hợp giữ chỗ PLACE1, PLACE2PLA. PLAluôn luôn thắng. OP nói: "tương đương với quét đầu vào trái sang phải cho một trận đấu dài nhất với một trong các chuỗi thay thế cho" (nhấn mạnh thêm)
rici

@rici - cảm ơn. Sau đó, tôi sẽ phải làm các dấu phân cách null. Trở lại trong nháy mắt.
mikeerv

@rici - Tôi vừa định đăng một phiên bản khác, nó sẽ xử lý những gì bạn mô tả, nhưng nhìn lại nó và tôi không nghĩ mình nên làm vậy. Ông nói dài nhất cho một trong các chuỗi thay thế nhất định. Điều này làm điều đó. Không có dấu hiệu nào cho thấy một chuỗi là tập con của chuỗi khác, chỉ có thể là giá trị được thay thế. Tôi cũng không nghĩ lặp đi lặp lại một danh sách là một cách hợp lệ để giải quyết vấn đề. Với vấn đề như tôi hiểu, đây là một giải pháp hiệu quả.
mikeerv

1

Một perlgiải pháp. Ngay cả khi một số tuyên bố là không thể, tôi đã tìm thấy một cái nhưng nói chung, một trận đấu đơn giản và thay thế là không thể và thậm chí nó còn tệ hơn do việc quay lại NFA, kết quả có thể bất ngờ.

Nói chung, và điều này phải được nói, vấn đề tạo ra các kết quả khác nhau phụ thuộc vào thứ tự và độ dài của các bộ dữ liệu thay thế. I E:

A B
AA CC

AAAkết quả đầu vào trong BBBhoặc CCB.

Đây là mã:

#!/usr/bin/perl

$v='if (0) {} ';
while (($a,$b)=split /\s+/, <DATA>) {
  $k.=$a.'|';
  $v.='elsif ($& eq \''.$a.'\') {print \''.$b.'\'} ';
}
$k.='.';
$v.='else {print $&;}';

eval "
while (<>) {
  \$_ =~ s/($k)/{$v}/geco;
}";  
print "\n";


__DATA__
A    B
B    A
abba baab
baab abbc
abbc aaba

Checkerbunny:

$ echo 'ABBabbaBBbaabAAabbc'|perl script
$ BAAbaabAAabbcBBaaba
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.