Những ký tự nào cần được thoát khi sử dụng Bash?


206

Có danh sách toàn diện nào về các nhân vật cần phải trốn thoát trong Bash không? Nó có thể được kiểm tra chỉ với sed?

Cụ thể, tôi đã kiểm tra xem có %cần phải trốn thoát hay không. Tôi đã thử

echo "h%h" | sed 's/%/i/g'

và làm việc tốt, mà không thoát %. Có nghĩa là %không cần phải trốn thoát? Đây có phải là một cách tốt để kiểm tra sự cần thiết?

Và tổng quát hơn: họ có phải là những nhân vật giống nhau để trốn thoát shellbashkhông?


4
Nói chung, nếu bạn quan tâm, bạn đang làm sai. Xử lý dữ liệu không bao giờ liên quan đến việc chạy nó thông qua quá trình phân tích và đánh giá được sử dụng cho mã, thực hiện thoát moot. Đây là một song song rất gần với các thực tiễn tốt nhất cho SQL - trong đó Điều đúng là sử dụng các biến liên kết và Điều sai là cố gắng "vệ sinh" dữ liệu được đưa vào thông qua thay thế chuỗi.
Charles Duffy


8
@CharlesDuffy Vâng, nhưng đôi khi những gì công cụ báo cáo đã chuẩn bị đang thực hiện trên phần phụ trợ chỉ là thoát khỏi mọi thứ. Có phải SO "làm sai" vì họ thoát khỏi các bình luận do người dùng gửi trước khi hiển thị chúng trong trình duyệt? Không. Họ đang ngăn XSS. Không quan tâm chút nào là làm sai.
Bắn Parthian

@ParthianShot, nếu công cụ tuyên bố đã chuẩn bị không giữ dữ liệu hoàn toàn nằm ngoài dải mã, những người đã viết nó sẽ bị bắn. Vâng, tôi biết giao thức dây của MySQL được thực hiện theo cách đó; tuyên bố của tôi đứng
Charles Duffy

@CharlesDuffy Và quan điểm của tôi - rằng đôi khi các tùy chọn của bạn là làm cho một cái gì đó hoạt động an toàn bằng cách sử dụng một chuỗi công cụ sẽ tạo ra một sự thay đổi thuần túy, hoặc giảm tám lần thời gian và nỗ lực để làm cho nó đẹp hơn - vẫn đứng vững.
Bắn Parthian

Câu trả lời:


282

Có hai quy tắc dễ dàng và an toàn không chỉ hoạt động shmà còn bash.

1. Đặt toàn bộ chuỗi trong dấu ngoặc đơn

Điều này làm việc cho tất cả các ký tự ngoại trừ trích dẫn duy nhất. Để thoát trích dẫn đơn, hãy đóng trích dẫn trước nó, chèn trích dẫn đơn và mở lại trích dẫn.

'I'\''m a s@fe $tring which ends in newline
'

lệnh sed: sed -e "s/'/'\\\\''/g; 1s/^/'/; \$s/\$/'/"

2. Thoát khỏi mọi char với dấu gạch chéo ngược

Điều này hoạt động cho tất cả các nhân vật ngoại trừ dòng mới. Đối với các ký tự dòng mới sử dụng dấu ngoặc đơn hoặc kép. Chuỗi rỗng vẫn phải được xử lý - thay thế bằng""

\I\'\m\ \a\ \s\@\f\e\ \$\t\r\i\n\g\ \w\h\i\c\h\ \e\n\d\s\ \i\n\ \n\e\w\l\i\n\e"
"

lệnh sed : sed -e 's/./\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.

2b. Phiên bản dễ đọc hơn của 2

Có một bộ ký tự an toàn dễ dàng, như [a-zA-Z0-9,._+:@%/-], có thể bỏ qua để giữ cho nó dễ đọc hơn

I\'m\ a\ s@fe\ \$tring\ which\ ends\ in\ newline"
"

lệnh sed : LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@%/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.


Lưu ý rằng trong một chương trình sed, người ta không thể biết liệu dòng đầu vào cuối cùng có kết thúc bằng một byte dòng mới hay không (trừ khi nó trống). Đó là lý do tại sao cả hai lệnh sed trên đều cho rằng không. Bạn có thể thêm một dòng mới được trích dẫn bằng tay.

