Der Umgang mit mehreren Ebenen des Zitierens (wirklich mehrere Ebenen des Parsens / Interpretierens) kann kompliziert werden. Es hilft, ein paar Dinge zu beachten:
- Jede „Zitierstufe“ kann möglicherweise eine andere Sprache beinhalten.
- Angebotsregeln variieren je nach Sprache.
- Wenn Sie mit mehr als einer oder zwei verschachtelten Ebenen arbeiten, ist es normalerweise am einfachsten, „von unten nach oben“ zu arbeiten (dh von innen nach außen).
Ebenen des Zitierens
Sehen wir uns Ihre Beispielbefehle an.
pgrep -fl java | grep -i datanode | awk '{print $1}'
Ihr erster Beispielbefehl (oben) verwendet vier Sprachen: Ihre Shell, den regulären Ausdruck in pgrep , den regulären Ausdruck in grep (der sich möglicherweise von der regulären Ausdruckssprache in pgrep unterscheidet ) und awk . Es gibt zwei Interpretationsebenen: die Shell und eine Ebene nach der Shell für jeden der beteiligten Befehle. Es gibt nur eine explizite Quote-Ebene (Shell-Quote in awk ).
ssh host …
Als nächstes haben Sie eine Stufe von ssh hinzugefügt . Dies ist praktisch eine andere Shell-Ebene: ssh interpretiert den Befehl nicht selbst, sondern übergibt ihn an eine Shell auf der Remote-Seite (über (z. B.) sh -c …
), und diese Shell interpretiert den String.
ssh host "sudo su user -c …"
Dann haben Sie gefragt, ob Sie eine weitere Shell-Ebene in der Mitte hinzufügen möchten , indem Sie su verwenden (über sudo , das die Befehlsargumente nicht interpretiert, sodass wir sie ignorieren können). An diesem Punkt gibt es drei Ebenen der Verschachtelung ( awk → shell, shell → shell ( ssh ), shell → shell ( su user -c ) Ihre Muscheln sind Bourne-kompatibel (z. B. sh , ash , dash , ksh , bash , zsh usw.). Eine andere Art von Muschel ( fish , rc)usw.) erfordern möglicherweise eine andere Syntax, die Methode gilt jedoch weiterhin.
Prost
- Formulieren Sie die Zeichenfolge, die Sie auf der innersten Ebene darstellen möchten.
- Wählen Sie einen Zitiermechanismus aus dem Zitierrepertoire der nächsthöheren Sprache.
- Zitieren Sie die gewünschte Zeichenfolge gemäß dem von Ihnen ausgewählten Zitiermechanismus.
- Es gibt oft viele Variationen, wie man welchen Zitiermechanismus anwendet. Das von Hand zu machen, ist normalerweise eine Frage der Übung und Erfahrung. Wenn Sie programmgesteuert vorgehen, ist es in der Regel am besten, die am einfachsten zu behebende Option auszuwählen (in der Regel die "wörtlichste" (die wenigsten Fluchtwege)).
- Verwenden Sie optional die resultierende Zeichenfolge in Anführungszeichen mit zusätzlichem Code.
- Wenn Sie Ihr gewünschtes Zitat / Interpretationsniveau noch nicht erreicht haben, nehmen Sie den resultierenden zitierten String (plus eventuell hinzugefügten Code) und verwenden Sie ihn als Start-String in Schritt 2.
Zitiersemantik Vary
Hierbei ist zu beachten, dass jede Sprache (Zitatstufe) ein und demselben Zitatzeichen eine leicht unterschiedliche Semantik (oder sogar eine drastisch unterschiedliche Semantik) geben kann.
Die meisten Sprachen haben einen "wörtlichen" Zitiermechanismus, aber sie unterscheiden sich genau darin, wie wörtlich sie sind. Das einfache Anführungszeichen von Bourne-ähnlichen Shells ist tatsächlich wörtlich (was bedeutet, dass Sie es nicht verwenden können, um ein einfaches Anführungszeichen selbst zu zitieren). Andere Sprachen (Perl, Ruby) sind insofern weniger wörtlich, als sie einige Backslash-Sequenzen innerhalb von Regionen in einfachen Anführungszeichen nicht wörtlich interpretieren (insbesondere \\
und \'
führen zu \
und '
, aber andere Backslash-Sequenzen sind tatsächlich wörtlich).
Sie müssen die Dokumentation für jede Ihrer Sprachen lesen, um die Angebotsregeln und die allgemeine Syntax zu verstehen.
Dein Beispiel
Die innerste Ebene Ihres Beispiels ist ein awk- Programm.
{print $1}
Sie werden dies in eine Shell-Befehlszeile einbetten:
pgrep -fl java | grep -i datanode | awk …
Wir müssen (zumindest) den Raum und $
das AWK- Programm schützen . Die naheliegende Wahl ist die Verwendung von einfachen Anführungszeichen in der Shell um das gesamte Programm.
Es gibt jedoch noch andere Möglichkeiten:
{print\ \$1}
direkt dem Raum entkommen und $
{print' $'1}
einfaches Anführungszeichen nur der Raum und $
"{print \$1}"
das Ganze doppelt zitieren und dem entkommen $
{print" $"1}
Nur das Leerzeichen in doppelte Anführungszeichen setzen und $
Dies kann die Regeln ein wenig verbiegen (das Entkommen $
am Ende einer Zeichenfolge in doppelten Anführungszeichen ist wörtlich), aber es scheint in den meisten Shells zu funktionieren.
Wenn das Programm ein Komma zwischen den offenen und geschlossenen geschweiften Klammern verwenden würde, müssten wir auch entweder das Komma oder die geschweiften Klammern in Anführungszeichen setzen oder streichen, um eine „Klammererweiterung“ in einigen Shells zu vermeiden.
Wir wählen es aus '{print $1}'
und binden es in den Rest des Shell-Codes ein:
pgrep -fl java | grep -i datanode | awk '{print $1}'
Als nächstes wollten Sie dies über su und sudo ausführen .
sudo su user -c …
su user -c …
ist genau wie some-shell -c …
(außer, dass es unter einer anderen UID läuft), also fügt su nur eine weitere Shell-Ebene hinzu. sudo interpretiert seine Argumente nicht und fügt daher keine Anführungszeichen hinzu.
Wir benötigen eine andere Shell-Ebene für unsere Befehlszeichenfolge. Wir können wieder einfache Anführungszeichen auswählen, müssen aber den vorhandenen einfachen Anführungszeichen eine besondere Behandlung geben. Der übliche Weg sieht so aus:
'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'
Hier gibt es vier Zeichenfolgen, die von der Shell interpretiert und verkettet werden: die erste Zeichenfolge in Anführungszeichen ( pgrep … awk
), ein einfaches Anführungszeichen mit Escapezeichen, das Programm awk mit Anführungszeichen und ein weiteres einfaches Anführungszeichen mit Escapezeichen.
Es gibt natürlich viele Alternativen:
pgrep\ -fl\ java\ \|\ grep\ -i\ datanode\ \|\ awk\ \'{print\ \$1}
entkomme allem wichtigen
pgrep\ -fl\ java\|grep\ -i\ datanode\|awk\ \'{print\$1}
das gleiche, aber ohne überflüssiges Leerzeichen (auch im awk- Programm!)
"pgrep -fl java | grep -i datanode | awk '{print \$1}'"
doppelte Anführungszeichen das Ganze, entkomme dem $
'pgrep -fl java | grep -i datanode | awk '"'"'{print \$1}'"'"
Ihre Variation; etwas länger als üblich, da doppelte Anführungszeichen (zwei Zeichen) anstelle von Escapezeichen (ein Zeichen) verwendet werden
Die Verwendung anderer Anführungszeichen in der ersten Ebene ermöglicht andere Variationen auf dieser Ebene:
'pgrep -fl java | grep -i datanode | awk "{print \$1}"'
'pgrep -fl java | grep -i datanode | awk {print\ \$1}'
Wenn Sie die erste Variante in die sudo / * su * -Befehlszeile einbetten, geben Sie Folgendes ein:
sudo su user -c 'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'
Sie können dieselbe Zeichenfolge auch in anderen Kontexten auf Shell-Ebene verwenden (z ssh host …
. B. ).
Als nächstes haben Sie eine Stufe von ssh hinzugefügt . Dies ist praktisch eine andere Shell-Ebene: ssh interpretiert den Befehl selbst nicht, übergibt ihn jedoch einer Shell auf der Remote-Seite (über (z. B.) sh -c …
), und diese Shell interpretiert den String.
ssh host …
Der Prozess ist der gleiche: Nehmen Sie die Zeichenfolge, wählen Sie eine Anführungszeichenmethode aus, verwenden Sie sie, betten Sie sie ein.
Wieder einfache Anführungszeichen verwenden:
'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'
Jetzt gibt es elf Zeichenfolgen, die interpretiert und verkettet werden: 'sudo su user -c '
Escape-Anführungszeichen, 'pgrep … awk '
Escape-Anführungszeichen, Escape-Backslash, Zwei Escape-Anführungszeichen, das Programm awk mit einfachen Anführungszeichen, Escape- Anführungszeichen, Escape-Backslash und End-Escape-Anführungszeichen .
Das endgültige Formular sieht folgendermaßen aus:
ssh host 'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'
Es ist etwas unhandlich, dies von Hand zu tippen, aber die wörtliche Art der einfachen Anführungszeichen in der Shell macht es einfach, eine geringfügige Variation zu automatisieren:
#!/bin/sh
sq() { # single quote for Bourne shell evaluation
# Change ' to '\'' and wrap in single quotes.
# If original starts/ends with a single quote, creates useless
# (but harmless) '' at beginning/end of result.
printf '%s\n' "$*" | sed -e "s/'/'\\\\''/g" -e 1s/^/\'/ -e \$s/\$/\'/
}
# Some shells (ksh, bash, zsh) can do something similar with %q, but
# the result may not be compatible with other shells (ksh uses $'...',
# but dash does not recognize it).
#
# sq() { printf %q "$*"; }
ap='{print $1}'
s1="pgrep -fl java | grep -i datanode | awk $(sq "$ap")"
s2="sudo su user -c $(sq "$s1")"
ssh host "$(sq "$s2")"