Cách thoát dấu ngoặc đơn trong chuỗi trích dẫn đơn


1016

Hãy nói rằng, bạn có một Bash aliasnhư:

alias rxvt='urxvt'

hoạt động tốt

Tuy nhiên:

alias rxvt='urxvt -fg '#111111' -bg '#111111''

sẽ không hoạt động, và sẽ không:

alias rxvt='urxvt -fg \'#111111\' -bg \'#111111\''

Vì vậy, làm thế nào để bạn kết thúc việc kết hợp mở và đóng dấu ngoặc kép trong một chuỗi khi bạn đã thoát dấu ngoặc kép?

alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''

có vẻ vô duyên mặc dù nó sẽ đại diện cho cùng một chuỗi nếu bạn được phép ghép chúng như thế.


16
Bạn có nhận ra rằng bạn không cần sử dụng dấu ngoặc đơn cho bí danh không? Báo giá kép dễ dàng hơn nhiều.
teknopaul


3
Dấu ngoặc kép lồng nhau có thể thoát được "\"", vì vậy chúng nên được sử dụng theo sở thích để trả lời @ liori bất cứ khi nào có thể.
alan

7
Dấu ngoặc kép hoạt động hoàn toàn khác với dấu ngoặc đơn trong * nix (bao gồm Bash và các công cụ liên quan như Perl), vì vậy thay thế dấu ngoặc kép bất cứ khi nào có vấn đề với dấu ngoặc đơn KHÔNG phải là giải pháp tốt. Dấu ngoặc kép xác định $ ... các biến sẽ được thay thế trước khi thực hiện, trong khi dấu ngoặc đơn chỉ định $ ... sẽ được xử lý theo nghĩa đen.
Chuck Kollars

Nếu bạn đang suy nghĩ, tôi đã sử dụng dấu ngoặc kép nhưng nó vẫn không hoạt động , hãy lấy lại tập lệnh của bạn.
Samy Bencherif

Câu trả lời:


1454

Nếu bạn thực sự muốn sử dụng dấu ngoặc đơn ở lớp ngoài cùng, hãy nhớ rằng bạn có thể dán cả hai loại trích dẫn. Thí dụ:

 alias rxvt='urxvt -fg '"'"'#111111'"'"' -bg '"'"'#111111'"'"
 #                     ^^^^^       ^^^^^     ^^^^^       ^^^^
 #                     12345       12345     12345       1234

Giải thích về cách '"'"'được giải thích như chỉ ':

  1. ' Kết thúc báo giá đầu tiên sử dụng dấu ngoặc đơn.
  2. " Bắt đầu báo giá thứ hai, sử dụng dấu ngoặc kép.
  3. ' Nhân vật được trích dẫn.
  4. " Kết thúc báo giá thứ hai, sử dụng dấu ngoặc kép.
  5. ' Bắt đầu báo giá thứ ba, sử dụng dấu ngoặc đơn.

Nếu bạn không đặt bất kỳ khoảng trắng nào giữa (1) và (2) hoặc giữa (4) và (5), shell sẽ diễn giải chuỗi đó thành một từ dài.


5
alias splitpath='echo $PATH | awk -F : '"'"'{print "PATH is set to"} {for (i=1;i<=NF;i++) {print "["i"]",$i}}'"'"Nó hoạt động khi có cả dấu ngoặc đơn và dấu ngoặc kép trong chuỗi bí danh!
Uphill_ What '1

17
Giải thích của tôi: bash ngầm kết hợp các biểu thức chuỗi được trích dẫn khác nhau.
Benjamin Atkin

2
làm việc cho tôi, ví dụ về các trích dẫn đơn thoát kép:alias serve_this_dir='ruby -rrack -e "include Rack;Handler::Thin.run Builder.new{run Directory.new'"'"''"'"'}"'
JAMESTONEco

2
Chắc chắn không phải là giải pháp dễ đọc nhất. Nó sử dụng quá mức các trích dẫn duy nhất mà chúng không thực sự cần thiết.
oberlies

26
Tôi cho rằng '\''nó dễ đọc hơn nhiều trong hầu hết các bối cảnh hơn '"'"'. Trong thực tế, trước đây hầu như luôn luôn phân biệt rõ ràng trong một chuỗi trích dẫn đơn, và do đó chỉ là vấn đề ánh xạ nó theo nghĩa ngữ nghĩa với "đó là một trích dẫn thoát", giống như trong một \"chuỗi trích dẫn kép. Trong khi đó, cái sau pha trộn vào một dòng dấu ngoặc kép và cần kiểm tra cẩn thận trong nhiều trường hợp để phân biệt chính xác.
mtraceur

263

Tôi luôn chỉ thay thế mỗi trích dẫn được nhúng bằng chuỗi: '\''(đó là: trích dẫn trích dẫn trích dẫn trích dẫn) đóng chuỗi, nối thêm một trích dẫn đã thoát và mở lại chuỗi.


Tôi thường sử dụng chức năng "trích dẫn" trong các tập lệnh Perl để thực hiện điều này cho tôi. Các bước sẽ là:

s/'/'\\''/g    # Handle each embedded quote
$_ = qq['$_']; # Surround result with single quotes.

Điều này khá nhiều chăm sóc tất cả các trường hợp.

Cuộc sống trở nên thú vị hơn khi bạn giới thiệu evalvào shell-scripts của mình. Bạn về cơ bản phải trích dẫn lại mọi thứ một lần nữa!

Ví dụ: tạo tập lệnh Perl có tên là trích dẫn có chứa các câu lệnh trên:

#!/usr/bin/perl -pl
s/'/'\\''/g;
$_ = qq['$_'];

