So vermeiden Sie einfache Anführungszeichen in Zeichenfolgen in einfachen Anführungszeichen

1016

Angenommen, Sie haben einen Bash aliaswie:

alias rxvt='urxvt'

das funktioniert gut.

Jedoch:

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

wird nicht funktionieren und auch nicht:

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

Wie können Sie also das Öffnen und Schließen von Anführungszeichen in einer Zeichenfolge abgleichen, wenn Sie den Anführungszeichen entkommen sind?

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

scheint unansehnlich, obwohl es die gleiche Zeichenfolge darstellen würde, wenn Sie sie so verketten dürfen.

Nachteile
quelle
16
Ist Ihnen klar, dass Sie für Alias ​​keine einfachen Anführungszeichen verwenden müssen? Doppelte Anführungszeichen sind viel einfacher.
Teknopaul
3
Verschachtelte doppelte Anführungszeichen können nicht verwendet werden. Daher "\""sollten diese nach Möglichkeit der Antwort von @ liori vorgezogen werden.
Alan
7
Doppelte Anführungszeichen verhalten sich ganz anders als einfache Anführungszeichen in * nix (einschließlich Bash und verwandten Tools wie Perl). Daher ist das Ersetzen von doppelten Anführungszeichen bei Problemen mit einfachen Anführungszeichen KEINE gute Lösung. Doppelte Anführungszeichen geben an, dass $ ... -Variablen vor der Ausführung ersetzt werden sollen, während einfache Anführungszeichen angeben, dass $ ... wörtlich behandelt werden sollen.
Chuck Kollars
Wenn Sie denken, ich habe doppelte Anführungszeichen verwendet, aber es funktioniert immer noch nicht , geben Sie Ihr Skript erneut ein.
Samy Bencherif

Antworten:

1454

Wenn Sie wirklich einfache Anführungszeichen in der äußersten Ebene verwenden möchten, denken Sie daran, dass Sie beide Arten von Anführungszeichen kleben können. Beispiel:

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

Erklärung, wie dies '"'"'als gerecht interpretiert wird ':

  1. ' Beenden Sie das erste Anführungszeichen mit einfachen Anführungszeichen.
  2. " Starten Sie das zweite Zitat mit doppelten Anführungszeichen.
  3. ' Zitierter Charakter.
  4. " Beenden Sie das zweite Anführungszeichen mit doppelten Anführungszeichen.
  5. ' Beginnen Sie das dritte Zitat mit einfachen Anführungszeichen.

Wenn Sie keine Leerzeichen zwischen (1) und (2) oder zwischen (4) und (5) platzieren, interpretiert die Shell diese Zeichenfolge als ein einziges langes Wort.

liori
quelle
5
alias splitpath='echo $PATH | awk -F : '"'"'{print "PATH is set to"} {for (i=1;i<=NF;i++) {print "["i"]",$i}}'"'"Es funktioniert, wenn die Alias-Zeichenfolge sowohl einfache als auch doppelte Anführungszeichen enthält!
Uphill_ What '1
17
Meine Interpretation: bash verkettet implizit unterschiedlich zitierte Zeichenfolgenausdrücke.
Benjamin Atkin
2
arbeitete für mich, Beispiel für doppelte Anführungszeichen:alias serve_this_dir='ruby -rrack -e "include Rack;Handler::Thin.run Builder.new{run Directory.new'"'"''"'"'}"'
JAMESSTONEco
2
Mit Sicherheit nicht die am besten lesbare Lösung. Es verwendet einfache Anführungszeichen, wenn sie nicht wirklich benötigt werden.
Oberlies
26
Ich behaupte, dass dies '\''in den meisten Kontexten weitaus besser lesbar ist als '"'"'. Tatsächlich unterscheidet sich Ersteres fast immer klar innerhalb einer Zeichenfolge in einfachen Anführungszeichen und ist daher nur eine Frage der semantischen Zuordnung zu der Bedeutung "Es ist ein entkommenes Anführungszeichen", wie dies bei \"Zeichenfolgen in doppelten Anführungszeichen der Fall ist . Letzteres fügt sich in eine Zeile mit Anführungszeichen ein und muss in vielen Fällen sorgfältig geprüft werden, um eine ordnungsgemäße Unterscheidung zu ermöglichen.
mtraceur
263

Ich ersetze immer nur jedes eingebettete einfache Anführungszeichen durch die folgende Sequenz '\''(dh Anführungszeichen-Backslash-Anführungszeichen), die die Zeichenfolge schließt, ein maskiertes einfaches Anführungszeichen anfügt und die Zeichenfolge erneut öffnet.


