หลบหนีสตริงสำหรับรูปแบบการแทนที่ sed


317

ในสคริปต์ทุบตีของฉันฉันมีสตริงภายนอก (ที่ได้รับจากผู้ใช้) ซึ่งฉันควรใช้ในรูปแบบ sed

REPLACE="<funny characters here>"
sed "s/KEYWORD/$REPLACE/g"

ฉันจะหลีกเลี่ยง$REPLACEสตริงเพื่อให้สามารถยอมรับได้อย่างปลอดภัยโดยsedการแทนที่ด้วยตัวอักษร?

หมายเหตุ:KEYWORDเป็น substring ใบ้ตรงกับไม่มี ฯลฯ มันไม่ได้เป็นที่จัดทำโดยผู้ใช้


13
คุณกำลังพยายามหลีกเลี่ยงปัญหา "Little Bobby Tables" หรือไม่หากพวกเขาพูดว่า "/ g -e '/ PASSWORD =. * / PASSWORD = abc / g'"
พอลทอมบลิ

2
หากใช้ bash คุณไม่จำเป็นต้องมีสติ เพียงใช้outputvar="${inputvar//"$txt2replace"/"$txt2replacewith"}".
destenson

@Destenson ฉันคิดว่าคุณไม่ควรวางตัวแปรทั้งสองไว้นอกเครื่องหมายคำพูด Bash สามารถอ่านตัวแปรภายในเครื่องหมายคำพูดคู่ (ในตัวอย่างของคุณช่องว่างสามารถทำให้สิ่งต่าง ๆ เกิดขึ้น)
Camilo Martin

2
ดูเพิ่มเติมที่: stackoverflow.com/q/29613304/45375
mklement0

1
@CamiloMartin ดูความคิดเห็นของฉันด้วยคำตอบของฉันเอง เครื่องหมายคำพูดใน $ {} ไม่ตรงกับเครื่องหมายคำพูดใน ตัวแปรสองตัวไม่ได้อยู่นอกเครื่องหมายคำพูด
destenson

คำตอบ:


268

คำเตือน : สิ่งนี้ไม่พิจารณาถึงการขึ้นบรรทัดใหม่ สำหรับคำตอบเชิงลึกเพิ่มเติมดูคำถามนี้แทน (ขอบคุณ Ed Morton และ Niklas Peter)

โปรดทราบว่าการหลีกหนีทุกอย่างเป็นความคิดที่ไม่ดี Sed ต้องการตัวละครมากมายที่ต้องหลบหนีเพื่อให้ได้ความหมายพิเศษของพวกเขา ตัวอย่างเช่นหากคุณหลีกเลี่ยงตัวเลขในสตริงการแทนที่มันจะเปลี่ยนเป็นการย้อนกลับ

ดังที่ Ben Blank กล่าวว่ามีอักขระเพียงสามตัวเท่านั้นที่จำเป็นต้องใช้ Escape ในสตริงการแทนที่

ESCAPED_REPLACE=$(printf '%s\n' "$REPLACE" | sed -e 's/[\/&]/\\&/g')
# Now you can use ESCAPED_REPLACE in the original sed statement
sed "s/KEYWORD/$ESCAPED_REPLACE/g"

หากคุณต้องการหลีกเลี่ยงKEYWORDสตริงต่อไปนี้คือสิ่งที่คุณต้องการ:

sed -e 's/[]\/$*.^[]/\\&/g'

และสามารถใช้ได้โดย:

KEYWORD="The Keyword You Need";
ESCAPED_KEYWORD=$(printf '%s\n' "$KEYWORD" | sed -e 's/[]\/$*.^[]/\\&/g');

# Now you can use it inside the original sed statement to replace text
sed "s/$ESCAPED_KEYWORD/$ESCAPED_REPLACE/g"

จำไว้ว่าถ้าคุณใช้ตัวละครอื่นที่ไม่ใช่/ตัวคั่นคุณต้องแทนที่เครื่องหมายทับในนิพจน์ข้างต้นด้วยอักขระที่คุณกำลังใช้ ดูความคิดเห็นของ PeterJCLaw สำหรับคำอธิบาย

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