Lưu ý rằng các biến shell chỉ được xác định cho văn bản theo nghĩa POSIX. Xử lý dữ liệu nhị phân không được xác định. Đối với các triển khai quan trọng, nhị phân hoạt động ngoại trừ byte NUL (vì các biến được triển khai với chuỗi C và được sử dụng làm chuỗi C, cụ thể là đối số chương trình), nhưng bạn nên chuyển sang ngôn ngữ "nhị phân" như latin1 .


(Bạn có thể dễ dàng xác thực các quy tắc bằng cách đọc thông số POSIX cho sh. Đối với bash, hãy kiểm tra hướng dẫn tham khảo được liên kết bởi @AustinPhillips)


Lưu ý: một biến thể tốt về số 1 có thể được thấy ở đây: github.com/scop/bash-completion/blob/ . Nó không yêu cầu chạy sed, nhưng không yêu cầu bash.
jwd

4
Lưu ý cho bất kỳ ai khác (như tôi!), Những người đấu tranh để có được những hoạt động này .... có vẻ như hương vị của sed bạn nhận được trên OSX không chạy các lệnh sed này một cách chính xác. Chúng hoạt động tốt trên Linux!
dalelane

@dalelane: Không thể kiểm tra ở đây. Vui lòng chỉnh sửa khi bạn có một phiên bản hoạt động trên cả hai.
Jo So

Có vẻ như bạn đã bỏ lỡ chuỗi nên bắt đầu bằng '-' (trừ) hoặc chỉ áp dụng cho tên tệp? - trong trường hợp sau cần một './' ở phía trước.
slashmais

Tôi không chắc ý của bạn là gì. Với các lệnh sed, chuỗi đầu vào được lấy từ stdin.
Jo So

59

định dạng có thể được sử dụng lại làm đầu vào shell

Có một định dạng đặc biệt printf chỉ thị ( %q) được xây dựng cho loại yêu cầu này:

định dạng printf [-v var] [đối số]

 %q     causes printf to output the corresponding argument
        in a format that can be reused as shell input.

Một số mẫu:

read foo
Hello world
printf "%q\n" "$foo"
Hello\ world

printf "%q\n" $'Hello world!\n'
$'Hello world!\n'

Điều này cũng có thể được sử dụng thông qua các biến:

printf -v var "%q" "$foo
"
echo "$var"
$'Hello world\n'

Kiểm tra nhanh với tất cả (128) byte ascii:

Lưu ý rằng tất cả các byte từ 128 đến 255 phải được thoát.

for i in {0..127} ;do
    printf -v var \\%o $i
    printf -v var $var
    printf -v res "%q" "$var"
    esc=E
    [ "$var" = "$res" ] && esc=-
    printf "%02X %s %-7s\n" $i $esc "$res"
done |
    column

Điều này phải hiển thị một cái gì đó như:

00 E ''         1A E $'\032'    34 - 4          4E - N          68 - h      
01 E $'\001'    1B E $'\E'      35 - 5          4F - O          69 - i      
02 E $'\002'    1C E $'\034'    36 - 6          50 - P          6A - j      
03 E $'\003'    1D E $'\035'    37 - 7          51 - Q          6B - k      
04 E $'\004'    1E E $'\036'    38 - 8          52 - R          6C - l      
05 E $'\005'    1F E $'\037'    39 - 9          53 - S          6D - m      
06 E $'\006'    20 E \          3A - :          54 - T          6E - n      
07 E $'\a'      21 E \!         3B E \;         55 - U          6F - o      
08 E $'\b'      22 E \"         3C E \<         56 - V          70 - p      
09 E $'\t'      23 E \#         3D - =          57 - W          71 - q      
0A E $'\n'      24 E \$         3E E \>         58 - X          72 - r      
0B E $'\v'      25 - %          3F E \?         59 - Y          73 - s      
0C E $'\f'      26 E \&         40 - @          5A - Z          74 - t      
0D E $'\r'      27 E \'         41 - A          5B E \[         75 - u      
0E E $'\016'    28 E \(         42 - B          5C E \\         76 - v      
0F E $'\017'    29 E \)         43 - C          5D E \]         77 - w      
10 E $'\020'    2A E \*         44 - D          5E E \^         78 - x      
11 E $'\021'    2B - +          45 - E          5F - _          79 - y      
12 E $'\022'    2C E \,         46 - F          60 E \`         7A - z      
13 E $'\023'    2D - -          47 - G          61 - a          7B E \{     
14 E $'\024'    2E - .          48 - H          62 - b          7C E \|     
15 E $'\025'    2F - /          49 - I          63 - c          7D E \}     
16 E $'\026'    30 - 0          4A - J          64 - d          7E E \~     
17 E $'\027'    31 - 1          4B - K          65 - e          7F E $'\177'
18 E $'\030'    32 - 2          4C - L          66 - f      
19 E $'\031'    33 - 3          4D - M          67 - g      

Trong đó trường thứ nhất là giá trị hexa của byte, trường thứ hai chứa Enếu ký tự cần được thoát và trường thứ ba hiển thị thoát trình bày ký tự.

Tại sao ,?

Bạn có thể thấy một số nhân vật không cần phải luôn luôn trốn thoát, như ,, }{.

Vì vậy, không phải luôn luôn nhưng đôi khi :

echo test 1, 2, 3 and 4,5.
test 1, 2, 3 and 4,5.

hoặc là

echo test { 1, 2, 3 }
test { 1, 2, 3 }

nhưng quan tâm:

echo test{1,2,3}
test1 test2 test3

echo test\ {1,2,3}
test 1 test 2 test 3

echo test\ {\ 1,\ 2,\ 3\ }
test  1 test  2 test  3

echo test\ {\ 1\,\ 2,\ 3\ }
test  1, 2 test  3 

Điều này có một vấn đề là, khi gọi pritnf qua bash / sh, chuỗi đầu tiên phải được thoát vỏ cho bash / sh
ThorSummoner

1
@ThorSummoner, không phải nếu bạn chuyển chuỗi dưới dạng đối số theo nghĩa đen sang shell từ một ngôn ngữ khác (nơi bạn có lẽ đã biết cách trích dẫn). Trong Python: subprocess.Popen(['bash', '-c', 'printf "%q\0" "$@"', '_', arbitrary_string], stdin=subprocess.PIPE, stdout=subprocess.PIPE).communicate()sẽ cung cấp cho bạn một phiên bản được trích dẫn chính xác arbitrary_string.
Charles Duffy

1
Bash của FYI %qđã bị hỏng trong một thời gian dài - Nếu tâm trí của tôi phục vụ tốt cho tôi, một lỗi đã được sửa (nhưng vẫn có thể bị hỏng) vào năm 2013 sau khi bị phá vỡ trong 10 năm. Vì vậy, đừng dựa vào nó.
Jo So

@CharlesDuffy Tất nhiên, một khi bạn ở vùng đất Python, shlex.quote()(> = 3.3, pipes.quote()- không có giấy tờ - đối với các phiên bản cũ hơn) cũng sẽ thực hiện công việc và tạo ra một phiên bản dễ đọc hơn (thêm dấu ngoặc kép và thoát, khi cần thiết) mà không cần phải sinh ra một cái vỏ.
Thomas Perl

1
Cảm ơn bạn để thêm ghi chú đặc biệt về ,. Tôi đã rất ngạc nhiên khi biết rằng Bash tích hợp printf -- %q ','mang lại \,, nhưng /usr/bin/printf -- %q ','cho ,(không thoát ra được). Tương tự cho ký tự khác: {, |, }, ~.
kevinarpe

34

Để cứu người khác khỏi phải RTFM ... trong bash :

Kèm theo ký tự trong dấu ngoặc kép giữ gìn giá trị văn chương của tất cả các ký tự trong dấu ngoặc kép, với ngoại lệ của $, `, \, và, khi mở rộng lịch sử được kích hoạt, !.

... Vì vậy, nếu bạn thoát khỏi những điều đó (và bản thân trích dẫn, tất nhiên) có lẽ bạn sẽ ổn.

Nếu bạn thận trọng hơn 'khi nghi ngờ, hãy thoát khỏi cách tiếp cận', có thể tránh việc thay thế các ký tự có ý nghĩa đặc biệt bằng cách không thoát các ký tự định danh (ví dụ: chữ cái, số hoặc '_'). Những điều này rất khó xảy ra (nghĩa là trong một số vỏ POSIX-ish kỳ lạ) có ý nghĩa đặc biệt và do đó cần phải được thoát ra.


1
đây là hướng dẫn được trích dẫn ở trên: gnu.org/software/bash/manual/html_node/Double-Quotes.html
code_monk

