Thoát một chuỗi cho một mô hình thay thế sed


317

Trong tập lệnh bash của tôi, tôi có một chuỗi bên ngoài (nhận được từ người dùng), mà tôi nên sử dụng trong mẫu sed.

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

Làm thế nào tôi có thể thoát khỏi $REPLACEchuỗi để nó được chấp nhận một cách an toàn bằng cách sedthay thế theo nghĩa đen?

LƯU Ý: Đây KEYWORDlà một chuỗi con câm không có kết quả trùng khớp, vv Nó không được cung cấp bởi người dùng.


13
Bạn có đang cố tránh vấn đề "Bàn Bobby nhỏ" nếu họ nói "/ g -e 's / PASSWORD =. * / PASSWORD = abc / g'"?
Paul Tomblin

2
Nếu sử dụng bash, bạn không cần sed. Chỉ cần sử dụngoutputvar="${inputvar//"$txt2replace"/"$txt2replacewith"}".
Destenson

@destenson Tôi nghĩ bạn không nên đặt hai biến bên ngoài dấu ngoặc kép. Bash có thể đọc các biến trong dấu ngoặc kép (trong ví dụ của bạn, khoảng trắng có thể làm hỏng mọi thứ).
Camilo Martin


1
@CamiloMartin, xem nhận xét của tôi về câu trả lời của riêng tôi. Các trích dẫn bên trong $ {} không khớp với các trích dẫn bên trong. Hai biến không nằm ngoài dấu ngoặc kép.
Destenson 18/8/2016

Câu trả lời:


268

Cảnh báo : Điều này không xem xét các dòng mới. Để có câu trả lời sâu hơn, thay vào đó hãy xem câu hỏi SO này . (Cảm ơn, Ed Morton & Niklas Peter)

Lưu ý rằng thoát khỏi mọi thứ là một ý tưởng tồi. Sed cần nhiều nhân vật để được trốn thoát để có được ý nghĩa đặc biệt của chúng. Ví dụ: nếu bạn thoát một chữ số trong chuỗi thay thế, nó sẽ chuyển sang trạng thái phản hồi.

Như Ben Blank đã nói, chỉ có ba ký tự cần được thoát trong chuỗi thay thế (tự thoát, gạch chéo về phía trước để kết thúc câu lệnh và & thay thế tất cả):

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"

Nếu bạn cần thoát KEYWORDchuỗi, sau đây là chuỗi bạn cần:

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

Và có thể được sử dụng bởi:

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"

Hãy nhớ rằng, nếu bạn sử dụng một ký tự không phải /là dấu phân cách, bạn cần thay thế dấu gạch chéo trong các biểu thức ở trên với ký tự bạn đang sử dụng. Xem bình luận của PeterJCLaw để được giải thích.

Đã chỉnh sửa: Do một số trường hợp góc trước đây không được tính, các lệnh trên đã thay đổi nhiều lần. Kiểm tra lịch sử chỉnh sửa để biết chi tiết.


17
Điều đáng chú ý là bạn có thể tránh phải thoát khỏi các dấu gạch chéo về phía trước bằng cách không sử dụng chúng làm dấu phân cách. Hầu hết (tất cả?) Phiên bản sed cho phép bạn sử dụng bất kỳ ký tự nào, miễn là phù hợp với mẫu: $ echo 'foo / bar' | sed s _ / _: _ # foo: bar
PeterJCLaw

2
sed -e 's / (\ / \ | \\\ | &) / \\ & / g' không hoạt động với tôi trên OSX nhưng điều này không: sed 's / ([\\\ / &]) / \\ & / g 'và nó ngắn hơn một chút.
jcoffland

1
Đối với mẫu tìm kiếm KEYWORD, trong GNU sed , đây là 2 ký tự nữa ^, $không được đề cập ở trên:s/[]\/$*.^|[]/\\&/g
Peter.O

1
@Jlie: Đã sửa. Trong thực tế, đó là sai lầm mà tôi cảnh báo trong đoạn đầu tiên. Tôi đoán tôi không thực hành những gì tôi giảng.
Pianosaurus

1
@NeronLeVelu: Tôi không chắc là tôi hiểu ý của bạn, nhưng "không có ý nghĩa đặc biệt trong các đường ống hoặc biến. Nó được phân tách bằng shell trước khi chạy kết quả, vì vậy, dấu ngoặc kép bên trong các biến là an toàn. Ví dụ: hãy thử chạy A='foo"bar' echo $A | sed s/$A/baz/trong bash. Các trích dẫn kép được xử lý giống như 'foo' và 'bar' xung quanh nó.
Pianosaurus

92

Lệnh sed cho phép bạn sử dụng các ký tự khác thay vì /làm dấu phân cách:

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

Các trích dẫn kép không phải là một vấn đề.


5
Bạn vẫn cần phải thoát .mà có ý nghĩa đặc biệt. Tôi chỉnh sửa câu trả lời của bạn.
ypid

Tôi vừa thử làm: sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' filevới sed '|CLIENTSCRIPT="foo"|a CLIENTSCRIPT2="hello"' filevà điều đó không làm như vậy.
Dimitri Kopriwa