17
เป็นที่น่าสังเกตว่าคุณสามารถหลีกเลี่ยงการใช้เครื่องหมายทับซ้ายเพื่อไม่ให้ใช้ตัวคั่นเหล่านั้นเป็นตัวคั่นได้ รุ่น sed ส่วนใหญ่ (ทั้งหมด?) อนุญาตให้คุณใช้อักขระใด ๆ ก็ได้ตราบใดที่มันเหมาะกับรูปแบบ: $ echo 'foo / bar' | sed s _ / _: _ # foo: bar
PeterJCLaw

2
sed -e 's / (\ / \ | \\\ | &) / \\ & / g' ไม่ทำงานสำหรับฉันใน OSX แต่สิ่งนี้ทำ: sed 's / ([\\\ / &]) / \\ & / g 'และมันสั้นกว่าเล็กน้อย
jcoffland

1
สำหรับการค้นหารูปแบบKEYWORDในGNU sedที่นี่มี 2 ตัวอักษรมากขึ้น^, $ไม่ได้กล่าวถึงข้างต้น:s/[]\/$*.^|[]/\\&/g
Peter.O

1
@Jesse: แก้ไข อันที่จริงแล้วนั่นเป็นความผิดพลาดที่ฉันเตือนไว้ในย่อหน้าแรก ฉันเดาว่าฉันไม่ได้ฝึกในสิ่งที่ฉันเทศนา
เปียโนซอรัส

1
@NeronLeVelu: ผมไม่แน่ใจว่าผมรู้ว่าสิ่งที่คุณหมายถึง แต่ "ไม่มีความหมายพิเศษในท่อหรือตัวแปรมันจะแยกจากเปลือกก่อนที่จะใช้ผลคำพูดคู่ดังนั้นภายในตัวแปรที่มีความปลอดภัยตัวอย่างเช่นลองใช้.. A='foo"bar' echo $A | sed s/$A/baz/ใน bash. อัญประกาศคู่จะได้รับการปฏิบัติเช่นเดียวกับ 'foo' และ 'bar' รอบ ๆ มัน
Pianosaurus

92

คำสั่ง sed ช่วยให้คุณสามารถใช้ตัวละครอื่น ๆ แทนการ/เป็นตัวคั่น:

sed 's#"http://www\.fubar\.com"#URL_FUBAR#g'

เครื่องหมายคำพูดคู่ไม่ใช่ปัญหา


5
คุณยังคงต้องหลบหนี.ซึ่งมีความหมายพิเศษ ฉันแก้ไขคำตอบของคุณ
ypid

ฉันเพิ่งลองทำ: sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' fileด้วยsed '|CLIENTSCRIPT="foo"|a CLIENTSCRIPT2="hello"' fileและนั่นก็ไม่ได้ทำเหมือนกัน
Dimitri Kopriwa

1
เนื่องจากสิ่งนี้ใช้กับการแทนที่เท่านั้นจึงควรพูดว่า: sคำสั่ง (แทน) ของ sed ทำให้คุณสามารถใช้อักขระอื่นแทน / เป็นตัวคั่น นอกจากนี้นี่จะเป็นคำตอบสำหรับวิธีใช้ sed บน URL ด้วยอักขระสแลช ไม่ตอบคำถาม OP ถึงวิธีการหลีกเลี่ยงสตริงที่ป้อนโดยผู้ใช้ซึ่งอาจมี /, \ แต่ยัง # ถ้าคุณตัดสินใจที่จะใช้ และนอกจากนี้ URI ยังมี # ด้วย
papo

2
มันเปลี่ยนชีวิตของฉัน! ขอบคุณ!
Franciscon Santos

48

อักขระตัวอักษรสามตัวเท่านั้นที่ได้รับการปฏิบัติเป็นพิเศษในส่วนคำสั่งแทนที่คือ/(เพื่อปิดส่วนคำสั่ง), \(เพื่อหลีกเลี่ยงตัวอักษร, การอ้างอิงกลับ, & c.) และ&(เพื่อรวมการแข่งขันในการแทนที่) ดังนั้นสิ่งที่คุณต้องทำคือหลบหนีตัวละครทั้งสาม:

sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"

ตัวอย่าง:

$ export REPLACE="'\"|\\/><&!"
$ echo fooKEYWORDbar | sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"
foo'"|\/><&!bar

ฉันคิดว่าขึ้นบรรทัดใหม่ ฉันจะหนีขึ้นบรรทัดใหม่ได้อย่างไร
Alexander Gladysh

2
ระวังพฤติกรรมเริ่มต้นของ echo ที่เกี่ยวกับแบ็กสแลช ในทุบตี echo เริ่มต้นที่จะไม่มีการตีความของเครื่องหมายทับขวาซึ่งทำหน้าที่วัตถุประสงค์ที่นี่ ในขณะที่ประ (sh), echo ตีความ backslash หนีและไม่มีทางเท่าที่ฉันรู้ของการปราบปรามนี้ ดังนั้นในเส้นประ (sh) แทนที่จะเป็น echo $ x ให้พิมพ์ '% s \ n' $ x
Youssef Eldakar

และให้ใช้ตัวเลือก -r ทุกครั้งเมื่อทำการอ่านเพื่อจัดการแบ็กสแลชในอินพุตของผู้ใช้เป็นตัวอักษร
Youssef Eldakar

สำหรับความเข้ากันได้ข้ามแพลตฟอร์มกับเชลล์อื่นคุณควรศึกษาเอกสารนี้เกี่ยวกับการเปลี่ยนอักขระพิเศษ: grymoire.com/Unix/Sed.html#toc-uh-62
Dejay Clayton

2
@Drux อักขระสามตัวเป็นอักขระพิเศษเฉพาะในส่วนคำสั่งแทนที่ อีกมากมายเป็นพิเศษในข้อรูปแบบ
lenz

33

จากนิพจน์ปกติของ Pianosaurus ฉันได้สร้างฟังก์ชันทุบตีที่หนีทั้งคำหลักและการแทนที่

function sedeasy {
  sed -i "s/$(echo $1 | sed -e 's/\([[\/.*]\|\]\)/\\&/g')/$(echo $2 | sed -e 's/[\/&]/\\&/g')/g" $3
}

นี่คือวิธีที่คุณใช้:

sedeasy "include /etc/nginx/conf.d/*" "include /apps/*/conf/nginx.conf" /etc/nginx/nginx.conf

3
ขอบคุณ! หากคนอื่นได้รับข้อผิดพลาดทางไวยากรณ์เมื่อพยายามใช้งานเช่นเดียวกับฉันอย่าลืมใช้งานโดยใช้ bash ไม่ใช่ sh
Konstantin Pereiaslov

1
มีฟังก์ชั่นเพื่อหนีสตริงสำหรับ sed แทนที่จะล้อมรอบ sed หรือไม่?
CMCDragonkai

เฮ้เป็นเพียงคำเตือนทั่วไปเกี่ยวกับการเริ่มต้นท่อด้วยเสียงสะท้อนเช่นนี้การใช้งานตัวเลือก (ดูมากที่สุดman echo) ทำให้เกิดการใช้งานโดยไม่คาดคิดเมื่ออาร์กิวเมนต์ของคุณ$1เริ่มต้นด้วยเส้นประ printf '%s\n' "$1"แต่คุณสามารถเริ่มต้นท่อของคุณด้วย
Pianosaurus

17

มันสายไปนิดที่จะตอบสนอง ... แต่มันมีวิธีที่ง่ายกว่ามากในการทำสิ่งนี้ เพียงแค่เปลี่ยนตัวคั่น (เช่นตัวละครที่แยกฟิลด์) ดังนั้นแทนที่จะคุณเขียนs/foo/bar/s|bar|foo

และนี่คือวิธีง่ายๆในการทำสิ่งนี้:

sed 's|/\*!50017 DEFINER=`snafu`@`localhost`\*/||g'

ผลลัพธ์ที่ได้จะไม่มีข้อ DEFINER ที่น่ารังเกียจ


10
ไม่ต้อง&และ `` ต้องยังคงหลบหนีเช่นเดียวกับตัวคั่นที่ต้องเลือก
mirabilos

