一重引用符で囲まれた文字列内で一重引用符をエスケープする方法


1018

たとえば、次のaliasようなBashがあるとします。

alias rxvt='urxvt'

これは正常に動作します。

しかしながら:

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

は機能せず、どちらも機能しません。

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

では、引用符をエスケープしたら、どうやって文字列内の開始引用符と終了引用符を一致させるのでしょうか。

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

あなたがそのようにそれらを連結することを許可されている場合、それは同じ文字列を表すでしょうが、それは珍しいようです。


16
エイリアスに一重引用符を使用する必要がないことを理解していますか?二重引用符ははるかに簡単です。
teknopaul


3
ネストされた二重引用符は回避"\""できるため、可能な限り@lioriの回答よりも優先して使用する必要があります。
アラン

7
二重引用符は* nixの単一引用符(BashやPerlなどの関連ツールを含む)とはまったく異なる動作をするため、単一引用符に問題がある場合は常に二重引用符を代用することは適切な解決策ではありません。二重引用符は$ ...変数を実行前に置換することを指定し、一方、単一引用符は$ ...を文字どおりに処理することを指定します。
チャックコラーズ

考えている場合は、二重引用符を使用しましたが、それでも機能しません。もう一度スクリプトを入手してください。
Samy Bencherif

回答:


1456

最も外側のレイヤーで単一引用符を本当に使用したい場合は、両方の種類の引用符を接着できることに注意してください。例:

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

がどのよう'"'"'に解釈されるかの説明'

  1. ' 単一引用符を使用する最初の引用を終了します。
  2. " 二重引用符を使用して、2番目の引用を開始します。
  3. ' 引用文字。
  4. " 二重引用符を使用して、2番目の引用を終了します。
  5. ' 単一引用符を使用して、3番目の引用を開始します。

(1)と(2)の間、または(4)と(5)の間に空白を入れないと、シェルはその文字列を1つの長い単語として解釈します。


5
alias splitpath='echo $PATH | awk -F : '"'"'{print "PATH is set to"} {for (i=1;i<=NF;i++) {print "["i"]",$i}}'"'"エイリアス文字列に一重引用符と二重引用符の両方がある場合に機能します。
上り坂

17
私の解釈:bashは、異なって引用された文字列式を暗黙的に連結します。
ベンジャミンアトキン2013

2
二重にエスケープされた一重引用符の例:alias serve_this_dir='ruby -rrack -e "include Rack;Handler::Thin.run Builder.new{run Directory.new'"'"''"'"'}"'
JAMESSTONEco

2
確かに最も読みやすいソリューションではありません。シングルクォーテーションは、実際には必要ないところで使いすぎています。
2013年

26
私はそれ'\''がほとんどの文脈でよりもはるかに読みやすいと主張してい'"'"'ます。実際、前者はほとんどの場合、単一引用符で囲まれた文字列内で明確に区別されるため\"、二重引用符で囲まれた文字列と同様に、意味的に「エスケープされた引用符です」という意味にマッピングするだけです。一方、後者は1行の引用ティックに溶け込み、多くの場合、適切に区別するために注意深い検査が必要です。
mtraceur 2016

263

私は常に、埋め込まれた各単一引用符を次のシーケンスに置き換えるだけです'\''(つまり、引用符のバックスラッシュ引用符)。これは文字列を閉じ、エスケープされた単一引用符を追加して、文字列を再び開きます。


私はよく、Perlスクリプトで「クォート」関数を作成してこれを行っています。手順は次のとおりです。

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

これはほとんどすべてのケースを処理します。

evalシェルスクリプトに導入すると、人生がより楽しくなります。本質的には、すべてを再度引用する必要があります!

たとえば、上記のステートメントを含むquotifyというPerlスクリプトを作成します。

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

それを使用して、正しく引用された文字列を生成します。

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

結果:

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

次に、aliasコマンドにコピー/貼り付けできます。

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

(コマンドをevalに挿入する必要がある場合は、再度quotifyを実行します:

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

結果:

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

これは、evalにコピー/貼り付けできます。

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

1
しかし、これはperlではありません。また、スティーブBが上で指摘したように、「gnuリファレンスマニュアル」を参照しているため、同じタイプの引用符内でbashの引用符をエスケープすることはできません。そして、実際には、代替引用符でそれらをエスケープする必要はありません、例えば「'」有効な単一引用符の文字列であり、「"」任意のエスケープ必要とせず、有効な二重引用符の文字列です。
nicerobot

8
@nicerobot:以下を示す例を追加しました:1)同じタイプの引用符内で引用符をエスケープしない、2)代替引用符でエスケープしない、3)有効なコードを生成するプロセスを自動化するためにPerlが使用されているbash文字列には引用符が埋め込まれています
Adrian Pronk