Ich rufe in meinen Perl-Skripten häufig eine "Anführungszeichen" -Funktion auf, um dies für mich zu tun. Die Schritte wären:

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

Dies kümmert sich so ziemlich um alle Fälle.

Das Leben macht mehr Spaß, wenn Sie es evalin Ihre Shell-Skripte einführen . Sie müssen im Wesentlichen alles neu zitieren!

Erstellen Sie beispielsweise ein Perl-Skript namens quotify, das die obigen Anweisungen enthält:

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

Verwenden Sie es dann, um eine korrekt in Anführungszeichen gesetzte Zeichenfolge zu generieren:

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

Ergebnis:

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

die dann in den Alias-Befehl kopiert / eingefügt werden kann:

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

(Wenn Sie den Befehl in eine Auswertung einfügen müssen, führen Sie das Anführungszeichen erneut aus:

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

Ergebnis:

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

die kopiert / in eine Bewertung eingefügt werden kann:

eval 'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''
Adrian Pronk
quelle
1
Aber das ist kein Perl. Und wie Steve B oben mit seinem Verweis auf das "Gnu-Referenzhandbuch" hervorhob, können Sie sich Zitaten in Bash innerhalb derselben Art von Zitat nicht entziehen. Und in der Tat müssen Sie sie nicht in alternativen Anführungszeichen maskieren, z. B. "'" ist eine gültige Zeichenfolge in einfachen Anführungszeichen und' "'ist eine gültige Zeichenfolge in doppelten Anführungszeichen, ohne dass ein
Escapezeichen
8
@nicerobot: Ich habe ein Beispiel hinzugefügt, das Folgendes zeigt: 1) Ich versuche nicht, Anführungszeichen innerhalb desselben Anführungszeichentyps zu umgehen, 2) noch in alternativen Anführungszeichen, und 3) Perl wird verwendet, um den Prozess der Generierung eines gültigen Angebots zu automatisieren Bash String mit eingebetteten Anführungszeichen
Adrian Pronk
18
Der erste Absatz an sich ist die Antwort, nach der ich gesucht habe.
Dave Causey
9
Dies ist , was bash funktioniert auch, geben set -xund , echo "here's a string"und Sie werden feststellen , dass bash ausführt sehen echo 'here'\''s a string'. ( set +xum normales Verhalten zurückzugeben)
Arekolek
195

Da die Bash 2.04- Syntax $'string'(statt nur 'string'; Warnung: nicht verwechseln mit $('string')) ein weiterer Anführungsmechanismus ist, der ANSI C-ähnliche Escape-Sequenzen ermöglicht und eine Erweiterung auf eine einfache Anführungszeichenversion ermöglicht.

Einfaches Beispiel:

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

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

In Ihrem Fall:

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

Häufige Escape-Sequenzen funktionieren wie erwartet:

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

Unten finden Sie eine Kopie + eingefügte Dokumentation aus man bash(Version 4.4):

Wörter der Form $ 'string' werden speziell behandelt. Das Wort wird zu Zeichenfolge erweitert, wobei Zeichen mit Backslash-Escapezeichen gemäß ANSI C-Standard ersetzt werden. Backslash-Escape-Sequenzen werden, falls vorhanden, wie folgt dekodiert:

    \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

Das erweiterte Ergebnis wird in einfachen Anführungszeichen gesetzt, als ob das Dollarzeichen nicht vorhanden gewesen wäre.


Siehe Zitate und Flucht: ANSI C wie Strings auf bash-hackers.org um weitere Informationen zu Wiki. Beachten Sie auch, dass in der Datei "Bash Changes" ( Übersicht hier ) häufig Änderungen und Fehlerbehebungen im Zusammenhang mit dem $'string'Angebotsmechanismus erwähnt werden.

Laut unix.stackexchange.com Wie verwende ich ein Sonderzeichen als normales? Es sollte (mit einigen Variationen) in Bash, Zsh, Mksh, Ksh93 und FreeBSD und Busybox Sh funktionieren.