Đây là một câu trả lời ngắn gọn, ngọt ngào và chủ yếu là chính xác (+1 cho điều đó) nhưng có lẽ tốt hơn là sử dụng các trích dẫn đơn - xem câu trả lời dài hơn của tôi.
Jo So

26

Sử dụng print '%q' kỹ thuật này , chúng ta có thể chạy một vòng lặp để tìm ra các ký tự đặc biệt:

#!/bin/bash
special=$'`!@#$%^&*()-_+={}|[]\\;\':",.<>?/ '
for ((i=0; i < ${#special}; i++)); do
    char="${special:i:1}"
    printf -v q_char '%q' "$char"
    if [[ "$char" != "$q_char" ]]; then
        printf 'Yes - character %s needs to be escaped\n' "$char"
    else
        printf 'No - character %s does not need to be escaped\n' "$char"
    fi
done | sort

Nó cho đầu ra này:

No, character % does not need to be escaped
No, character + does not need to be escaped
No, character - does not need to be escaped
No, character . does not need to be escaped
No, character / does not need to be escaped
No, character : does not need to be escaped
No, character = does not need to be escaped
No, character @ does not need to be escaped
No, character _ does not need to be escaped
Yes, character   needs to be escaped
Yes, character ! needs to be escaped
Yes, character " needs to be escaped
Yes, character # needs to be escaped
Yes, character $ needs to be escaped
Yes, character & needs to be escaped
Yes, character ' needs to be escaped
Yes, character ( needs to be escaped
Yes, character ) needs to be escaped
Yes, character * needs to be escaped
Yes, character , needs to be escaped
Yes, character ; needs to be escaped
Yes, character < needs to be escaped
Yes, character > needs to be escaped
Yes, character ? needs to be escaped
Yes, character [ needs to be escaped
Yes, character \ needs to be escaped
Yes, character ] needs to be escaped
Yes, character ^ needs to be escaped
Yes, character ` needs to be escaped
Yes, character { needs to be escaped
Yes, character | needs to be escaped
Yes, character } needs to be escaped

Một số kết quả, như ,nhìn một chút nghi ngờ. Sẽ rất thú vị khi có được đầu vào của @ CharlesDuffy về điều này.


2
Bạn có thể đọc câu trả lời để ,trông hơi nghi ngờ ở đoạn cuối câu trả lời của tôi
F. Hauri

2
Hãy nhớ rằng %qkhông biết nơi nào trong vỏ bạn đang dự định sử dụng nhân vật, vì vậy nó sẽ thoát khỏi tất cả các ký tự có thể có ý nghĩa đặc biệt trong bất kỳ bối cảnh vỏ có thể nào. ,Bản thân nó không có ý nghĩa đặc biệt với vỏ của cô ấy, nhưng như @ F.Hauri đã chỉ ra trong câu trả lời của anh ấy, nó có một ý nghĩa đặc biệt trong {...}việc mở rộng cú đúp: gnu.org/savannah-checkouts/gnu/bash/manual/ trộm Điều này giống như! mà cũng chỉ yêu cầu mở rộng trong các tình huống cụ thể, không nói chung: echo Hello World!hoạt động tốt, nhưng echo test!testsẽ thất bại.
Mecki

18

Các nhân vật cần thoát là khác nhau trong vỏ Bourne hoặc POSIX so với Bash. Nói chung (rất) Bash là một superset của những cái vỏ đó, vì vậy bất cứ thứ gì bạn thoát ra đều shellphải được thoát ra trong Bash.

Một quy tắc chung tốt đẹp sẽ là "nếu nghi ngờ, hãy thoát khỏi nó". Nhưng thoát khỏi một số nhân vật mang lại cho họ một ý nghĩa đặc biệt, như thế \n. Chúng được liệt kê trong các man bashtrang dưới Quotingecho.

Ngoài ra, thoát khỏi bất kỳ ký tự không phải là chữ và số, nó sẽ an toàn hơn. Tôi không biết về một danh sách dứt khoát duy nhất.

Các trang người đàn ông liệt kê tất cả chúng ở đâu đó, nhưng không phải ở một nơi. Học ngôn ngữ, đó là cách để chắc chắn.

Một trong đó đã bắt tôi ra là !. Đây là một nhân vật đặc biệt (mở rộng lịch sử) trong Bash (và csh) nhưng không phải trong vỏ Korn. Thậm chí echo "Hello world!"đưa ra vấn đề. Sử dụng dấu ngoặc đơn, như thường lệ, loại bỏ ý nghĩa đặc biệt.


1
Tôi đặc biệt thích một quy tắc chung tốt đẹp sẽ là "nếu nghi ngờ, hãy thoát khỏi nó" . Vẫn còn nghi ngờ liệu kiểm tra với sedlà đủ tốt để xem nếu nó phải được thoát. Cảm ơn câu trả lời của bạn!
fedorqui 'SO ngừng gây hại'

2
@fedorqui: Kiểm tra với sedlà không cần thiết, bạn có thể kiểm tra với hầu hết mọi thứ. sedkhông phải là vấn đề, bashlà. Trong các trích dẫn đơn không có ký tự đặc biệt (ngoại trừ các trích dẫn đơn), bạn thậm chí không thể thoát các ký tự ở đó. Một sedlệnh thường phải nằm trong các dấu ngoặc đơn vì các siêu ký tự RE có quá nhiều phần trùng lặp với các siêu ký tự shell để được an toàn. Ngoại lệ là khi nhúng các biến shell, phải được thực hiện cẩn thận.
cdarke

5
Kiểm tra với echo. Nếu bạn nhận ra những gì bạn đưa vào, nó không cần phải thoát ra. :)
Mark Reed

6

Tôi đoán rằng bạn đang nói về chuỗi bash. Có nhiều loại chuỗi khác nhau có một bộ yêu cầu khác nhau để thoát. ví dụ. Chuỗi trích dẫn đơn khác với chuỗi trích dẫn kép.

Tài liệu tham khảo tốt nhất là phần Trích dẫn của hướng dẫn bash.

Nó giải thích những nhân vật cần thoát. Lưu ý rằng một số ký tự có thể cần thoát tùy thuộc vào tùy chọn nào được bật, chẳng hạn như mở rộng lịch sử.


3
Vì vậy, nó xác nhận rằng thoát là một khu rừng như vậy mà không có một giải pháp dễ dàng, sẽ phải kiểm tra từng trường hợp. Cảm ơn!
fedorqui 'SO ngừng gây hại'

@fedorqui Như với bất kỳ ngôn ngữ nào, có một bộ quy tắc phải tuân theo. Đối với thoát chuỗi bash, bộ quy tắc khá nhỏ như được mô tả trong hướng dẫn. Chuỗi dễ sử dụng nhất là dấu ngoặc đơn vì không có gì cần thoát. Tuy nhiên, không có cách nào để bao gồm một trích dẫn trong một chuỗi trích dẫn.
Austin Phillips

@fedorqui. Đó không phải là một khu rừng. Chạy trốn là khá khả thi. Xem bài viết mới của tôi.
Jo So

@fedorqui Bạn không thể sử dụng một trích dẫn trong một chuỗi trích dẫn đơn nhưng bạn có thể "thoát" nó bằng một cái gì đó như: 'văn bản' "'' 'thêm văn bản'
CR.

4

Tôi nhận thấy rằng bash tự động thoát một số ký tự khi sử dụng tự động hoàn thành.

Ví dụ: nếu bạn có một thư mục có tên dir:A, bash sẽ tự động hoàn thànhdir\:A

Sử dụng điều này, tôi đã chạy một số thử nghiệm bằng cách sử dụng các ký tự của bảng ASCII và rút ra các danh sách sau:

Các ký tự bash thoát khi tự động hoàn thành : (bao gồm khoảng trắng)

 !"$&'()*,:;<=>?@[\]^`{|}

Nhân vật bash không thoát :

#%+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~

(Tôi đã loại trừ /, vì nó không thể được sử dụng trong tên thư mục)


2
Nếu bạn thực sự muốn có một danh sách toàn diện, tôi khuyên bạn nên xem xét các ký tự printf %qnào và không sửa đổi nếu được chuyển thành đối số - lý tưởng nhất là đi qua toàn bộ bộ ký tự.
Charles Duffy

Có những trường hợp ngay cả với chuỗi dấu nháy đơn, bạn có thể muốn thoát các chữ cái và số để tạo ra các ký tự đặc biệt. Ví dụ: tr '\ n' '\ t' giúp dịch các ký tự dòng mới thành các ký tự tab.
Dick Guertin
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.