แทนที่สตริงจำนวนมากในการส่งผ่านครั้งเดียว


11

ฉันกำลังมองหาวิธีที่จะแทนที่สตริงตัวยึดตำแหน่งในไฟล์แม่แบบด้วยค่าที่เป็นรูปธรรมด้วยเครื่องมือ Unix ทั่วไป (ทุบตี, sed, awk, อาจ perl) เป็นสิ่งสำคัญที่การเปลี่ยนจะดำเนินการในรอบเดียวนั่นคือสิ่งที่สแกน / เปลี่ยนแล้วจะต้องไม่ถูกนำมาพิจารณาสำหรับการเปลี่ยนใหม่ ตัวอย่างเช่นความพยายามสองครั้งนี้ล้มเหลว:

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

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

ผลลัพธ์ที่ถูกต้องในกรณีนี้คือหลักสูตร BA

โดยทั่วไปการแก้ปัญหาควรเทียบเท่ากับการสแกนอินพุตจากซ้ายไปขวาสำหรับการจับคู่ที่ยาวที่สุดไปยังสตริงการแทนที่ที่กำหนดอย่างใดอย่างหนึ่งและสำหรับการแข่งขันแต่ละครั้งทำการแทนที่และดำเนินการต่อจากจุดนั้นบนอินพุต (ไม่มี อ่านอินพุตแล้วหรือการแทนที่ที่ดำเนินการควรพิจารณาให้ตรงกัน) ที่จริงแล้วรายละเอียดไม่สำคัญเพียงแค่ว่าผลของการเปลี่ยนจะไม่ถูกนำมาพิจารณาสำหรับการทดแทนอื่นทั้งหมดหรือบางส่วน

หมายเหตุฉันกำลังมองหาโซลูชันทั่วไปที่ถูกต้องเท่านั้น โปรดอย่าเสนอวิธีแก้ปัญหาที่ล้มเหลวสำหรับอินพุตบางอย่าง (ไฟล์อินพุต, ค้นหาและแทนที่คู่) แต่ไม่น่าจะเป็นไปได้


ฉันคิดว่าพวกมันมีความยาวมากกว่าหนึ่งตัว tr AB BAสำหรับนี้คุณสามารถใช้
Kevin

3
และตรงไปตรงมาฉันจะไม่แปลกใจถ้ามีคนคิดว่าบันทึกของคุณหยาบคายเล็กน้อย
เตอร์

1
คุณคาดหวังว่าจะ "รับโซลูชันที่ถูกต้องเท่านั้น" เมื่อคุณไม่ได้ให้ตัวอย่างอินพุตหรือเอาต์พุต
jasonwryan

1
ฉันเกรงว่าคุณจะต้องทำอย่างที่คุณอธิบาย - แยกวิเคราะห์ตั้งแต่ต้นและแทนที่ตามที่คุณไป - นั่นไม่ใช่การแสดงออกปกติ
เตอร์

2
นี่เป็นคำถามที่ยุติธรรม แต่คำตอบก็คือคุณต้องใช้เครื่องมือแยกวิเคราะห์สถานะซึ่งเป็นคำตอบที่ Rici จัดให้ (ในรูปแบบของแฮ็กเกอร์จริงฉันคิดว่า) ในคำอื่น ๆ คุณประเมินความซับซ้อนของงานต่ำเกินไป "ฉันต้องการแยก ML (HT | X) ML ด้วยนิพจน์ทั่วไป" -> คำตอบคือไม่ คุณไม่สามารถใช้ (แค่) sed คุณไม่สามารถใช้ awk ได้ AFAIK ไม่มีเครื่องมือที่มีอยู่ที่จะทำสิ่งนี้นอกกรอบ การใช้ประโยชน์จาก Sans rici คุณจะต้องเขียนโค้ด
goldilocks

คำตอบ:


10

ตกลงวิธีแก้ปัญหาทั่วไป ฟังก์ชัน bash ต่อไปนี้ต้องการ2kอาร์กิวเมนต์ แต่ละคู่ประกอบด้วยตัวยึดตำแหน่งและการแทนที่ มันขึ้นอยู่กับคุณที่จะพูดสตริงอย่างเหมาะสมเพื่อส่งผ่านเข้าไปในฟังก์ชัน หากจำนวนอาร์กิวเมนต์เป็นจำนวนคี่จะมีการเพิ่มอาร์กิวเมนต์ว่างโดยนัยซึ่งจะลบการเกิดขึ้นของตัวยึดตำแหน่งสุดท้ายอย่างมีประสิทธิภาพ