mj41
quelle
könnte verwendet werden, aber die einfache Anführungszeichenfolge hier ist keine echte einfache Anführungszeichenfolge. Der Inhalt dieser Zeichenfolge kann von der Shell interpretiert werden: echo $'foo\'b!ar'=> !ar': event not found
regilero
2
Auf meiner Maschine > echo $BASH_VERSION 4.2.47(1)-release > echo $'foo\'b!ar' foo'b!ar
mj41
1
Ja, das ist der Grund für "Mai", ich hatte es auf einem Red Hat 6.4, sicherlich einer älteren Bash-Version.
Regilero
Bash ChangeLog enthält viele Fehlerkorrekturen im Zusammenhang mit dem $'wahrscheinlich einfachsten Weg, es selbst auf älteren Systemen zu versuchen.
MJ41
Beachten Sie : e. Bash no longer inhibits C-style escape processing ($'...') while performing pattern substitution word expansions.Entnommen aus www.case.edu/php/chet/bash/CHANGES . Funktioniert immer noch in 4.3.42, aber nicht in 4.3.48.
stiller_leser
49

Ich sehe den Eintrag nicht in seinem Blog (Link pls?), Aber laut Gnu-Referenzhandbuch :

Durch das Einschließen von Zeichen in einfache Anführungszeichen ('' ') wird der Literalwert jedes Zeichens in den Anführungszeichen beibehalten. Ein einfaches Anführungszeichen darf nicht zwischen einfachen Anführungszeichen stehen, selbst wenn ein Backslash vorangestellt ist.

Bash wird also nicht verstehen:

alias x='y \'z '

Sie können dies jedoch tun, wenn Sie doppelte Anführungszeichen verwenden:

alias x="echo \'y "
> x
> 'y
Steve B.
quelle
Mit doppelten Anführungszeichen versehene Inhalte werden derzeit ausgewertet, sodass es die richtige Lösung zu sein scheint, nur einfache Anführungszeichen in doppelte Anführungszeichen zu setzen, wie von liori vorgeschlagen.
Piotr Dobrogost
3
Dies ist die eigentliche Antwort auf die Frage. Während die akzeptierte Antwort eine Lösung bietet, beantwortet sie technisch eine Frage, die nicht gestellt wurde.
Matthew G
3
Matthew, die Frage war, wie man einfachen Anführungszeichen in einfachen Anführungszeichen entgeht. Diese Antwort fordert den Benutzer auf, sein Verhalten zu ändern. Wenn Sie die Verwendung von doppelten Anführungszeichen behindern (wie der Fragentitel andeutet), hilft diese Antwort nicht weiter. Es ist zwar ziemlich nützlich (wenn auch offensichtlich) und verdient als solches eine positive Bewertung, aber die akzeptierte Antwort spricht das genaue Problem an, nach dem Op gefragt hat.
Fernando Cordeiro
Sie müssen kein einfaches Anführungszeichen in einer doppelten Anführungszeichenfolge angeben.
Matthew D. Scholefield
32

Ich kann bestätigen, dass die Verwendung '\''eines einfachen Anführungszeichens in einer Zeichenfolge in einfachen Anführungszeichen in Bash funktioniert, und dies kann auf die gleiche Weise erklärt werden wie das Argument "Kleben" von früher im Thread. Angenommen, wir haben eine Zeichenfolge in 'A '\''B'\'' C'Anführungszeichen : (Alle Anführungszeichen hier sind einfache Anführungszeichen). Wenn es an echo übergeben wird, wird Folgendes ausgegeben : A 'B' C. In jedem '\''der ersten Anführungszeichen wird die aktuelle Zeichenfolge in einfachen Anführungszeichen geschlossen, im Folgenden wird \'ein einfaches Anführungszeichen an die vorherige Zeichenfolge geklebt ( \'eine Möglichkeit, ein einfaches Anführungszeichen anzugeben, ohne eine Zeichenfolge in Anführungszeichen zu setzen), und im letzten Anführungszeichen wird eine weitere Zeichenfolge in einfachen Anführungszeichen geöffnet.

Mikhail bei YugaByte
quelle
2
Dies ist irreführend. Diese Syntax '\' 'geht nicht in eine einzelne Zeichenfolge in Anführungszeichen. In dieser Anweisung 'A' \ '' B '\' 'C' verketten Sie 5 \
Escape-
1
@teknopaul Die Zuweisung alias something='A '\''B'\'' C'führt dazu, dass somethinges sich um eine einzelne Zeichenfolge handelt. Auch wenn die rechte Seite der Zuweisung technisch gesehen keine einzelne Zeichenfolge ist, halte ich es nicht für wichtig.
Teemu Leisti
Während dies in Ihrem Beispiel funktioniert, bietet es technisch keine Lösung für das Einfügen eines einfachen Anführungszeichens in eine Zeichenfolge in einfachen Anführungszeichen. Du hast es bereits erklärt, aber ja, es tut es 'A ' + ' + 'B' + ' + ' C'. Mit anderen Worten, eine Lösung zum Einfügen von einfachen Anführungszeichen in eine Zeichenfolge in einfachen Anführungszeichen sollte es mir ermöglichen, eine solche Zeichenfolge selbst zu erstellen und zu drucken. Diese Lösung funktioniert in diesem Fall jedoch nicht. STR='\''; echo $STR. Wie geplant, lässt BASH dies nicht wirklich zu.
krb686
@mikhail_b, ja, '\''funktioniert für Bash. Können Sie darauf hinweisen, welche Abschnitte von gnu.org/software/bash/manual/bashref.html ein solches Verhalten angeben?
Jingguo Yao
20

Beide Versionen funktionieren entweder mit Verkettung unter Verwendung des maskierten einfachen Anführungszeichens (\ ') oder mit Verkettung durch Einschließen des einfachen Anführungszeichens in doppelte Anführungszeichen ("'").

Der Autor der Frage bemerkte nicht, dass am Ende seines letzten Fluchtversuchs ein zusätzliches einfaches Anführungszeichen (') stand:

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

Wie Sie im vorherigen schönen Stück ASCII / Unicode-Grafik sehen können, folgt auf das letzte maskierte einfache Anführungszeichen (\ ') ein unnötiges einfaches Anführungszeichen ('). Die Verwendung eines Syntax-Textmarkers wie dem in Notepad ++ kann sich als sehr hilfreich erweisen.

Gleiches gilt für ein anderes Beispiel wie das folgende:

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

Diese beiden schönen Beispiele für Aliase zeigen auf sehr komplizierte und verschleierte Weise, wie eine Datei in einer Reihe angeordnet werden kann. Das heißt, aus einer Datei mit vielen Zeilen erhalten Sie nur eine Zeile mit Kommas und Leerzeichen zwischen den Inhalten der vorherigen Zeilen. Um den vorherigen Kommentar zu verstehen, ist das Folgende ein Beispiel:

$ cat Little_Commas.TXT
201737194
201802699
201835214

$ rc Little_Commas.TXT
201737194, 201802699, 201835214
links herum
quelle
3
Upwoted für die ASCII-Tabelle Abbildung :)
PHP-Dev
16

Einfaches Beispiel für das Entkommen von Anführungszeichen in der Shell:

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

Dazu beenden Sie bereits geöffnete ( '), platzieren Escape ( \') und öffnen dann eine weitere ( '). Diese Syntax funktioniert für alle Befehle. Es ist sehr ähnlich wie bei der ersten Antwort.

Kenorb
quelle
15

Ich gehe nicht speziell auf das Problem des Zitierens ein, da es manchmal nur sinnvoll ist, einen alternativen Ansatz in Betracht zu ziehen.

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

die Sie dann anrufen können als:

rxvt 123456 654321

Die Idee ist, dass Sie dies jetzt ohne Rücksicht auf Anführungszeichen aliasisieren können:

alias rxvt='rxvt 123456 654321'

oder, wenn Sie das #aus irgendeinem Grund in alle Anrufe einbeziehen müssen:

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

die Sie dann anrufen können als:

rxvt '#123456' '#654321'

dann lautet ein Alias ​​natürlich:

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

(Ups, ich denke, ich habe das Zitat irgendwie angesprochen :)

Nicerobot
quelle
1
Ich habe versucht, etwas in einfache Anführungszeichen zu setzen, das in doppelte Anführungszeichen gesetzt war, die wiederum in einfache Anführungszeichen standen. Huch. Vielen Dank für Ihre Antwort "Versuchen Sie einen anderen Ansatz". Das machte den Unterschied.
Clinton Blackmore
1
Ich bin 5 Jahre zu spät, aber fehlt Ihnen in Ihrem letzten Alias ​​kein einziges Zitat?
Julien
1
@ Julien Ich sehe kein Problem ;-)
Nicerobot
11

Da man keine einfachen Anführungszeichen in einfache Anführungszeichen setzen kann, ist die einfachste und am besten lesbare Option die Verwendung einer HEREDOC-Zeichenfolge

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

alias rxvt=$command

Im obigen Code wird das HEREDOC an den catBefehl gesendet und die Ausgabe davon wird einer Variablen über die Befehlsersetzungsnotation zugewiesen$(..)

Es ist erforderlich, ein einfaches Anführungszeichen um das HEREDOC zu setzen, da es sich innerhalb von a befindet $()

Nerrve
quelle
Ich wünschte, ich hätte so weit nach unten gescrollt - ich habe diesen Ansatz neu erfunden und bin hierher gekommen, um ihn zu posten! Dies ist weitaus sauberer und lesbarer als alle anderen Fluchtversuche. Nicht, dass es bei einigen Nicht-Bash-Shells nicht funktioniert, z. B. dashbei der Standard-Shell in Ubuntu-Upstart-Skripten und anderswo.
Korny
Vielen Dank! das, wonach ich gesucht habe, die Art und Weise, einen Befehl so zu definieren, wie er über heredoc ist, und den automatisch maskierten Befehl an ssh zu übergeben. BTW cat << COMMAND ohne Anführungszeichen ermöglicht die Interpolation von Variablen innerhalb des Befehls und funktioniert auch für diesen Ansatz.
Igor Tverdovskiy
10

Ich benutze nur Shell-Codes .. zB \x27oder \\x22wie zutreffend. Kein Ärger, nie wirklich.

Rob Jens
quelle
Können Sie ein Beispiel dafür in Betrieb zeigen? Für mich druckt es nur ein Literal x27(auf Centos 6.6)
Will Sheppard
6

Die meisten dieser Antworten treffen auf den speziellen Fall, nach dem Sie fragen. Es gibt einen allgemeinen Ansatz , dass ein Freund und ich habe entwickelt, das für beliebige zitieren , falls erlaubt müssen Sie zitieren bash Befehle durch mehrere Schichten von Shell - Erweiterung, zum Beispiel durch ssh, su -c, bash -cetc. Es gibt einen Kern primitiv ist , dass Sie benötigen, hier in native 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
}