sau đó sử dụng nó để tạo ra một chuỗi trích dẫn chính xác:

$ quotify
urxvt -fg '#111111' -bg '#111111'

kết quả:

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

mà sau đó có thể được sao chép / dán vào lệnh bí danh:

alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

(Nếu bạn cần chèn lệnh vào một eval, hãy chạy lại trích dẫn:

 $ quotify
 alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

kết quả:

'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''

có thể được sao chép / dán vào một eval:

eval 'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''

1
Nhưng đây không phải là perl. Và như Steve B đã chỉ ra ở trên, với tài liệu tham khảo của mình về "hướng dẫn tham khảo gnu", bạn không thể thoát khỏi dấu ngoặc kép trong cùng một loại trích dẫn. Và trên thực tế, không cần phải thoát chúng trong các trích dẫn thay thế, ví dụ: "'" là một chuỗi trích dẫn đơn hợp lệ và' "'là một chuỗi trích dẫn kép hợp lệ mà không yêu cầu bất kỳ thoát nào.
nicerobot

8
@nicerobot: Tôi đã thêm một ví dụ cho thấy: 1) Tôi không cố thoát các trích dẫn trong cùng một loại trích dẫn, 2) cũng như trong các trích dẫn thay thế và 3) Perl được sử dụng để tự động hóa quá trình tạo hợp lệ bash chuỗi containg trích dẫn nhúng
Adrian Pronk

18
Đoạn đầu tiên của chính nó là câu trả lời tôi đang tìm kiếm.
Dave Causey

9
Đây là những gì bash nào là tốt, gõ set -xecho "here's a string"và bạn sẽ thấy rằng thực thi bash echo 'here'\''s a string'. ( set +xđể trả lại hành vi bình thường)
arekolek

196

Vì cú pháp Bash 2.04$'string' (thay vì chỉ 'string'; cảnh báo: không nhầm lẫn với $('string')) là một cơ chế trích dẫn khác cho phép các chuỗi thoát giống như ANSI C và thực hiện mở rộng sang phiên bản trích dẫn đơn.

Ví dụ đơn giản:

  $> echo $'aa\'bb'
  aa'bb

  $> alias myvar=$'aa\'bb'
  $> alias myvar
  alias myvar='aa'\''bb'

Trong trường hợp của bạn:

$> alias rxvt=$'urxvt -fg \'#111111\' -bg \'#111111\''
$> alias rxvt
alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

Trình tự thoát phổ biến hoạt động như mong đợi:

\'     single quote
\"     double quote
\\     backslash
\n     new line
\t     horizontal tab
\r     carriage return

Dưới đây là bản sao + dán tài liệu liên quan từ man bash(phiên bản 4.4):

Các từ có dạng $ 'chuỗi' được xử lý đặc biệt. Từ này mở rộng thành chuỗi, với các ký tự thoát dấu gạch chéo ngược được thay thế theo quy định của tiêu chuẩn ANSI C. Các chuỗi thoát dấu gạch chéo ngược, nếu có, được giải mã như sau:

    \a     alert (bell)
    \b     backspace
    \e
    \E     an escape character
    \f     form feed
    \n     new line
    \r     carriage return
    \t     horizontal tab
    \v     vertical tab
    \\     backslash
    \'     single quote
    \"     double quote
    \?     question mark
    \nnn   the eight-bit character whose value is the octal 
           value nnn (one to three digits)
    \xHH   the eight-bit character whose value is the hexadecimal
           value HH (one or two hex digits)
    \uHHHH the Unicode (ISO/IEC 10646) character whose value is 
           the hexadecimal value HHHH (one to four hex digits)
    \UHHHHHHHH the Unicode (ISO/IEC 10646) character whose value 
               is the hexadecimal value HHHHHHHH (one to eight 
               hex digits)
    \cx    a control-x character

Kết quả mở rộng được trích dẫn một lần, như thể ký hiệu đô la không có mặt.


Xem Báo giá và thoát: ANSI C như các chuỗi trên wiki bash-hackers.org để biết thêm chi tiết. Cũng lưu ý rằng tệp "Bash Change" ( tổng quan ở đây ) đề cập rất nhiều đến các thay đổi và sửa lỗi liên quan đến $'string'cơ chế trích dẫn.

Theo unix.stackexchange.com Làm thế nào để sử dụng một ký tự đặc biệt như một nhân vật bình thường? nó nên hoạt động (với một số biến thể) trong bash, zsh, mksh, ksh93 và FreeBSD và busybox sh.


có thể được sử dụng nhưng chuỗi trích dẫn đơn ở đây không phải là một chuỗi được trích dẫn thực sự, nội dung trên chuỗi này có thể được giải thích bởi shell: echo $'foo\'b!ar'=> !ar': event not found
regilero

2
Trên máy của tôi > echo $BASH_VERSION 4.2.47(1)-release > echo $'foo\'b!ar' foo'b!ar
mj41

1
Vâng, đó là lý do cho "có thể", tôi đã có nó trên một chiếc mũ đỏ 6.4, chắc chắn là một phiên bản bash cũ hơn.
regilero

Bash ChangeLog chứa rất nhiều sửa lỗi liên quan đến $'vì vậy có lẽ cách dễ nhất là tự mình thử nó trên các hệ thống cũ.
mj41

lưu ý: e. Bash no longer inhibits C-style escape processing ($'...') while performing pattern substitution word expansions.Lấy từ tiswww.case.edu/php/chet/bash/CHANGES . Vẫn hoạt động trong 4.3.42 nhưng không hoạt động trong 4.3.48.
stiller_leser

49