18
最初の段落自体が、私が探していた答えです。
Dave Causey

9
これは、bashが同様に行うことであり、入力するset -xecho "here's a string"、bashが実行されることがわかりますecho 'here'\''s a string'。(set +x通常の動作に戻る)
arekolek

196

Bash 2.04構文$'string'(単なる'string';警告ではなく:と混同しないでください$('string'))は、ANSI Cのようなエスケープシーケンスを許可し、単一引用バージョンに拡張できる別の引用メカニズムです。

簡単な例:

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

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

あなたの場合:

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

一般的なエスケープシーケンスは期待どおりに機能します。

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

以下は、man bash(バージョン4.4)からコピーして貼り付けた関連ドキュメントです。

$ 'string'形式の単語は特別に扱われます。単語は文字列に展開され、バックスラッシュでエスケープされた文字はANSI C規格の指定に従って置き換えられます。バックスラッシュエスケープシーケンスが存在する場合は、次のようにデコードされます。

    \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

展開された結果は、ドル記号が存在しないかのように、単一引用符で囲まれています。


詳細については、bash-hackers.org wikiの「引用とエスケープ:ANSI Cのような文字列」を参照してください。また、「Bashの変更」ファイル(概要はこちら)には、$'string'引用メカニズムに関連する変更とバグ修正の多くが記載されています。

unix.stackexchange.comによれば、特殊文字を通常の文字として使用する方法は?bash、zsh、mksh、ksh93、FreeBSD、busybox shで(いくつかのバリエーションはありますが)動作するはずです。


使用できますが、ここで一重引用符で囲まれた文字列は実際の一重引用符ではありません。この文字列の内容はシェルによって解釈される可能性があります:echo $'foo\'b!ar'=> !ar': event not found
regilero

2
私のマシンでは > echo $BASH_VERSION 4.2.47(1)-release > echo $'foo\'b!ar' foo'b!ar
mj41

1
はい、それが「かもしれない」理由です。私はそれをRed Hat 6.4、確かに古いbashバージョンで使用しました。
regilero 14年

Bash ChangeLogには、関連する多くのバグ修正が含まれている$'ため、おそらく最も簡単な方法は、古いシステムで自分で試すことです。
mj41

注意:tiswww.case.edu/php/chet/bash/CHANGESe. Bash no longer inhibits C-style escape processing ($'...') while performing pattern substitution word expansions.から取得。4.3.42では機能しますが、4.3.48では機能しません。
stiller_leser

49

彼のブログ(リンクpls?)にエントリは表示されませんが、gnuリファレンスマニュアルによると:

文字を単一引用符( '' ')で囲むと、引用符内の各文字のリテラル値が保持されます。バックスラッシュが前に付いていても、一重引用符は一重引用符の間に出現しない場合があります。

だからbashは理解しません:

alias x='y \'z '

ただし、二重引用符で囲むと、これを行うことができます。

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


二重引用符で囲まれたコンテンツが評価されているため、lioriによって提案されているように、単一引用符のみを二重引用符で囲むことが適切な解決策のようです。
Piotr Dobrogost

3
これが質問に対する実際の答えです。受け入れられた答えは解決策を提供するかもしれませんが、それは尋ねられなかった質問に技術的に答えることです。
マシューG

3
マシュー、問題は単一引用符内の単一引用符のエスケープについてでした。この回答は、ユーザーに動作を変更するように求めます。二重引用符を使用することに問題がある場合(質問のタイトルが示すとおり)、この回答は役に立ちません。それはかなり明白ですが(明らかではありますが)、そのため賛成に値しますが、受け入れられた回答は、Opが尋ねた正確な問題に対処します。
フェルナンドコルデイロ2015

二重引用符文字列で単一引用符を引用する必要はありません。
Matthew D. Scholefield

32

'\''一重引用符で囲まれた文字列内で一重引用符を使用するとBashで機能することを確認できます。これは、スレッドの初期の「接着」引数と同じ方法で説明できます。引用符付きの文字列があるとします'A '\''B'\'' C'(ここでのすべての引用符は単一引用符です)。echoに渡されると、次のメッセージが出力されますA 'B' C'\''最初の引用符はそれぞれ、現在の単一引用符付き文字列を閉じます。以下は\'、単一引用符を前の文字列に接着し(\'引用符付き文字列を開始せずに単一引用符を指定する方法です)、最後の引用符が別の単一引用符付き文字列を開きます。


2
これは誤解を招くものであり、この構文「\」は単一引用符で囲まれた文字列の「内側」には入りません。このステートメント 'A' \ '' B '\' 'C'では、5つの\エスケープ文字列と単一引用符文字列を連結しています
teknopaul

1
@teknopaul割り当てalias something='A '\''B'\'' C'something単一の文字列になるので、割り当ての右側は厳密には単一の文字列ではありませんが、それほど重要ではないと思います。
Teemu Leisti

これはあなたの例では機能しますが、一重引用符で囲まれた文字列に一重引用符を挿入する方法のソリューションを技術的に提供するものではありません。すでに説明しましたが、そうです。言い換えれば、一重引用符で囲まれた文字列内に一重引用符文字を挿入するソリューションでは、そのような文字列を自分で作成して印刷できるはずです。ただし、この場合、このソリューションは機能しません。 。設計上、BASHはこれを実際に許可していません。'A ' + ' + 'B' + ' + ' C'STR='\''; echo $STR
krb686 2016

@mikhail_b、はい、'\''bash で動作します。gnu.org/software/bash/manual/bashref.htmlのどのセクションがそのような動作を指定しているかを指摘できますか?
Jingguo Yao

20

両方のバージョンは、エスケープされた一重引用符文字(\ ')を使用して連結するか、一重引用符文字を二重引用符( "'")で囲んで連結します。

質問の著者は、最後のエスケープ試行の最後に余分な一重引用符( ')があることに気づきませんでした:

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

前のASCII / Unicodeアートのすばらしい部分でわかるように、最後にエスケープされた一重引用符(\ ')の後に、不要な一重引用符(')が続きます。Notepad ++にあるような構文ハイライターを使用すると、非常に役立ちます。

次のような別の例でも同じことが言えます。

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

エイリアスのこれら2つの美しいインスタンスは、ファイルを並べる方法を非常に複雑で難読化された方法で示しています。つまり、多くの行を含むファイルからは、前の行の内容の間にコンマとスペースを含む1行のみが取得されます。前のコメントを理解するために、以下に例を示します。

$ cat Little_Commas.TXT
201737194
201802699
201835214

$ rc Little_Commas.TXT
201737194, 201802699, 201835214

3
ASCIIテーブルのイラストにうんざりしました:)
php-dev

16

シェルで引用符をエスケープする簡単な例:

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

これは、既に開いているもの(')を終了し、エスケープされたもの(\')を配置してから、別のもの(')を開くことによって行われます。この構文は、すべてのコマンドで機能します。これは、1番目の回答と非常によく似ています。


15

別のアプローチを検討するのが妥当な場合もあるので、引用の問題については特に取り上げていません。

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

次のように呼び出すことができます。

rxvt 123456 654321

引用符を気にすることなく、これに別名を付けることができるという考えです。

alias rxvt='rxvt 123456 654321'

または、#何らかの理由ですべての呼び出しにを含める必要がある場合:

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

次のように呼び出すことができます。

rxvt '#123456' '#654321'

もちろん、エイリアスは次のとおりです。

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

(おっと、私は引用に対処したようなものだと思います:)


1
私は二重引用符で囲まれた単一引用符の中に何かを入れようとしていました。うわぁ。「別のアプローチをお試しください」とお答えいただきありがとうございます。それが違いを生みました。
クリントンブラックモア

1
私は5年遅れていますが、最後のエイリアスの単一引用符が抜けていませんか?
Julien

1
@Julien問題はありません;-)
nicerobot 2017年

11

単一引用符で囲まれた文字列内に単一引用符を置くことはできないため、最も簡単で最も読みやすいオプションは、HEREDOC文字列を使用することです。

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

alias rxvt=$command

上記のコードでは、HEREDOCがcatコマンドに送信され、その出力はコマンド置換表記を介して変数に割り当てられます。$(..)

HEREDOCは単一引用符内にあるため、引用符で囲む必要があります。 $()


ここまで下にスクロールしておけばよかった-このアプローチを再発明し、投稿するためにここに来た!これは、他のすべてのエスケープアプローチよりもはるかにクリーンで読みやすくなっています。dashUbuntu upstartスクリプトなどのデフォルトのシェルなど、一部の非bashシェルでは機能しません。
Korny 2017

ありがとうございました!私が探していたのは、ヒアドキュメントを使用してコマンドをそのまま定義し、自動エスケープコマンドをsshに渡す方法です。ところで、引用符のないcat << COMMANDを使用すると、コマンド内の変数を補間でき、このアプローチでも同様に機能します。
イゴールトヴェルドフスキー

10

私はちょうど..例えばシェルコードを使用する\x27か、\\x22該当します。本当に面倒はありません。


動作中の例を見せていただけますか?私にとっては、リテラルを印刷するだけですx27(Centos 6.6)
ウィルシェパード2018年

6

これらの回答のほとんどは、あなたが尋ねている特定のケースに当てはまります。任意のは、あなたが例えば、SSH経由で、シェル拡張の複数の層を通じてbashのコマンドを引用する必要がある場合に引用することができます友人と私が開発していることを一般的なアプローチがありsu -cbash -c一方のコアは、ここで、あなたが必要とする原始的な存在である、などがネイティブbashの場合:

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
}