Dies macht genau das, was es sagt: Es zitiert jedes Argument einzeln (natürlich nach der Bash-Erweiterung):

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

Es ist das Offensichtliche für eine Expansionsschicht:

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

(Beachten Sie, dass die doppelten Anführungszeichen $(quote_args ...)erforderlich sind, um das Ergebnis in ein einziges Argument umzuwandeln bash -c.) Und es kann allgemeiner verwendet werden, um durch mehrere Erweiterungsebenen richtig zu zitieren:

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

Das obige Beispiel:

  1. Shell zitiert jedes Argument quote_argseinzeln in das Innere und kombiniert dann die resultierende Ausgabe zu einem einzigen Argument mit den inneren doppelten Anführungszeichen.
  2. Shell-Zitate bash, -cund das schon einmal zitierte Ergebnis aus Schritt 1, und kombinieren dann das Ergebnis in ein einziges Argument mit den äußeren Anführungszeichen.
  3. sendet dieses Chaos als Argument an die äußere bash -c.

Das ist die Idee auf den Punkt gebracht. Sie können einige ziemlich komplizierte Dinge damit machen, aber Sie müssen vorsichtig sein, in welcher Reihenfolge die Bewertung erfolgt und welche Teilzeichenfolgen zitiert werden. Zum Beispiel machen die folgenden Dinge die falschen Dinge (für eine Definition von "falsch"):

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