ตัวยึดตำแหน่งหรือการแทนที่อาจไม่มีอักขระ NUL แต่คุณอาจใช้ C \-escapes มาตรฐานเช่น\0ถ้าคุณต้องการNULs (และดังนั้นคุณต้องเขียน\\ถ้าคุณต้องการ a \)

มันต้องใช้เครื่องมือสร้างมาตรฐานซึ่งควรจะปรากฏในระบบเหมือน posix (เล็กและซีซี)

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

เราคิดว่า\มันถูกหลบหนีไปแล้วถ้าจำเป็นในการโต้แย้ง แต่เราจำเป็นต้องหลบหนีอัญประกาศถ้ามี นั่นคือสิ่งที่อาร์กิวเมนต์ที่สองของ printf ตัวที่สองทำ เนื่องจากการlexกระทำเริ่มต้นคือECHOเราไม่ต้องกังวลเกี่ยวกับมัน

ตัวอย่างการรัน (ด้วยการกำหนดเวลาสำหรับผู้สงสัยมันเป็นเพียงแล็ปท็อปสินค้าราคาถูก o):

$ 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

สำหรับปัจจัยการผลิตที่มีขนาดใหญ่ก็อาจจะมีประโยชน์ในการจัดให้มีการเพิ่มประสิทธิภาพให้กับธงccและเข้ากันได้ Posix c99ปัจจุบันมันจะดีกว่าที่จะใช้ การใช้งานที่มีความทะเยอทะยานมากขึ้นอาจพยายามแคชไฟล์เอ็กซีคิ้วท์ที่สร้างขึ้นแทนที่จะสร้างมันในแต่ละครั้ง

แก้ไข

หากคุณมีtccคุณสามารถหลีกเลี่ยงความยุ่งยากในการสร้างไดเรกทอรีชั่วคราวและเพลิดเพลินกับเวลารวบรวมที่เร็วขึ้นซึ่งจะช่วยในอินพุตขนาดปกติ:

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

ฉันไม่แน่ใจว่านี่เป็นเรื่องตลกหรือไม่;)
Ambroz Bizjak

3
@ambrozbizjak: ใช้งานได้รวดเร็วสำหรับอินพุตขนาดใหญ่และยอมรับได้อย่างรวดเร็วสำหรับอินพุตขนาดเล็ก อาจไม่ใช้เครื่องมือที่คุณคิด แต่เป็นเครื่องมือมาตรฐาน ทำไมมันเป็นเรื่องตลก?
rici

4
+1 สำหรับการไม่ตลก! : D
goldilocks

ที่จะ POSIX fn() { tcc ; } <<CODE\n$(gen code)\nCODE\nเหมือนแบบพกพา ฉันสามารถถามได้ไหมว่า - นี่เป็นคำตอบที่ยอดเยี่ยมและฉันก็อัปเกรดทันทีที่ฉันอ่าน - แต่ฉันไม่เข้าใจว่าเกิดอะไรขึ้นกับอาร์เรย์ของเชลล์ สิ่ง"${@//\"/\\\"}"นี้ทำอะไร
mikeserv