これはまさにそれが言うことを行います:それは各引数を個別にシェルクォートします(もちろん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'

それは、拡張の1つの層に対して明白なことを行います。

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

(周りの二重引用符$(quote_args ...)は、結果をへの単一の引数にするために必要bash -cです。)そして、より一般的に使用して、複数の展開層を適切に引用することができます。

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

上記の例:

  1. shellは、各引数を内部でquote_args個別に引用し、結果の出力を単一の引数に内部の二重引用符で結合します。
  2. シェル重引用符bash-cおよびステップ1から既に一度、引用された結果は、その後、外側の二重引用符と単一の引数に結果を兼ね備えています。
  3. その混乱を引数としてアウターに送信しbash -cます。

それが一言で言えばアイデアです。これでかなり複雑なことを行うことができますが、評価の順序と引用される部分文字列に注意する必要があります。たとえば、以下は間違った処理を行います(「間違った」の定義の場合)。

$ (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

最初の例では、bashはすぐquote_args cd /; pwd 1>&2に2つの別々のコマンド、quote_args cd /およびに展開されるpwd 1>&2ため、CWDはコマンドが実行された/tmpときのままpwdです。2番目の例は、グロビングの同様の問題を示しています。実際、すべてのbash展開で同じ基本的な問題が発生します。ここでの問題は、コマンド置換が関数呼び出しではないことです。これは、文字通り1つのbashスクリプトを評価し、その出力を別のbashスクリプトの一部として使用することです。

単にシェル演算子をエスケープしようとすると、渡される結果の文字列はbash -c、演算子として解釈されない個別に引用された一連の文字列なので、失敗します。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'

ここでの問題は、あなたが引用しすぎているということです。必要なのは、演算子を囲んbash -cでいるへの入力として引用符で囲まないようにすることです。つまり、演算子は$(quote_args ...)コマンド置換の外側にある必要があります。

したがって、最も一般的な意味で行う必要があるのは、コマンド置換時に個別に展開されることを意図していないコマンドの各単語をシェル引用し、シェル演算子に余分な引用を適用しないことです。

$ (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

これを実行すると、文字列全体が任意のレベルの評価にさらに引用するための公正なゲームになります。

$ 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

これらの例は、のような言葉ことを考えると神経が高ぶった見えるかもしれませんsuccesssbinとは、pwdシェル引用されている必要はありませんが、任意の入力を取ってスクリプトを記述する際に覚えておくべき重要なポイントは、あなたが絶対にわからない、すべて引用したいということです」doesnのをtはユーザがスローされたときに、あなたが知っていることはありませんので、引用必要ですRobert'; rm -rf /

カバーの下で何が起こっているかをよりよく理解するために、2つの小さなヘルパー関数をいじることができます。

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

debug_args_and_run() {
    debug_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 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

こんにちはカイル。あなたの解決策は、私が単一の引数として引数のグループを渡す必要があるときに私が経験した場合にうまくいきましたvagrant ssh -c {single-arg} guest{single-arg}浮浪者がゲスト名としてそれの後に次のargがかかるため、単一の引数として扱われる必要があります。順序は変更できません。しかし、コマンドとその引数を内で渡す必要がありました{single-arg}。したがって、私はあなたを使用してquote_args()コマンドとその引数を引用し、結果を二重引用符で囲みましたvagrant ssh -c "'command' 'arg 1 with blanks' 'arg 2'" guest。これは魅力のように機能しました:。ありがとう!!!
Andreas Maier

6

私見本当の答えは、単一引用符で囲まれた文字列内の単一引用符はエスケープできないということです。

それは不可能だ。

私たちはbashを使用していると仮定します。

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.

他の文字列エスケープメカニズムの1つを使用する必要があります "または\

alias一重引用符を使用することを要求する魔法はありません。

以下の両方がbashで動作します。

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

後者は、スペース文字をエスケープするために\を使用しています。

また、単一引用符を必要とする#111111についての魔法はありません。

次のオプションは、rxvtエイリアスが期待どおりに機能するという点で、他の2つのオプションと同じ結果を実現します。

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

面倒な#を直接回避することもできます

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

「本当の答えは、単一引用符で囲まれた文字列内の単一引用符はエスケープできないということです。」それは技術的に真実です。ただし、単一引用符で始まり、単一引用符で終わり、中央に単一引用符しか含まれないソリューションを使用できます。stackoverflow.com/a/49063038
wisbucky 2018年

エスケープではなく、連結によってのみ。
teknopaul 2018

4

与えられた例では、外側のエスケープメカニズムとして、単一引用符の代わりに単純に二重引用符を使用しました。

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

このアプローチは、固定文字列をコマンドに渡したいだけの多くの場合に適しています。シェルが二重引用符で囲まれた文字列を echo必要に応じてバックスラッシュで文字をエスケープします。

この例では、文字列を保護するには二重引用符で十分であることがわかります。

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

4

明らかに、二重引用符で囲むだけの方が簡単ですが、その中での課題はどこですか?ここでは、単一引用符のみを使用した答えです。代わりに変数を使用しているaliasので、校正のために印刷するのは簡単ですが、を使用するのと同じaliasです。

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

説明

重要なのは、単一引用符を閉じて、何度でもそれを再び開くことができるということです。たとえばはとfoo='a''b'同じfoo='ab'です。したがって、単一引用符を閉じて、文字通りの単一引用符を投げることができます\'、次の単一引用符を再び開くます。

内訳図

この図では、ブラケットを使用して一重引用符が開いている場所と閉じている場所を示しています。引用符は、括弧のように「ネスト」されていません。正しく適用される色の強調表示にも注意を払うことができます。引用符で囲まれた文字列は栗色ですが、\'黒色です。

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

(これは本質的にエイドリアンの答えと同じですが、私はこれがそれをよりよく説明していると思います。また、彼の答えは最後に2つの余分な単一引用符を持っています。)


+1は、人間が読みにくい'\''方法よりも推奨する方法を使用するための'"'"'ものです。
mtraceur

3

上記の真実の答えの詳細は次のとおりです。

sshでrsyncを使用してダウンロードし、 'を含むファイル名を2回エスケープする必要がある場合があります。(OMG!)1回はbash、もう1回はsshです。引用符区切り文字を交互にする同じ原理がここでも機能しています。

たとえば、取得したいとしましょう:Louis TherouxのLAストーリー...

  1. まず、Louis Therouxをbashの単一引用符とsshの二重引用符で囲みます: '"Louis Theroux"'
  2. 次に、一重引用符を使用して二重引用符「」をエスケープします
  3. 二重引用符を使用してアポストロフィ「 '」をエスケープします
  4. 次に、#2を繰り返します。単一引用符を使用して、二重引用符「 "」をエスケープします。
  5. 次に、LA Storiesをbashの単一引用符とsshの二重引用符で囲みます: '"LA Stories"'

そして見よ!あなたはこれで終わります:

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

これは1つの小さな作業としては大変な作業です」


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

実装の説明:

  • 二重引用符を使用すると、簡単な折り返しの単一引用符を出力して${...}構文を使用できます。

  • bashの検索と置換は次のようになります。 ${varname//search/replacement}

  • 私たちはと置き換え'ています'\''

  • '\'''そのように単一をエンコードします:

    1. ' 単一引用符を終了します

    2. \'をエンコードします'(引用符内にないため、バックスラッシュが必要です)

    3. ' もう一度シングルクォートを開始します

    4. bashは、間に空白を入れずに文字列を自動的に連結します

  • あります\すべての前\'それがためにエスケープルールだから${...//.../...}

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

PS二重引用符で囲まれた文字列よりも単純であるため、常に単一引用符で囲まれた文字列にエンコードします。


2

ネストされた引用のレイヤーが多すぎるという問題を修正する別の方法:

小さすぎるスペースに詰め込みすぎているので、bash関数を使用してください。

問題は、ネストのレベルが多すぎることです。また、基本的なエイリアス技術は、対応するのに十分なほど強力ではありません。次のようなbash関数を使用して、単一引用符、二重引用符、ティック、および渡されたパラメーターがすべて期待どおりに正常に処理されるようにします。

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

次に、エイリアス関数が整合性を壊すことを心配することなく、$ 1と$ 2の変数と一重引用符、二重引用符、およびバックティックを使用できます。

このプログラムは出力します:

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

GNU Parallelがインストールされている場合は、その内部引用を使用できます。

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

バージョン20190222以降では、--shellquote複数回実行することもできます。

$ 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

(だけでなくbash)サポートされているすべてのシェルで文字列を引用します。


1

この機能:

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

'内部の引用を許可し'ます。このように使用してください:

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

二重引用符と単一引用符が混在するように、引用する行がより複雑になる場合、変数内で文字列を引用するのは非常に難しくなる可能性があります。このようなケースが表示されたら、引用符で囲む必要がある正確な行をスクリプト内に記述します(これと同様)。

#!/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_

出力されます:

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

一重引用符内のすべての正しく引用された文字列。


1

Python 2またはPython 3でシェル文字列を生成している場合は、次のように引数を引用するとよいでしょう。

#!/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()]))

これは出力します:

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

1

これが私の2セントです。sh特定のものだけでなく、可搬型にしたい場合bash(ただし、外部プログラムを起動するため、このソリューションはあまり効率的ではありませんsed):

  • これをquote.sh(または単にquote)あなたのどこかに入れてくださいPATH
#これは標準入力(stdin)で動作します
見積もり() {
  echo -n "'";
  sed 's / \([' "'"'] ['"'" '] * \)/' "'"' "\ 1" '"'" '/ g';
  echo -n "'"
}

ケース「$ 1」
 -) 見積もり ;;
 *)echo "usage:cat ... | quote-#Bourne shellの単一引用符入力" 2>&1 ;;
esac

例:

$ echo -n "G'day、mate!" | ./quote.sh-
「G」「」「日、仲間!」

そしてもちろん、それは元に戻ります:

$ echo 'G' "'"' day、mate! '
こんにちは、仲間!

説明:基本的に、入力を引用符'で囲み、次に単一の引用符をこのマイクロモンスターで置き換える必要があります:('"'"'開始引用符をペアリング'で終了し、見つかった単一引用符を二重引用符で囲んでエスケープします- "'"、およびそして、最終的には新しいオープニング単一引用符を発行'、または擬似表記を:' + "'" + ' == '"'"'

これを行う1つの標準的な方法sedは、次の置換コマンドで使用することです。

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

ただし、小さな問題の1つは、それをシェルで使用するために、sed式自体でこれらすべての単一引用符文字をエスケープする必要があることです。

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

(そして、この結果を作成するための1つの良い方法は、元の表現s/\(['][']*\)/'"\1"'/gをカイルローズまたはジョージV.レイリーのスクリプトにフィードすることです)。

最後に、入力からの入力を期待することはある程度理にかなっていますstdin-コマンドライン引数を介して入力を渡すことはすでに非常に面倒である可能性があるためです。

(ああ、また、誰かがスクリプトを実行しただけでスクリプトがハングしないように、何をしているの./quote.sh --helpだろうと思って、小さなヘルプメッセージを追加したいかもしれません。)


0

これが別の解決策です。上記の投票された回答が説明するように、この関数は単一の引数を取り、単一引用符文字を使用して適切にそれを引用します:

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

したがって、次のように使用できます。

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

x="this text is quoted: 'hello'"
eval "echo $(single_quote "$x")"
this text is quoted: 'hello'
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.