Im ersten Beispiel, dehnt bash sofort quote_args cd /; pwd 1>&2in zwei getrennte Befehle, quote_args cd /und pwd 1>&2, so wie vor der CWD ist , /tmpwenn der pwdBefehl ausgeführt wird. Das zweite Beispiel zeigt ein ähnliches Problem beim Globbing. In der Tat tritt das gleiche Grundproblem bei allen Bash-Erweiterungen auf. Das Problem hierbei ist, dass eine Befehlssubstitution kein Funktionsaufruf ist: Sie wertet buchstäblich ein Bash-Skript aus und verwendet seine Ausgabe als Teil eines anderen Bash-Skripts.

Wenn Sie versuchen, den Shell-Operatoren einfach zu entkommen, schlagen Sie fehl, da die resultierende Zeichenfolge, an die übergeben bash -cwird, nur eine Folge von Zeichenfolgen in Anführungszeichen ist, die dann nicht als Operatoren interpretiert werden. Dies ist leicht zu erkennen, wenn Sie die Zeichenfolge wiedergeben, die dies tun würde wurden an bash übergeben:

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

Das Problem hier ist, dass Sie zu viel zitieren. Was Sie brauchen, ist, dass die Operatoren nicht als Eingabe für das Gehäuse in Anführungszeichen gesetzt werden bash -c, was bedeutet, dass sie sich außerhalb der $(quote_args ...)Befehlsersetzung befinden müssen.