3
ที่แก้ไขปัญหาของฉันเนื่องจากฉันมีตัวอักษร "/" ในสตริงแทนที่ ขอบคุณผู้ชาย!
Evgeny Goldin

ทำงานได้สำหรับฉัน สิ่งที่ทำคือพยายามที่จะหลบหนี$ในสตริงที่จะมีการเปลี่ยนแปลงและรักษาความหมายของ$ในสตริงการเปลี่ยน บอกว่าผมต้องการที่จะเปลี่ยน$XXXค่าของตัวแปร$YYY, sed -i "s|\$XXX|$YYY|g" fileทำงานได้ดี
hakunami

11

ปรากฎว่าคุณถามคำถามผิด ฉันยังถามคำถามที่ผิดด้วย เหตุผลที่ผิดคือจุดเริ่มต้นของประโยคแรก: "In the bash script ... "

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

ตัวอย่างเช่นแทน:

function escape-all-funny-characters() { UNKNOWN_CODE_THAT_ANSWERS_THE_QUESTION_YOU_ASKED; }
INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A="$(escape-all-funny-characters 'KEYWORD')"
B="$(escape-all-funny-characters '<funny characters here>')"
OUTPUT="$(sed "s/$A/$B/g" <<<"$INPUT")"

คุณสามารถใช้คุณสมบัติทุบตีเฉพาะ:

INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A='KEYWORD'
B='<funny characters here>'
OUTPUT="${INPUT//"$A"/"$B"}"

BTW การเน้นไวยากรณ์ที่นี่ผิด คำพูดภายนอกจับคู่และคำพูดตกแต่งภายในตรงกัน กล่าวอีกนัยหนึ่งมันดูเหมือน$Aและไม่มี$Bใครพูดถึง แต่มันไม่ใช่ เครื่องหมายคำพูดด้านใน${}ไม่ตรงกับเครื่องหมายคำพูดนอก
destenson