Tôi không thấy mục trên blog của anh ấy (link xin vui lòng?) Nhưng theo hướng dẫn tham khảo gnu :

Đóng các ký tự trong dấu ngoặc đơn ('' ') sẽ giữ nguyên giá trị bằng chữ của mỗi ký tự trong dấu ngoặc kép. Một trích dẫn có thể không xảy ra giữa các trích dẫn đơn, ngay cả khi trước dấu gạch chéo ngược.

vì vậy bash sẽ không hiểu:

alias x='y \'z '

tuy nhiên, bạn có thể làm điều này nếu bạn bao quanh với dấu ngoặc kép:

alias x="echo \'y "
> x
> 'y


Nội dung kèm theo dấu ngoặc kép đang được đánh giá vì vậy chỉ kèm theo dấu ngoặc đơn trong dấu ngoặc kép theo đề xuất của liori dường như là giải pháp phù hợp.
Piotr Dobrogost

3
Đây là câu trả lời thực tế cho câu hỏi. Mặc dù câu trả lời được chấp nhận có thể cung cấp giải pháp, nhưng về mặt kỹ thuật, nó trả lời một câu hỏi không được hỏi.
Matthew G

3
Matthew, câu hỏi là về việc thoát khỏi dấu ngoặc đơn trong dấu ngoặc đơn. Câu trả lời này yêu cầu người dùng thay đổi hành vi của họ và nếu bạn gặp trở ngại trong việc sử dụng dấu ngoặc kép (như tiêu đề câu hỏi gợi ý), câu trả lời này sẽ không hữu ích. Mặc dù nó khá hữu ích (Mặc dù rõ ràng), và như vậy xứng đáng là một upvote, nhưng câu trả lời được chấp nhận giải quyết vấn đề chính xác mà Op đã hỏi về.
Fernando Cordeiro

Không cần trích dẫn một trích dẫn trong một chuỗi trích dẫn kép.
Matthew D. Scholefield

32

Tôi có thể xác nhận rằng việc sử dụng '\''cho một trích dẫn bên trong một chuỗi trích dẫn duy nhất có tác dụng với Bash và nó có thể được giải thích theo cách tương tự như đối số "dán" từ trước đó trong chuỗi. Giả sử chúng ta có một chuỗi trích dẫn: 'A '\''B'\'' C'(tất cả các trích dẫn ở đây là các trích dẫn đơn). Nếu nó được truyền cho echo, nó sẽ in như sau : A 'B' C. Trong mỗi '\''trích dẫn đầu tiên đóng chuỗi trích dẫn đơn hiện tại, \'đoạn trích sau sẽ trích dẫn một trích dẫn duy nhất cho chuỗi trước đó ( \'là cách chỉ định một trích dẫn mà không bắt đầu chuỗi trích dẫn) và trích dẫn cuối cùng mở ra một chuỗi trích dẫn đơn.


2
Điều này là sai lệch, cú pháp '\' 'này không đi "bên trong" một chuỗi trích dẫn. Trong tuyên bố này 'A' \ '' B '\' 'C', bạn đang nối 5 chuỗi thoát và chuỗi trích dẫn đơn
teknopaul

1
@teknopaul Việc chuyển nhượng alias something='A '\''B'\'' C'không dẫn đến somethingmột chuỗi đơn, vì vậy ngay cả khi bên phải của nhiệm vụ không phải là một chuỗi về mặt kỹ thuật, tôi không nghĩ đó là vấn đề quan trọng.
Teemu Leisti

Mặc dù điều này hoạt động trong ví dụ của bạn, nhưng về mặt kỹ thuật , nó không cung cấp giải pháp cho cách chèn một trích dẫn bên trong một chuỗi trích dẫn. Bạn đã giải thích nó, nhưng đúng vậy 'A ' + ' + 'B' + ' + ' C'. Nói cách khác, một giải pháp để chèn các ký tự trích dẫn đơn bên trong một chuỗi trích dẫn sẽ cho phép tôi tự tạo một chuỗi như vậy và in nó. Tuy nhiên giải pháp này sẽ không hoạt động trong trường hợp này. STR='\''; echo $STR. Theo thiết kế, BASH không thực sự cho phép điều này.
krb686

@mikhail_b, vâng, '\''hoạt động cho bash. Bạn có thể chỉ ra phần nào của gnu.org/software/bash/manual/bashref.html chỉ định hành vi như vậy không?
Jingguo Yao

20

Cả hai phiên bản đều hoạt động, bằng cách ghép nối bằng cách sử dụng ký tự trích dẫn đơn thoát (\ ') hoặc ghép nối bằng cách đặt ký tự trích dẫn đơn trong dấu ngoặc kép ("'").

Tác giả của câu hỏi đã không nhận thấy rằng có thêm một trích dẫn (') vào cuối nỗ lực trốn thoát cuối cùng của mình:

alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''
           │         │┊┊|       │┊┊│     │┊┊│       │┊┊│
           └─STRING──┘┊┊└─STRIN─┘┊┊└─STR─┘┊┊└─STRIN─┘┊┊│
                      ┊┊         ┊┊       ┊┊         ┊┊│
                      ┊┊         ┊┊       ┊┊         ┊┊│
                      └┴─────────┴┴───┰───┴┴─────────┴┘│
                          All escaped single quotes    │
                                                       │
                                                       ?

Như bạn có thể thấy trong phần đẹp của nghệ thuật ASCII / Unicode trước đây, trích dẫn đơn thoát cuối cùng (\ ') được theo sau bởi một trích dẫn không cần thiết ('). Sử dụng công cụ đánh dấu cú pháp như trình bày trong Notepad ++ có thể rất hữu ích.

Điều tương tự cũng đúng với một ví dụ khác như ví dụ sau:

alias rc='sed '"'"':a;N;$!ba;s/\n/, /g'"'"
alias rc='sed '\'':a;N;$!ba;s/\n/, /g'\'

Hai trường hợp bí danh đẹp này thể hiện một cách rất phức tạp và khó hiểu làm thế nào một tập tin có thể được xếp hàng. Đó là, từ một tệp có rất nhiều dòng bạn chỉ nhận được một dòng có dấu phẩy và khoảng trắng giữa nội dung của các dòng trước đó. Để có ý nghĩa của nhận xét trước đó, sau đây là một ví dụ:

$ cat Little_Commas.TXT
201737194
201802699
201835214

$ rc Little_Commas.TXT
201737194, 201802699, 201835214

3
Được nâng cấp cho minh họa Bảng ASCII :)
php-dev

16

Ví dụ đơn giản về thoát dấu ngoặc kép trong shell:

$ echo 'abc'\''abc'
abc'abc
$ echo "abc"\""abc"
abc"abc

Nó được thực hiện bằng cách hoàn thành đã mở một ( '), đặt thoát một ( \'), sau đó mở một ( ') khác. Cú pháp này hoạt động cho tất cả các lệnh. Đó là cách tiếp cận rất giống với câu trả lời đầu tiên.


15

Tôi không đề cập cụ thể đến vấn đề trích dẫn bởi vì, đôi khi, thật hợp lý khi xem xét một phương pháp thay thế.

rxvt() { urxvt -fg "#${1:-000000}" -bg "#${2:-FFFFFF}"; }

mà sau đó bạn có thể gọi là:

rxvt 123456 654321

ý tưởng là bây giờ bạn có thể đặt bí danh này mà không cần quan tâm đến dấu ngoặc kép:

alias rxvt='rxvt 123456 654321'

hoặc, nếu bạn cần bao gồm #trong tất cả các cuộc gọi vì một số lý do:

rxvt() { urxvt -fg "${1:-#000000}" -bg "${2:-#FFFFFF}"; }

mà sau đó bạn có thể gọi là:

rxvt '#123456' '#654321'

sau đó, tất nhiên, một bí danh là:

alias rxvt="rxvt '#123456' '#654321'"

(Rất tiếc, tôi đoán tôi đã loại địa chỉ trích dẫn :)


1
Tôi đã cố gắng đặt một cái gì đó trong dấu ngoặc đơn trong dấu ngoặc kép, lần lượt, trong dấu ngoặc đơn. Rất tiếc. Cảm ơn bạn đã trả lời của bạn về "thử một cách tiếp cận khác". Điều đó làm nên sự khác biệt.
Clinton Blackmore

1
Tôi trễ 5 năm, nhưng bạn không bỏ lỡ một trích dẫn nào trong bí danh cuối cùng của bạn?
Julien

1
@Julien Tôi không thấy vấn đề gì ;-)
nicerobot

11

Vì người ta không thể đặt dấu ngoặc đơn trong các chuỗi được trích dẫn, nên tùy chọn đơn giản và dễ đọc nhất là sử dụng chuỗi HEREDOC

command=$(cat <<'COMMAND'
urxvt -fg '#111111' -bg '#111111'
COMMAND
)

alias rxvt=$command

Trong đoạn mã trên, HEREDOC được gửi đến catlệnh và đầu ra của nó được gán cho một biến thông qua ký hiệu thay thế lệnh$(..)

Đặt một trích dẫn xung quanh HEREDOC là cần thiết vì nó nằm trong $()


Tôi ước tôi đã cuộn xuống đến nay trước đây - tôi đã phát minh lại cách tiếp cận này và đến đây để đăng nó! Điều này là sạch sẽ hơn và dễ đọc hơn tất cả các phương pháp thoát hiểm khác. Không phải nó sẽ không hoạt động trên một số shell không bash, chẳng hạn như dashshell mặc định trong các tập lệnh khởi động Ubuntu và các nơi khác.
Korny

Cảm ơn bạn! rằng những gì tôi tìm kiếm, cách để xác định một lệnh là thông qua heredoc và truyền lệnh tự động thoát cho ssh. Mèo BTW << THÔNG TIN không có dấu ngoặc kép cho phép nội suy các biến động bên trong lệnh và hoạt động tốt cho phương pháp này.
Igor Tverdovskiy

10

Tôi chỉ sử dụng mã shell .. ví dụ \x27hoặc \\x22như áp dụng. Không có rắc rối, bao giờ thực sự.


Bạn có thể cho thấy một ví dụ về điều này trong hoạt động? Đối với tôi, nó chỉ in một chữ x27(trên Centos 6.6)
Will Sheppard

6

Hầu hết các câu trả lời đánh vào trường hợp cụ thể mà bạn đang hỏi về. Có một cách tiếp cận chung rằng một người bạn và tôi đã phát triển cho phép tùy tiện trích dẫn trong trường hợp bạn cần phải quote bash lệnh thông qua nhiều lớp mở rộng vỏ, ví dụ, thông qua ssh, su -c, bash -c, vv Có một lõi nguyên thủy bạn cần, đây trong bash bản địa:

quote_args() {
    local sq="'"
    local dq='"'
    local space=""
    local arg
    for arg; do
        echo -n "$space'${arg//$sq/$sq$dq$sq$dq$sq}'"
        space=" "
    done
}

Điều này thực hiện chính xác những gì nó nói: tất nhiên, nó trích dẫn từng đối số riêng lẻ (sau khi mở rộng bash):

$ quote_args foo bar
'foo' 'bar'
$ quote_args arg1 'arg2 arg2a' arg3
'arg1' 'arg2 arg2a' 'arg3'
$ quote_args dq'"'
'dq"'
$ quote_args dq'"' sq"'"
'dq"' 'sq'"'"''
$ quote_args "*"
'*'
$ quote_args /b*
'/bin' '/boot'

Nó thực hiện điều hiển nhiên cho một lớp mở rộng:

$ bash -c "$(quote_args echo a'"'b"'"c arg2)"
a"b'c arg2

(Lưu ý rằng các trích dẫn kép xung quanh $(quote_args ...)là cần thiết để biến kết quả thành một đối số duy nhất bash -c.) Và nó có thể được sử dụng chung hơn để trích dẫn chính xác thông qua nhiều lớp mở rộng:

$ bash -c "$(quote_args bash -c "$(quote_args echo a'"'b"'"c arg2)")"
a"b'c arg2

Ví dụ trên:

  1. shell-trích dẫn từng đối số vào bên trong quote_argsriêng lẻ và sau đó kết hợp đầu ra kết quả thành một đối số duy nhất với dấu ngoặc kép bên trong.
  2. vỏ có dấu ngoặc kép bash, -cvà kết quả đã một lần được trích dẫn từ bước 1, và sau đó kết hợp kết quả vào một đối số duy nhất với các dấu ngoặc kép bên ngoài.
  3. gửi mớ hỗn độn đó như đối số ra bên ngoài bash -c.

Đó là ý tưởng ngắn gọn. Bạn có thể làm một số thứ khá phức tạp với điều này, nhưng bạn phải cẩn thận về thứ tự đánh giá và về các chuỗi con được trích dẫn. Chẳng hạn, những điều sau đây làm những điều sai (đối với một số định nghĩa của "sai"):

$ (cd /tmp; bash -c "$(quote_args cd /; pwd 1>&2)")
/tmp
$ (cd /tmp; bash -c "$(quote_args cd /; [ -e *sbin ] && echo success 1>&2 || echo failure 1>&2)")
failure

Trong ví dụ đầu tiên, bash ngay lập tức mở rộng quote_args cd /; pwd 1>&2thành hai lệnh riêng biệt quote_args cd /pwd 1>&2do đó, CWD vẫn còn /tmpkhi pwdlệnh được thực thi. Ví dụ thứ hai minh họa một vấn đề tương tự đối với Globing. Thật vậy, cùng một vấn đề cơ bản xảy ra với tất cả các mở rộng bash. Vấn đề ở đây là sự thay thế lệnh không phải là một lệnh gọi hàm: nó thực sự đánh giá một tập lệnh bash và sử dụng đầu ra của nó như là một phần của tập lệnh bash khác.

Nếu bạn cố gắng đơn giản thoát khỏi các toán tử shell, bạn sẽ thất bại vì chuỗi kết quả được chuyển đến bash -cchỉ là một chuỗi các chuỗi được trích dẫn riêng lẻ mà sau đó không được hiểu là các toán tử, điều này rất dễ thấy nếu bạn lặp lại chuỗi đó sẽ đã được thông qua để bash:

$ (cd /tmp; echo "$(quote_args cd /\; pwd 1\>\&2)")
'cd' '/;' 'pwd' '1>&2'
$ (cd /tmp; echo "$(quote_args cd /\; \[ -e \*sbin \] \&\& echo success 1\>\&2 \|\| echo failure 1\>\&2)")
'cd' '/;' '[' '-e' '*sbin' ']' '&&' 'echo' 'success' '1>&2' '||' 'echo' 'failure' '1>&2'

Vấn đề ở đây là bạn đang trích dẫn quá mức. Những gì bạn cần là cho các toán tử không được trích dẫn làm đầu vào cho phần kèm theo bash -c, có nghĩa là chúng cần nằm ngoài $(quote_args ...)lệnh thay thế.

Do đó, những gì bạn cần làm theo nghĩa chung nhất là trích dẫn shell từng từ của lệnh không có ý định mở rộng tại thời điểm thay thế lệnh riêng biệt và không áp dụng bất kỳ trích dẫn bổ sung nào cho các toán tử shell:

$ (cd /tmp; echo "$(quote_args cd /); $(quote_args pwd) 1>&2")
'cd' '/'; 'pwd' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")
/
$ (cd /tmp; echo "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
'cd' '/'; [ -e *'sbin' ] && 'echo' 'success' 1>&2 || 'echo' 'failure' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
success

Khi bạn đã thực hiện điều này, toàn bộ chuỗi là trò chơi công bằng để trích dẫn thêm cho các mức đánh giá tùy ý:

$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")"
/
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")"
/
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")")"
/
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")"
success
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *sbin ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")"
success
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")")"
success