Folglich müssen Sie im allgemeinsten Sinne jedes Wort des Befehls in Shell zitieren, das zum Zeitpunkt der Befehlssubstitution nicht erweitert werden soll, und keine zusätzlichen Anführungszeichen auf die Shell-Operatoren anwenden:

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

Sobald Sie dies getan haben, ist die gesamte Zeichenfolge ein faires Spiel, um weitere Bewertungsstufen zu zitieren:

$ 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

usw.

Diese Beispiele können overwrought scheinen gegeben , dass Worte wie success, sbinund pwdnicht seine muschel zitiert müssen, aber der entscheidende Punkt zu erinnern , wenn ein Skript unter beliebiger Eingabe zu schreiben ist , dass man alles zitieren wollen Sie nicht absolut sicher doesn‘ Ich brauche kein Zitat, weil Sie nie wissen, wann ein Benutzer eine einwirft Robert'; rm -rf /.

Um besser zu verstehen, was unter der Decke vor sich geht, können Sie mit zwei kleinen Hilfsfunktionen herumspielen:

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

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

Dadurch werden alle Argumente zu einem Befehl aufgelistet, bevor sie ausgeführt werden:

$ 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
Kyle Rose
quelle
Hallo Kyle. Ihre Lösung hat in einem Fall, in dem ich eine Gruppe von Argumenten als einzelnes Argument übergeben musste, hervorragend funktioniert : vagrant ssh -c {single-arg} guest. Das {single-arg}muss als ein einzelnes Argument behandelt werden, da Vagrant das nächste Argument danach als Gastnamen verwendet. Die Reihenfolge kann nicht geändert werden. Aber ich musste einen Befehl und seine Argumente übergeben {single-arg}. Also habe ich Ihren verwendet quote_args(), um den Befehl und seine Argumente zu zitieren und das Ergebnis in doppelte Anführungszeichen zu setzen, und es hat wie ein Zauber funktioniert : vagrant ssh -c "'command' 'arg 1 with blanks' 'arg 2'" guest. Vielen Dank!!!
Andreas Maier
6

IMHO ist die wirkliche Antwort, dass Sie einfache Anführungszeichen in einfachen Anführungszeichen nicht entkommen können.

Es ist unmöglich.

Wenn wir davon ausgehen, dass wir Bash verwenden.

Aus dem Bash-Handbuch ...

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.

Sie müssen einen der anderen String-Escape-Mechanismen verwenden "oder \

Es ist nichts Magisches daran, aliasdass es einfache Anführungszeichen erfordert.

Beide folgenden Arbeiten in Bash.

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

Letzterer verwendet \, um dem Leerzeichen zu entkommen.

Es gibt auch nichts Magisches an # 111111, das einfache Anführungszeichen erfordert.

Die folgenden Optionen erzielen das gleiche Ergebnis wie die beiden anderen Optionen, da der rxvt-Alias ​​wie erwartet funktioniert.

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

Sie können dem lästigen # auch direkt entkommen

alias rxvt="urxvt -fg \#111111 -bg \#111111"
Teknopaul
quelle
"Eine echte Antwort ist, dass Sie sich einfachen Anführungszeichen in Zeichenfolgen in einfachen Anführungszeichen nicht entziehen können." Das stimmt technisch. Sie können jedoch eine Lösung haben, die mit einem einfachen Anführungszeichen beginnt, mit einem einfachen Anführungszeichen endet und nur einfache Anführungszeichen in der Mitte enthält. stackoverflow.com/a/49063038
wisbucky
Nicht durch Flucht, sondern nur durch Verkettung.
Teknopaul
4

Verwenden Sie im angegebenen Beispiel einfach doppelte Anführungszeichen anstelle von einfachen Anführungszeichen als äußeren Escape-Mechanismus:

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

Dieser Ansatz eignet sich für viele Fälle, in denen Sie nur eine feste Zeichenfolge an einen Befehl übergeben möchten: Überprüfen Sie einfach, wie die Shell die Zeichenfolge in doppelten Anführungszeichen durch a interpretiert echo, und maskieren Sie Zeichen bei Bedarf mit Backslash.

Im Beispiel sehen Sie, dass doppelte Anführungszeichen ausreichen, um die Zeichenfolge zu schützen:

$ echo "urxvt -fg '#111111' -bg '#111111'"
urxvt -fg '#111111' -bg '#111111'
oberlies
quelle
4

Natürlich wäre es einfacher, einfach mit doppelten Anführungszeichen zu umgeben, aber wo liegt die Herausforderung dabei? Hier ist die Antwort nur in einfachen Anführungszeichen. Ich verwende stattdessen eine Variable, aliasdamit es einfacher ist, sie als Proof zu drucken, aber es ist dasselbe wie bei der Verwendung alias.

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