คุณไม่จำเป็นต้องพูดทางด้านขวามือของงานมอบหมาย (เว้นแต่คุณต้องการทำสิ่งที่ชอบvar='has space') - OUTPUT=${INPUT//"$A"/"$B"}ปลอดภัย
Benjamin W.

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

1
ดูคู่มือ : "ค่าทั้งหมดได้รับการขยายตัวหนอน, พารามิเตอร์และการขยายตัวตัวแปร, การทดแทนคำสั่ง, การขยายเลขคณิตและการลบเครื่องหมายคำพูด (รายละเอียดด้านล่าง)" เช่นเดียวกับในเครื่องหมายคำพูดคู่
Benjamin W.

1
ถ้าคุณต้องการใช้ไฟล์บนไฟล์ล่ะ
Efren

1

ใช้ awk - มันสะอาดกว่า:

$ awk -v R='//addr:\\file' '{ sub("THIS", R, $0); print $0 }' <<< "http://file:\_THIS_/path/to/a/file\\is\\\a\\ nightmare"
http://file:\_//addr:\file_/path/to/a/file\\is\\\a\\ nightmare

2
ปัญหาawkคือมันไม่มีอะไรคล้ายกับsed -iซึ่งมีประโยชน์มาก 99% ของเวลา
Tino

นี่เป็นขั้นตอนในทิศทางที่ถูกต้อง แต่ awk ยังตีความ metacharacters บางตัวในการทดแทนของคุณดังนั้นจึงยังไม่ปลอดภัยสำหรับการป้อนข้อมูลของผู้ใช้
Jeremy Huiskamp

0

นี่คือตัวอย่างของ AWK ที่ฉันใช้เมื่อนานมาแล้ว มันเป็น AWK ที่พิมพ์ AWKS ใหม่ AWK และ SED มีความคล้ายคลึงกันอาจเป็นแม่แบบที่ดี

ls | awk '{ print "awk " "'"'"'"  " {print $1,$2,$3} " "'"'"'"  " " $1 ".old_ext > " $1 ".new_ext"  }' > for_the_birds

ดูเหมือนมากเกินไป แต่อย่างใดการรวมกันของคำพูดทำงานเพื่อให้ 'พิมพ์เป็นตัวอักษร ถ้าฉันจำได้ถูกต้องแล้วว่าช่องว่างนั้นล้อมรอบไปด้วยเครื่องหมายคำพูดเช่นนี้: "$ 1" ลองให้ฉันรู้ว่ามันทำงานอย่างไรกับ SED


0

ฉันมีการปรับปรุงฟังก์ชั่น sedeasy ซึ่งจะทำลายด้วยตัวอักษรพิเศษเช่นแท็บ

function sedeasy_improved {
    sed -i "s/$(
        echo "$1" | sed -e 's/\([[\/.*]\|\]\)/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/$(
        echo "$2" | sed -e 's/[\/&]/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/g" "$3"
}

ดังนั้นมันแตกต่างกันอย่างไร $1และ$2ห่อในเครื่องหมายคำพูดเพื่อหลีกเลี่ยงการขยายตัวของเชลล์และรักษาแท็บหรือเว้นวรรคสองครั้ง

ท่อเพิ่มเติม| sed -e 's:\t:\\t:g'(ผมชอบ:เป็นสัญลักษณ์) \tที่แปลงแท็บใน


แต่เห็นความคิดเห็นของฉันเกี่ยวกับคำตอบที่เกลี้ยงเกลาเกี่ยวกับการใช้เสียงสะท้อนในท่อ
Pianosaurus

0

นี่คือรหัสหลบหนีที่ฉันพบ:

* = \x2a
( = \x28
) = \x29

" = \x22
/ = \x2f
\ = \x5c

' = \x27
? = \x3f
% = \x25
^ = \x5e

-1

อย่าลืมความสุขที่เกิดขึ้นกับข้อ จำกัด ของเปลือกหอยรอบ ๆ "และ"

ดังนั้น (เป็น ksh)

Var=">New version of \"content' here <"
printf "%s" "${Var}" | sed "s/[&\/\\\\*\\"']/\\&/g' | read -r EscVar

echo "Here is your \"text\" to change" | sed "s/text/${EscVar}/g"

ทิศทางที่ฉันต้องการสำหรับการหลบหนีจากการค้นหาพบผ่าน google ดังนั้นอาจเป็นประโยชน์สำหรับใครบางคน - จบลงด้วย - sed "s / [& \\\ * \\" \ '\ "') (] / \\ & / g '
MolbOrg

-1

หากคุณเพียงต้องการเปลี่ยนค่าตัวแปรในคำสั่ง sed เพียงแค่ลบตัวอย่าง:

sed -i 's/dev-/dev-$ENV/g' test to sed -i s/dev-/dev-$ENV/g test

-2

หากเกิดกรณีที่คุณกำลังสร้างรหัสผ่านแบบสุ่มเพื่อส่งผ่านเพื่อsedแทนที่รูปแบบจากนั้นคุณเลือกที่จะระมัดระวังเกี่ยวกับชุดอักขระในสตริงแบบสุ่ม หากคุณเลือกรหัสผ่านที่ทำโดยการเข้ารหัสค่าเป็น base64 แสดงว่ามีเพียงอักขระที่เป็นไปได้ทั้งใน base64 และเป็นอักขระพิเศษในsedรูปแบบการแทนที่ อักขระนั้นคือ "/" และถูกลบออกจากรหัสผ่านที่คุณกำลังสร้างได้อย่างง่ายดาย:

# password 32 characters log, minus any copies of the "/" character.
pass=`openssl rand -base64 32 | sed -e 's/\///g'`;

-4

วิธีที่ง่ายกว่าในการทำเช่นนี้คือการสร้างสตริงก่อนมือและใช้เป็นพารามิเตอร์สำหรับ sed

rpstring="s/KEYWORD/$REPLACE/g"
sed -i $rpstring  test.txt

ความล้มเหลวและอันตรายอย่างยิ่งเนื่องจาก REPLACE เป็นผู้ใช้ที่จัดหา: REPLACE=/มอบให้sed: -e expression #1, char 12: unknown option to `s'
Tino
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.