Vân vân.

Những ví dụ này có vẻ quá căng thẳng khi các từ như success, sbinpwdkhông cần được trích dẫn bằng vỏ, nhưng điểm quan trọng cần nhớ khi viết một tập lệnh lấy đầu vào tùy ý là bạn muốn trích dẫn mọi thứ mà bạn không chắc chắn lắm ' Không cần trích dẫn, bởi vì bạn không bao giờ biết khi nào người dùng sẽ ném vào Robert'; rm -rf /.

Để hiểu rõ hơn những gì đang diễn ra dưới vỏ bọc, bạn có thể chơi xung quanh với hai chức năng trợ giúp nhỏ:

debug_args() {
    for (( I=1; $I <= $#; I++ )); do
        echo -n "$I:<${!I}> " 1>&2
    done
    echo 1>&2
}

debug_args_and_run() {
    debug_args "$@"
    "$@"
}

sẽ liệt kê từng đối số cho một lệnh trước khi thực hiện nó:

$ debug_args_and_run echo a'"'b"'"c arg2
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)"
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'bash'"'"' '"'"'-c'"'"' '"'"''"'"'"'"'"'"'"'"'debug_args_and_run'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'echo'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'a"b'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'c'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'arg2'"'"'"'"'"'"'"'"''"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

Chào Kyle. Giải pháp của bạn đã làm việc rất tốt cho một trường hợp tôi có, khi tôi cần vượt qua một nhóm đối số dưới dạng một đối số duy nhất : vagrant ssh -c {single-arg} guest. Các {single-arg}nhu cầu được coi là một đối số duy nhất bởi vì vagrant lấy đối số tiếp theo sau nó làm tên khách. Thứ tự không thể thay đổi. Nhưng tôi cần phải truyền một lệnh và các đối số của nó bên trong {single-arg}. Vì vậy, tôi đã sử dụng của bạn quote_args()để trích dẫn lệnh và đối số của nó, và đặt dấu ngoặc kép xung quanh kết quả, và nó hoạt động như một bùa mê : vagrant ssh -c "'command' 'arg 1 with blanks' 'arg 2'" guest. Cảm ơn!!!
Andreas Maier

6

IMHO câu trả lời thực sự là bạn không thể thoát các dấu ngoặc đơn trong các chuỗi trích dẫn đơn.

Điều đó là không thể.

Nếu chúng tôi cho rằng chúng tôi đang sử dụng bash.

Từ hướng dẫn bash ...

Enclosing characters in single quotes preserves the literal value of each
character within the quotes.  A single quote may not occur
between single quotes, even when preceded by a backslash.

Bạn cần sử dụng một trong các cơ chế thoát chuỗi khác "hoặc \

Không có gì kỳ diệu về aliasđiều đó đòi hỏi nó sử dụng dấu ngoặc đơn.

Cả hai công việc sau đây trong bash.

alias rxvt="urxvt -fg '#111111' -bg '#111111'"
alias rxvt=urxvt\ -fg\ \'#111111\'\ -bg\ \'#111111\'

Cái sau đang sử dụng \ để thoát khỏi ký tự khoảng trắng.

Cũng không có gì kỳ diệu về # 111111 yêu cầu trích dẫn đơn.

Các tùy chọn sau đạt được kết quả tương tự hai tùy chọn còn lại, trong đó bí danh rxvt hoạt động như mong đợi.

alias rxvt='urxvt -fg "#111111" -bg "#111111"'
alias rxvt="urxvt -fg \"#111111\" -bg \"#111111\""

Bạn cũng có thể thoát trực tiếp # rắc rối

alias rxvt="urxvt -fg \#111111 -bg \#111111"

"câu trả lời thực sự là bạn không thể thoát các dấu ngoặc đơn trong các chuỗi trích dẫn đơn." Điều đó đúng về mặt kỹ thuật. Nhưng bạn có thể có một giải pháp bắt đầu bằng một trích dẫn, kết thúc bằng một trích dẫn duy nhất và chỉ chứa các trích dẫn duy nhất ở giữa. stackoverflow.com/a/49063038
wvducky

Không phải bằng cách trốn thoát, chỉ bằng cách ghép.
teknopaul

4

Trong ví dụ đã cho, chỉ cần sử dụng dấu ngoặc kép thay vì dấu ngoặc đơn làm cơ chế thoát bên ngoài:

alias rxvt="urxvt -fg '#111111' -bg '#111111'"

Cách tiếp cận này phù hợp với nhiều trường hợp bạn chỉ muốn truyền một chuỗi cố định vào một lệnh: Chỉ cần kiểm tra cách shell sẽ diễn giải chuỗi trích dẫn kép thông qua một echoký tự và thoát ký tự bằng dấu gạch chéo ngược nếu cần.

Trong ví dụ này, bạn sẽ thấy rằng dấu ngoặc kép là đủ để bảo vệ chuỗi:

$ echo "urxvt -fg '#111111' -bg '#111111'"
urxvt -fg '#111111' -bg '#111111'

4

Rõ ràng, sẽ dễ dàng hơn khi bao quanh với dấu ngoặc kép, nhưng thách thức ở đâu? Đây là câu trả lời chỉ sử dụng dấu ngoặc đơn. Tôi đang sử dụng một biến thay vì aliasvậy việc in bằng chứng sẽ dễ dàng hơn, nhưng nó cũng giống như sử dụng alias.

$ rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'
$ echo $rxvt
urxvt -fg '#111111' -bg '#111111'

Giải trình

Điều quan trọng là bạn có thể đóng trích dẫn duy nhất và mở lại bao nhiêu lần tùy ý. Ví dụ foo='a''b'giống như foo='ab'. Vì vậy, bạn có thể đóng trích dẫn đơn, ném vào một trích dẫn theo nghĩa đen \', sau đó mở lại trích dẫn duy nhất tiếp theo.

Sơ đồ sự cố

Sơ đồ này làm cho nó rõ ràng bằng cách sử dụng dấu ngoặc để hiển thị nơi các dấu ngoặc đơn được mở và đóng. Trích dẫn không được "lồng" như dấu ngoặc đơn có thể được. Bạn cũng có thể chú ý đến việc tô sáng màu, được áp dụng chính xác. Các chuỗi trích dẫn là maroon, trong khi đó \'là màu đen.

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'    # original
[^^^^^^^^^^] ^[^^^^^^^] ^[^^^^^] ^[^^^^^^^] ^    # show open/close quotes
 urxvt -fg   ' #111111  '  -bg   ' #111111  '    # literal characters remaining

(Đây thực chất là câu trả lời giống như của Adrian, nhưng tôi cảm thấy điều này giải thích nó tốt hơn. Ngoài ra, câu trả lời của anh ấy có 2 trích dẫn đơn thừa ở cuối.)


+1 để sử dụng '\''phương pháp tôi khuyên dùng '"'"'phương pháp thường khó đọc hơn đối với con người.
mtraceur

3

Đây là một chi tiết về Câu trả lời đúng nhất được tham chiếu ở trên:

Đôi khi tôi sẽ tải xuống bằng rsync qua ssh và phải thoát tên tệp có 'trong đó TWICE! (OMG!) Một lần cho bash và một lần cho ssh. Nguyên tắc tương tự của dấu phân cách trích dẫn xen kẽ là tại nơi làm việc.

Ví dụ: giả sử chúng tôi muốn nhận: Câu chuyện LA của Louis Theroux ...

  1. Trước tiên, bạn gửi Louis Theroux trong dấu ngoặc đơn cho bash và dấu ngoặc kép cho ssh: '"Louis Theroux"'
  2. Sau đó, bạn sử dụng dấu ngoặc đơn để thoát dấu ngoặc kép '"'
  3. Việc sử dụng dấu ngoặc kép để thoát khỏi dấu nháy đơn "'"
  4. Sau đó lặp lại # 2, sử dụng dấu ngoặc đơn để thoát dấu ngoặc kép '"'
  5. Sau đó gửi kèm LA Stories trong các trích dẫn đơn cho bash và dấu ngoặc kép cho ssh: '"LA Stories"'

Và kìa! Bạn kết thúc với điều này:

rsync -ave ssh '"Louis Theroux"''"'"'"'"''"s LA Stories"'

đó là rất nhiều công việc cho một chút '- nhưng bạn đi


3
shell_escape () {
    printf '%s' "'${1//\'/\'\\\'\'}'"
}

Giải thích thực hiện:

  • dấu ngoặc kép để chúng ta có thể dễ dàng xuất ra các dấu ngoặc đơn và sử dụng ${...}cú pháp

  • bash's tìm kiếm và thay thế trông giống như: ${varname//search/replacement}

  • chúng tôi đang thay thế 'bằng'\''

  • '\''mã hóa một 'như vậy:

    1. ' kết thúc trích dẫn

    2. \'mã hóa một '(dấu gạch chéo ngược là cần thiết bởi vì chúng tôi không nằm trong dấu ngoặc kép)

    3. ' bắt đầu trích dẫn lại

    4. bash tự động nối các chuỗi không có khoảng trắng giữa

  • \trước \'bởi vì đó là quy tắc thoát cho ${...//.../...}.

string="That's "'#@$*&^`(@#'
echo "original: $string"
echo "encoded:  $(shell_escape "$string")"
echo "expanded: $(bash -c "echo $(shell_escape "$string")")"

PS Luôn mã hóa thành các chuỗi được trích dẫn bởi vì chúng đơn giản hơn các chuỗi được trích dẫn kép.


2

Một cách khác để khắc phục vấn đề của quá nhiều lớp trích dẫn lồng nhau:

Bạn đang cố nhồi nhét quá nhiều vào một không gian quá nhỏ, vì vậy hãy sử dụng hàm bash.

Vấn đề là bạn đang cố gắng có quá nhiều cấp độ lồng nhau, và công nghệ bí danh cơ bản không đủ mạnh để đáp ứng. Sử dụng hàm bash như thế này để làm cho nó sao cho dấu ngoặc kép, dấu ngoặc kép trở lại và được truyền trong các tham số đều được xử lý bình thường như chúng ta mong đợi:

lets_do_some_stuff() {
    tmp=$1                       #keep a passed in parameter.
    run_your_program $@          #use all your passed parameters.
    echo -e '\n-------------'    #use your single quotes.
    echo `date`                  #use your back ticks.
    echo -e "\n-------------"    #use your double quotes.
}
alias foobarbaz=lets_do_some_stuff

Sau đó, bạn có thể sử dụng các biến $ 1 và $ 2 của mình và các dấu ngoặc đơn, dấu ngoặc kép và dấu kiểm ngược mà không phải lo lắng về hàm bí danh phá hỏng tính toàn vẹn của chúng.

Chương trình này in:

el@defiant ~/code $ foobarbaz alien Dyson ring detected @grid 10385
alien Dyson ring detected @grid 10385
-------------
Mon Oct 26 20:30:14 EDT 2015
-------------

2

Nếu bạn đã cài đặt GNU Parallel, bạn có thể sử dụng trích dẫn nội bộ của nó:

$ parallel --shellquote
L's 12" record
<Ctrl-D>
'L'"'"'s 12" record'
$ echo 'L'"'"'s 12" record'
L's 12" record

Từ phiên bản 20190222 bạn thậm chí có thể --shellquotenhiều lần:

$ parallel --shellquote --shellquote --shellquote
L's 12" record
<Ctrl-D>
'"'"'"'"'"'"'L'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'s 12" record'"'"'"'"'"'"'
$ eval eval echo '"'"'"'"'"'"'L'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'s 12" record'"'"'"'"'"'"'
L's 12" record

Nó sẽ trích dẫn chuỗi trong tất cả các shell được hỗ trợ (không chỉ bash).


1

Chức năng này:

quote () 
{ 
    local quoted=${1//\'/\'\\\'\'};
    printf "'%s'" "$quoted"
}

cho phép trích dẫn 'bên trong '. Sử dụng như thế này:

$ quote "urxvt -fg '#111111' -bg '#111111'"
'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

Nếu dòng trích dẫn trở nên phức tạp hơn, như dấu ngoặc kép trộn với dấu ngoặc đơn, có thể sẽ trở nên khá khó khăn để có được chuỗi trích dẫn bên trong một biến. Khi những trường hợp như vậy xuất hiện, hãy viết dòng chính xác mà bạn cần trích dẫn bên trong một tập lệnh (tương tự như thế này).

#!/bin/bash

quote ()
{
    local quoted=${1//\'/\'\\\'\'};
    printf "'%s'" "$quoted"
}

while read line; do
    quote "$line"
done <<-\_lines_to_quote_
urxvt -fg '#111111' -bg '#111111'
Louis Theroux's LA Stories
'single quote phrase' "double quote phrase"
_lines_to_quote_

Sẽ xuất:

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''
'Louis Theroux'\''s LA Stories'
''\''single quote phrase'\'' "double quote phrase"'

Tất cả các chuỗi trích dẫn chính xác trong dấu ngoặc đơn.


1

Nếu bạn đang tạo chuỗi shell trong Python 2 hoặc Python 3, những điều sau đây có thể giúp trích dẫn các đối số:

#!/usr/bin/env python

from __future__ import print_function

try:  # py3
    from shlex import quote as shlex_quote
except ImportError:  # py2
    from pipes import quote as shlex_quote

s = """foo ain't "bad" so there!"""

print(s)
print(" ".join([shlex_quote(t) for t in s.split()]))

Điều này sẽ xuất ra:

foo ain't "bad" so there!
foo 'ain'"'"'t' '"bad"' so 'there!'

1

Dưới đây là hai xu của tôi - trong trường hợp nếu một người muốn có thể shdi chuyển được , không chỉ là bashcụ thể (giải pháp không quá hiệu quả, mặc dù, vì nó bắt đầu một chương trình bên ngoài - sed):

  • đặt cái này vào quote.sh(hoặc chỉ quote) ở đâu đó trên PATH:
# này hoạt động với đầu vào tiêu chuẩn (stdin)
Trích dẫn() {
  tiếng vang -n "'";
  sed 's / \ ([' "'"'] ['"'" '] * \) /' "'"' "\ 1" '"'" '/ g';
  tiếng vang -n "'"
}

trường hợp "$ 1" trong
 -) Trích dẫn ;;
 *) echo "cách sử dụng: cat ... | quote - # đầu vào dấu ngoặc đơn cho shell Bourne" 2> & 1 ;;
esac

Một ví dụ:

$ echo -n "G'day, bạn đời!" | ./quote.sh -
'G' "'"' ngày, bạn đời! '

Và, tất nhiên, điều đó chuyển đổi trở lại:

$ echo 'G' "'"' ngày, bạn đời! '
G'day, bạn đời!

Giải thích: về cơ bản chúng ta phải kèm theo đầu vào bằng dấu ngoặc kép ', và sau đó cũng thay thế bất kỳ trích dẫn nào trong vi quái vật này: '"'"'(kết thúc trích dẫn mở đầu bằng một cặp ', thoát khỏi trích dẫn được tìm thấy bằng cách gói nó bằng dấu ngoặc kép - "'"và sau đó cuối cùng đưa ra một trích dẫn duy nhất mở đầu ', hoặc trong ký hiệu giả ' + "'" + ' == '"'"':)

Một cách tiêu chuẩn để làm điều đó là sử dụng sedlệnh thay thế sau:

s/\(['][']*\)/'"\1"'/g 

Tuy nhiên, một vấn đề nhỏ là để sử dụng cái vỏ đó, người ta cần phải thoát khỏi tất cả các ký tự trích dẫn đơn này trong chính biểu thức sed - điều gì dẫn đến một cái gì đó như

sed 's/\(['"'"']['"'"']*\)/'"'"'"\1"'"'"'/g' 

(và một cách tốt để xây dựng kết quả này là cung cấp biểu thức gốc s/\(['][']*\)/'"\1"'/gcho các kịch bản của Kyle Rose hoặc George V. Reilly).

Cuối cùng, thật hợp lý khi hy vọng đầu vào đến từ stdin- vì việc chuyển nó qua các đối số dòng lệnh có thể đã gặp quá nhiều rắc rối.

(Ồ, và có thể chúng tôi muốn thêm một thông báo trợ giúp nhỏ để tập lệnh không bị treo khi ai đó chạy nó như ./quote.sh --helpđang tự hỏi nó làm gì.)


0

Đây là một giải pháp khác. Hàm này sẽ lấy một đối số duy nhất và trích dẫn nó một cách thích hợp bằng cách sử dụng ký tự trích dẫn đơn, giống như câu trả lời được bình chọn ở trên giải thích:

single_quote() {
  local quoted="'"
  local i=0
  while [ $i -lt ${#1} ]; do
    local ch="${1:i:1}"
    if [[ "$ch" != "'" ]]; then
      quoted="$quoted$ch"
    else
      local single_quotes="'"
      local j=1
      while [ $j -lt ${#1} ] && [[ "${1:i+j:1}" == "'" ]]; do
        single_quotes="$single_quotes'"
        ((j++))
      done
      quoted="$quoted'\"$single_quotes\"'"
      ((i+=j-1))
    fi
    ((i++))
  done
  echo "$quoted'"
}

Vì vậy, bạn có thể sử dụng nó theo cách này:

single_quote "1 2 '3'"
'1 2 '"'"'3'"'"''

x="this text is quoted: 'hello'"
eval "echo $(single_quote "$x")"
this text is quoted: 'hello'
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.