Erläuterung

Der Schlüssel ist, dass Sie das einfache Anführungszeichen schließen und es so oft öffnen können, wie Sie möchten. Zum Beispiel foo='a''b'ist das gleiche wie foo='ab'. Sie können also das einfache Anführungszeichen schließen, ein wörtliches einfaches Anführungszeichen \'eingeben und das nächste einfache Anführungszeichen erneut öffnen.

Aufschlüsselungsdiagramm

Dieses Diagramm verdeutlicht anhand von Klammern, wo die einfachen Anführungszeichen geöffnet und geschlossen werden. Anführungszeichen sind nicht wie in Klammern "verschachtelt". Sie können auch auf die korrekte Farbhervorhebung achten. Die zitierten Saiten sind kastanienbraun, während die \'schwarz sind.

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

(Dies ist im Wesentlichen die gleiche Antwort wie die von Adrian, aber ich denke, das erklärt es besser. Auch seine Antwort enthält am Ende zwei überflüssige einfache Anführungszeichen.)

wisbucky
quelle
+1 für die Verwendung der '\''Methode Ich empfehle gegenüber der '"'"'Methode, die für Menschen oft schwerer zu lesen ist.
mtraceur
3

Hier ist eine Ausarbeitung zu The One True Answer, auf die oben Bezug genommen wurde:

Manchmal lade ich mit rsync über ssh herunter und muss einem Dateinamen mit einem 'darin ZWEIMAL entkommen! (OMG!) Einmal für Bash und einmal für SSH. Hier gilt das gleiche Prinzip alternierender Anführungszeichen.

Nehmen wir zum Beispiel an, wir wollen: Louis Therouxs LA Stories ...

  1. Zuerst schließen Sie Louis Theroux in einfache Anführungszeichen für Bash und doppelte Anführungszeichen für SSH ein: "Louis Theroux"
  2. Dann verwenden Sie einfache Anführungszeichen, um einem doppelten Anführungszeichen '"' zu entgehen.
  3. Verwenden Sie doppelte Anführungszeichen, um dem Apostroph "'" zu entkommen.
  4. Wiederholen Sie dann # 2 mit einfachen Anführungszeichen, um einem doppelten Anführungszeichen '"' zu entgehen.
  5. Schließen Sie dann LA Stories in einfache Anführungszeichen für Bash und doppelte Anführungszeichen für ssh ein: '"LA Stories"'

Und siehe da! Sie landen damit:

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

Das ist eine Menge Arbeit für ein kleines Kind - aber los geht's

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