@mikeserv: «สำหรับแต่ละอาร์กิวเมนต์เป็นค่าที่ยกมา ("$ @") ให้แทนที่ทั้งหมด (//) การอ้างอิง (\ ") ด้วย (/) เครื่องหมายแบ็กสแลช (\\) ตามด้วยเครื่องหมายคำพูด (\") » ดูการขยายพารามิเตอร์ในคู่มือทุบตี
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

บางสิ่งเช่นนี้จะแทนที่สตริงเป้าหมายของคุณทุกครั้งเพียงครั้งเดียวเมื่อเกิดเหตุการณ์เหล่าsedนี้ในสตรีมด้วยการกัดเพียงครั้งเดียวต่อบรรทัด นี่เป็นวิธีที่เร็วที่สุดที่ฉันสามารถจินตนาการได้ว่าคุณจะทำ จากนั้นอีกครั้งฉันไม่ได้เขียน C แต่นี่จะจัดการกับตัวคั่น null ได้อย่างน่าเชื่อถือหากคุณต้องการ ดูคำตอบนี้เพื่อดูว่ามันทำงานอย่างไร ไม่มีปัญหากับอักขระเชลล์พิเศษใด ๆ ที่มีหรือคล้ายกัน - แต่เป็นโลแคลเฉพาะ ASCII หรือกล่าวอีกนัยหนึ่งodจะไม่ส่งออกอักขระหลายไบต์ในบรรทัดเดียวกันและจะทำอย่างใดอย่างหนึ่งต่อ iconvหากปัญหานี้เป็นปัญหาที่คุณจะต้องการที่จะเพิ่มใน


+1 ทำไมคุณถึงพูดว่ามันเข้ามาแทนที่ "การเกิดขึ้นครั้งแรกของสตริงเป้าหมายของคุณ" เท่านั้น? ในผลลัพธ์มันดูเหมือนว่าจะแทนที่พวกเขาทั้งหมด ฉันไม่ได้ขอให้ดู แต่วิธีนี้สามารถทำได้โดยไม่ต้องเข้ารหัสค่าหรือไม่
goldilocks

@goldilocks - ใช่ - แต่จะเกิดขึ้นทันทีที่เกิดขึ้น บางทีฉันควรจะพูดว่า และใช่ - คุณสามารถเพิ่มตรงกลางsedและบันทึกเป็นโมฆะหรือบางสิ่งบางอย่างแล้วมีที่sedเขียนสคริปต์ของคนนี้; หรือวางไว้ในการทำงานของเชลล์และให้ค่าที่หนึ่งกัดต่อบรรทัดเช่น"/$1/"... "/$2/"- บางทีฉันอาจจะเขียนฟังก์ชั่นเหล่านั้นมากเกินไป ...
mikeserv

นี้ดูเหมือนจะไม่ทำงานในกรณีที่ตัวยึดมีPLACE1, และPLACE2 ชนะตลอด. OP กล่าวว่า: "เทียบเท่ากับการสแกนอินพุตจากซ้ายไปขวาเพื่อจับคู่ที่ยาวที่สุดกับหนึ่งในสตริงการแทนที่ที่กำหนด" (เน้นการเพิ่ม)PLAPLA
rici

@rici - ขอบคุณ จากนั้นฉันจะต้องทำตัวคั่นว่าง ย้อนกลับไปในแฟลช
mikeserv

@rici - ฉันเพิ่งจะโพสต์รุ่นอื่นซึ่งจะจัดการสิ่งที่คุณอธิบาย แต่ดูอีกครั้งและฉันไม่คิดว่าฉันควร เขาบอกว่ายาวที่สุดสำหรับหนึ่งในสตริงการแทนที่ที่กำหนด สิ่งนี้ทำ ไม่มีข้อบ่งชี้ว่าสตริงหนึ่งเป็นชุดย่อยของอีกสตริงเท่านั้นที่อาจมีค่าที่ถูกแทนที่ ฉันไม่คิดว่าการวนซ้ำรายการเป็นวิธีที่ถูกต้องในการแก้ปัญหา เมื่อพิจารณาถึงปัญหาตามที่ฉันเข้าใจนี่เป็นวิธีแก้ปัญหา
mikeserv

1

perlวิธีการแก้ปัญหา แม้ว่าบางคนระบุว่ามันเป็นไปไม่ได้ฉันก็พบหนึ่ง แต่โดยทั่วไปแล้วการจับคู่แบบง่าย ๆ และการแทนที่ไม่สามารถทำได้และแม้จะแย่ลงเพราะการย้อนรอยของ NFA ผลที่ได้อาจไม่คาดคิด

โดยทั่วไปและสิ่งนี้ต้องบอกว่าปัญหาเกิดขึ้นกับผลลัพธ์ที่แตกต่างกันซึ่งขึ้นอยู่กับคำสั่งและความยาวของสิ่งอันดับทดแทน เช่น:

A B
AA CC

และการป้อนข้อมูลAAAผลลัพธ์ในหรือBBBCCB

ที่นี่รหัส:

#!/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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.