1
Bởi vì điều này chỉ áp dụng cho thay thế, nên điều này có nghĩa là: sLệnh (như thay thế) của sed cho phép bạn sử dụng các ký tự khác thay vì / như một dấu phân cách. Ngoài ra, đây sẽ là một câu trả lời cho cách sử dụng sed trên URL với các ký tự gạch chéo. Nó không trả lời câu hỏi OP làm thế nào để thoát một chuỗi được nhập bởi người dùng, có thể chứa /, \, nhưng cũng # nếu bạn quyết định sử dụng chuỗi đó. Và bên cạnh đó, URI có thể chứa # too
papo

2
nó đã thay đổi cuộc đời tôi Cảm ơn bạn!
Franciscon Santos

48

Ba ký tự duy nhất được xử lý đặc biệt trong mệnh đề thay thế là /(để đóng mệnh đề), \(để thoát các ký tự, phản hồi, & c.) Và &(để bao gồm cả khớp trong thay thế). Do đó, tất cả những gì bạn cần làm là thoát khỏi ba nhân vật đó:

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

Thí dụ:

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

Cũng là một dòng mới, tôi nghĩ. Làm thế nào để tôi thoát khỏi một dòng mới?
Alexander Gladysh

2
Hãy cẩn thận những hành vi mặc định của tiếng vang liên quan đến dấu gạch chéo ngược. Trong bash, echo mặc định không có sự giải thích về thoát dấu gạch chéo ngược, phục vụ mục đích ở đây. Mặt khác, trong dash (sh), echo diễn giải dấu gạch chéo ngược thoát ra và không có cách nào, theo như tôi biết, về việc triệt tiêu điều này. Do đó, trong dấu gạch ngang (sh), thay vì echo $ x, hãy in printf '% s \ n' $ x.
Youssef Eldakar

Ngoài ra, luôn luôn sử dụng tùy chọn -r khi thực hiện đọc để xử lý dấu gạch chéo ngược trong đầu vào của người dùng dưới dạng chữ.
Youssef Eldakar

Để tương thích đa nền tảng với các hệ vỏ khác, bạn nên tham khảo tài liệu này về việc thay thế các ký tự đặc biệt của sed: grymoire.com/Unix/Sed.html#toc-uh-62
Dejay Clayton

2
@Drux Ba ký tự là những ký tự đặc biệt duy nhất trong mệnh đề thay thế . Nhiều hơn nữa là đặc biệt trong các điều khoản mẫu.
lenz

33

Dựa trên các biểu thức thông thường của Pianosaurus, tôi đã tạo một hàm bash thoát cả từ khóa và thay thế.

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

Đây là cách bạn sử dụng nó:

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

3
cảm ơn! nếu bất cứ ai khác gặp lỗi cú pháp khi cố gắng sử dụng nó, giống như tôi, chỉ cần nhớ chạy nó bằng bash, chứ không phải sh
Konstantin Pereiaslov

1
Có một chức năng chỉ để thoát khỏi một chuỗi cho sed thay vì quấn quanh sed?
CMCDragonkai

Này, chỉ là một cảnh báo chung về các ống bắt đầu có tiếng vang như thế này: Một số (hầu hết?) Việc thực hiện các tùy chọn lấy tiếng vang (xem man echo), khiến đường ống hành xử bất ngờ khi đối số của bạn $1bắt đầu bằng dấu gạch ngang. Thay vào đó, bạn có thể bắt đầu đường ống của bạn với printf '%s\n' "$1".
Pianosaurus

17

Hơi muộn để trả lời ... nhưng có một cách đơn giản hơn nhiều để làm điều này. Chỉ cần thay đổi dấu phân cách (nghĩa là ký tự phân tách các trường). Vì vậy, thay vì s/foo/bar/bạn viếts|bar|foo .

Và, đây là cách dễ dàng để làm điều này:

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

Kết quả đầu ra không có điều khoản DEFINER khó chịu đó.


10
Không, &và `` vẫn phải được thoát, cũng như dấu phân cách, tùy theo cái nào được chọn.
mirabilos

3
Điều đó đã giải quyết vấn đề của tôi, vì tôi có ký tự "/" trong chuỗi thay thế. Cảm ơn, anh bạn!
Evgeny Goldin

làm việc cho tôi Những gì đang làm là cố gắng thoát $trong chuỗi sắp được thay đổi và duy trì ý nghĩa của $chuỗi thay thế. nói rằng tôi muốn thay đổi $XXXgiá trị của biến $YYY, sed -i "s|\$XXX|$YYY|g" filehoạt động tốt.
hakunami

11

Hóa ra bạn đang hỏi sai câu hỏi. Tôi cũng đã hỏi sai câu hỏi. Lý do sai là bắt đầu câu đầu tiên: "Trong bash của tôi kịch bản ...".

Tôi đã có cùng một câu hỏi và mắc lỗi tương tự. Nếu bạn đang sử dụng bash, bạn không cần sử dụng sed để thay thế chuỗi (và sẽ sạch hơn nhiều khi sử dụng tính năng thay thế được tích hợp trong bash).