Erklärung zur Implementierung:

  • doppelte Anführungszeichen, damit wir einfache einfache Anführungszeichen ausgeben und die ${...}Syntax verwenden können

  • Das Suchen und Ersetzen von bash sieht folgendermaßen aus: ${varname//search/replacement}

  • wir ersetzen 'durch'\''

  • '\''codiert eine Single 'wie folgt:

    1. ' beendet das einfache Anführungszeichen

    2. \'codiert a '(der Backslash wird benötigt, da wir uns nicht in Anführungszeichen befinden)

    3. ' Startet das einfache Anführungszeichen erneut

    4. bash verkettet automatisch Zeichenfolgen ohne Leerzeichen dazwischen

  • Es gibt ein \vor jedem \und 'weil das die Fluchtregeln für sind ${...//.../...}.

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

PS Codieren Sie immer in Zeichenfolgen in einfachen Anführungszeichen, da diese viel einfacher sind als Zeichenfolgen in doppelten Anführungszeichen.

JasonWoof
quelle
2

Eine andere Möglichkeit, das Problem zu vieler Ebenen verschachtelter Anführungszeichen zu beheben:

Sie versuchen, zu viel in einen zu kleinen Raum zu stopfen, verwenden Sie also eine Bash-Funktion.

Das Problem ist, dass Sie versuchen, zu viele Verschachtelungsebenen zu haben, und die grundlegende Alias-Technologie nicht leistungsfähig genug ist, um sie zu berücksichtigen. Verwenden Sie eine Bash-Funktion wie diese, damit die Ticks mit einfachen, doppelten Anführungszeichen und übergebenen Parametern wie erwartet normal behandelt werden:

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

Dann können Sie Ihre $ 1- und $ 2-Variablen sowie einfache, doppelte Anführungszeichen und Back-Ticks verwenden, ohne sich Gedanken über die Alias-Funktion machen zu müssen, die deren Integrität beeinträchtigt.

Dieses Programm druckt:

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

Wenn Sie GNU Parallel installiert haben, können Sie das interne Angebot verwenden:

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

Ab Version 20190222 können Sie sogar --shellquotemehrmals:

$ 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

Die Zeichenfolge wird in allen unterstützten Shells (nicht nur bash) zitiert .

Ole Tange
quelle
1

Diese Funktion:

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

ermöglicht das Zitieren von 'innen '. Verwenden Sie wie folgt:

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

Wenn die zu zitierende Zeile komplexer wird, z. B. doppelte Anführungszeichen gemischt mit einfachen Anführungszeichen, kann es ziemlich schwierig werden, die Zeichenfolge innerhalb einer Variablen in Anführungszeichen zu setzen. Wenn solche Fälle auftreten, schreiben Sie die genaue Zeile, die Sie zitieren müssen, in ein Skript (ähnlich wie dieses).

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

Wird ausgegeben:

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

Alle korrekt in Anführungszeichen gesetzten Zeichenfolgen in einfachen Anführungszeichen.


quelle
1

Wenn Sie die Shell-Zeichenfolge in Python 2 oder Python 3 generieren, können die folgenden Argumente hilfreich sein:

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

Dies wird Folgendes ausgeben:

foo ain't "bad" so there!
foo 'ain'"'"'t' '"bad"' so 'there!'
George V. Reilly
quelle
1

Hier sind meine zwei Cent - für den Fall, dass man shportabel und nicht nur spezifisch sein möchte bash(die Lösung ist jedoch nicht zu effizient, da sie ein externes Programm startet - sed):

  • Setzen Sie dies in quote.sh(oder nur quote) irgendwo auf Ihre PATH:
# Dies funktioniert mit Standardeingabe (stdin)
quote () {
  echo -n "'";
  sed 's / \ ([' '' ''] ['' '' '] * \) /' '' '' \ 1 '' '' '/ g';
  echo -n "'"
}}

Fall "$ 1" in
 -) Zitat ;;
 *) echo "usage: cat ... | quote - # Eingabe in einfachen Anführungszeichen für die Bourne-Shell" 2> & 1 ;;
esac

Ein Beispiel:

$ echo -n "G'day, Kumpel!" | ./quote.sh -
'Guten Tag, Freund!'

Und das konvertiert natürlich zurück:

$ echo 'G' "'"' Tag, Kumpel! '
Guten Tag, Freund!

Erläuterung: Grundsätzlich müssen wir die Eingabe in Anführungszeichen setzen 'und dann auch jedes einzelne Anführungszeichen durch dieses Mikromonster ersetzen: '"'"'(Beenden Sie das erste Anführungszeichen mit einer Paarung ', umgehen Sie das gefundene einfache Anführungszeichen, indem Sie es mit doppelten Anführungszeichen umschließen - "'"und dann endlich eine neue Öffnung Apostroph ausgeben ', oder in pseudo-Notation: ' + "'" + ' == '"'"')

Eine Standardmethode hierfür ist die Verwendung seddes folgenden Substitutionsbefehls:

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

Ein kleines Problem ist jedoch, dass man, um das in der Shell zu verwenden, all diesen einfachen Anführungszeichen im sed-Ausdruck selbst entkommen muss - was zu so etwas führt

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

(und eine gute Möglichkeit, dieses Ergebnis zu erzielen, besteht darin, den ursprünglichen Ausdruck s/\(['][']*\)/'"\1"'/gden Skripten von Kyle Rose oder George V. Reilly zuzuführen).

Schließlich ist es irgendwie sinnvoll zu erwarten, dass die Eingabe von kommt stdin- da das Weiterleiten über Befehlszeilenargumente bereits zu viel Mühe bereiten kann.

(Oh, und vielleicht möchten wir eine kleine Hilfemeldung hinzufügen, damit das Skript nicht hängen bleibt, wenn jemand es nur ausführt und sich ./quote.sh --helpfragt, was es tut.)

ジ ョ ー ジ
quelle
0

Hier ist eine andere Lösung. Diese Funktion nimmt ein einzelnes Argument und zitiert es angemessen mit dem einfachen Anführungszeichen, genau wie in der obigen Antwort erklärt:

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

Sie können es also folgendermaßen verwenden:

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

x="this text is quoted: 'hello'"
eval "echo $(single_quote "$x")"
this text is quoted: 'hello'
Exbuddha
quelle