Thay vì một cái gì đó như, ví dụ:

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

bạn có thể sử dụng các tính năng bash độc quyền:

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

BTW, cú pháp tô sáng ở đây là sai. Các báo giá bên ngoài phù hợp với & báo giá nội thất phù hợp với. Nói cách khác, nó trông giống như $A$Bkhông được trích dẫn, nhưng chúng không phải. Các trích dẫn bên trong ${}không khớp với các trích dẫn bên ngoài nó.
Destenson 20/07/2016

Bạn thực sự không phải trích dẫn phía bên phải của bài tập (trừ khi bạn muốn làm một cái gì đó như var='has space') - OUTPUT=${INPUT//"$A"/"$B"}là an toàn.
Benjamin W.

Bạn thực sự không phải trích dẫn bên phải của một bài tập (trừ khi bạn muốn nó hoạt động trong thế giới thực và không chỉ là một kịch bản đồ chơi để hiển thị yur mad skilz). Tôi luôn cố gắng trích dẫn mọi mở rộng biến mà tôi không muốn trình diễn diễn giải, trừ khi tôi có một lý do cụ thể không. Bằng cách đó, mọi thứ có xu hướng phá vỡ ít thường xuyên hơn, đặc biệt là khi được cung cấp đầu vào mới hoặc bất ngờ.
Destenson

1
Xem hướng dẫn : "Tất cả các giá trị trải qua mở rộng dấu ngã, mở rộng tham số và biến, thay thế lệnh, mở rộng số học và xóa trích dẫn (chi tiết bên dưới)." Tức là, giống như trong dấu ngoặc kép.
Benjamin W.

1
Điều gì nếu bạn cần sử dụng sed trên một tập tin?
Efren

1

Sử dụng awk - nó sạch hơn:

$ 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
Vấn đề awklà nó không có gì tương tự sed -i, nó cực kỳ tiện dụng 99% thời gian.
Tino

Đây là một bước đi đúng hướng, nhưng awk vẫn diễn giải một số siêu nhân vật thay thế bạn, vì vậy nó vẫn không an toàn cho đầu vào của người dùng.
Jeremy Huiskamp

0

Đây là một ví dụ về AWK tôi đã sử dụng một thời gian trước đây. Đây là một AWK in AWKS mới. AWK và SED giống nhau, nó có thể là một mẫu tốt.

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

Có vẻ quá mức, nhưng bằng cách nào đó, sự kết hợp của các trích dẫn hoạt động để giữ cho 'được in dưới dạng chữ. Sau đó, nếu tôi nhớ chính xác, các biến được bao quanh với các trích dẫn như thế này: "$ 1". Hãy thử nó, cho tôi biết làm thế nào nó hoạt động với SED.


0

Tôi có một cải tiến về chức năng quyến rũ, SILL phá vỡ với các ký tự đặc biệt như tab.

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

Vì vậy, những gì khác nhau? $1$2được bọc trong dấu ngoặc kép để tránh mở rộng vỏ và giữ các tab hoặc dấu cách kép.

Đường ống bổ sung | sed -e 's:\t:\\t:g'(tôi thích :như mã thông báo) mà chuyển đổi một tab trong \t.


Nhưng hãy xem nhận xét của tôi về câu trả lời quyến rũ liên quan đến việc sử dụng tiếng vang trong đường ống.
Pianosaurus

0

Đây là những mã thoát mà tôi đã tìm thấy:

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

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

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

-1

đừng quên tất cả những niềm vui xảy ra với giới hạn vỏ xung quanh "và '

vì vậy (tính bằng 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"

chính xác hướng tôi cần, để thoát kết quả tìm kiếm, được tìm thấy qua google nên có thể hữu ích cho ai đó - đã kết thúc bằng - sed "s / [& \\\ * \\" \ '\ "') (] / \\ & / g '
MolbOrg

-1

Nếu bạn chỉ tìm cách thay thế giá trị Biến trong lệnh sed thì chỉ cần xóa Ví dụ:

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

-2

Nếu trường hợp xảy ra là bạn đang tạo một mật khẩu ngẫu nhiên để vượt qua để sedthay thế mẫu, thì bạn chọn cẩn thận về tập hợp các ký tự trong chuỗi ngẫu nhiên. Nếu bạn chọn một mật khẩu được tạo bằng cách mã hóa một giá trị là base64, thì chỉ có một ký tự có thể có trong cơ sở64 và cũng là một ký tự đặc biệt trong sedmẫu thay thế. Ký tự đó là "/" và dễ dàng bị xóa khỏi mật khẩu bạn đang tạo:

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

-4

Một cách dễ dàng hơn để làm điều này chỉ đơn giản là xây dựng chuỗi trước khi sử dụng và sử dụng nó làm tham số cho sed

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

Thất bại và cực kỳ nguy hiểm, vì REPLACE được người dùng cung cấp: REPLACE=/chosed: -e expression #1, char 12: unknown option to `s'
Tino
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.