Leerzeichen im SCP-Befehl können in einer While-Schleife nicht escaped werden

Leerzeichen im SCP-Befehl können in einer While-Schleife nicht escaped werden

Ich versuche, ein Skript zu erstellen, um ein Spiegel-Backup eines kostenlosen ESXi 6.5 auf einem anderen kostenlosen ESXi 6.5-Host zu erstellen. Ich bin fast fertig, aber dieses Problem macht mich verrückt. Dies ist ein Teil des Skripts; ich verwende Bash für das Skript:

#!/bin/sh
find /vmfs/volumes/datastore1/ -regex '.*\.\(vmx\|nvram\|vmsd\|vmdk\)$' ! -name *-flat.vmdk | while read line; do
    dir1=$(dirname "${line}"| sed 's/ /\\ /g')
    dir2=$(dirname "${line}"| sed 's/ /\\\\ /g')
    ssh -n [email protected] "mkdir -p $dir1"
    cmd=$(echo $line "XX.XX.XX.XX:\""$dir2"/\"")
    echo $cmd
    scp -pr $cmd
done

Die Ausgabe ist:

  • für jede VM, deren Name keine Leerzeichen enthält, erfolgreich.
  • für jede VM mit Leerzeichen im Namen (letztes Wort im VM-Namen): Keine solche Datei oder kein solches Verzeichnis

Ich habe alles versucht, damit dieser SCP den vollständigen Pfad erhält, und er ignoriert alles: Setzen Sie einfache Anführungszeichen, doppelte Anführungszeichen, Escape-Zeichen durch Leerzeichen, doppelte, dreifache Escape-Zeichen. Setzen Sie Argumente direkt in SCP, setzen Sie alle Argumente von SCP in eine Variable und übergeben Sie sie anschließend.

Wenn der Befehl außerhalb eines Skripts ausgeführt wird, läuft er fehlerfrei. Wenn er im Skript ausgeführt wird, tritt ein Fehler auf und nur der letzte Teil nach dem Leerzeichen wird übernommen.

Antwort1

Ihr Code ist in vielerlei Hinsicht fehlerhaft.

-name *-flat.vmdkIst anfällig fürGlotzen; wohin es erweitert wird, hängt von den Dateien im aktuellen Arbeitsverzeichnis ab. *sollte in Anführungszeichen gesetzt werden (z. B. -name '*-flat.vmdk').

Dies ist nicht das einzige Mal, dass Ihrem Code Anführungszeichen fehlen. echo $lineist fehlerhaft wegenDas(UndDasIm Algemeinen).

read linesollte mindestens sein IFS= read -r line. Es würde immer noch fehlschlagen, wenn ein Pfad (der von zurückgegeben wird find) das Newline-Zeichen (ein gültiges Zeichen in Dateinamen) enthielte. Aus diesem Grund find … -exec … \;ist es besser. Sie können so vorgehen:

find … -exec sh -c '…' sh {} \;

was eine andere Ebene des Zitierens einführt; oder so:

find … -exec helper_script {} \;

was das Zitieren erleichtert helper_script. Letzterer Ansatz wird befürwortet vondiese Antwort, die Antwort behebt immer noch keine anderen Probleme.

Ihre Variablen dir1scheinen dir2einige umständliche Escape-Zeichen einzufügen, um mit Leerzeichen umzugehen. Sie sollten sich nicht auf Escape-Zeichen wie dieses verlassen. Selbst wenn Sie es mit Leerzeichen hinbekommen, gibt es andere Zeichen, die Sie generell escapen müssen. Der richtige Weg ist,Zitatrichtig.

Es gibt mindestens drei Zitierebenen:

  1. in der ursprünglichen Shell, wo findaufgerufen wird;
  2. in einer Shell, die von erzeugt wurde -exec shoder in einer Shell, die das interpretiert helper_script;
  3. in einer Shell, die auf der Remote-Seite von erzeugt wird ssh … "whatever command"(ähnlich für Pfade, die von verarbeitet werden scp).

Durch die Einführung von a helper_scriptwird sichergestellt, dass die erste Ebene nicht mit dem Rest in Konflikt gerät. Der Hauptbefehl wäre:

find /vmfs/volumes/datastore1/ -regex '.*\.\(vmx\|nvram\|vmsd\|vmdk\)$' ! -name '*-flat.vmdk' -exec /path/to/helper_script {} \;

Und das helper_script:

#!/bin/sh
# no need for bash

addrs=XX.XX.XX.XX

pth="$1"
drctry="${pth%/*}"
# no need for dirname (separate executable)

ssh "root@$addrs" "mkdir -p '$drctry'"
scp -pr "$pth" "$addrs:'$drctry/'"

Jetzt ist das Wichtigste, es als String sshzu bekommen . Dieser wird an die Remote-Shell übergeben undmkdir -p 'whatever/the var{a,b}e/expand$t*'interpretiert. Ohne die inneren einfachen Anführungszeichen könnte es auf eine Weise interpretiert werden, die Sie nicht möchten; mein Beispiel übertreibt dies. Sie könnten versuchen, jedes störende Zeichen zu maskieren, das wäre schwierig; also zitieren Sie.

AberWenn die Variable ein einfaches Anführungszeichen enthält, können auf der Remote-Seite einige Teilzeichenfolgen entfernt werden. Dies öffnet eine Schwachstelle für Code-Injection. Beispiel: dieser Pfad:

…/foo/'$(nasty command)'bar/baz/…

wird sehr gefährlich, wenn es in einfache Anführungszeichen eingebettet und interpretiert wird. Sie sollten $drctryvorher bereinigen:

drctry="$(printf '%s' "${pth%/*}" | sed "s/'/'\"'\"'/g")"

Der gefährliche Beispielpfad sieht nun folgendermaßen aus:

…/foo/'"'"'$(nasty command)'"'"'bar/baz/…

Dies ähnelt in gewisser Weise Ihrer Verwendung von sed, aber da das einfache Anführungszeichen nun das einzige problematische Zeichen ist, sollte es besser sein.

scperfordert aus im Wesentlichen demselben Grund ähnliche Anführungszeichen im Remote-Pfad. Auch hier ist das korrekte Escapen mit Backslashs umständlicher (falls überhaupt möglich).


Eine kleine Verbesserung besteht darin, dem Hilfsskript zu erlauben, mehr als ein Objekt zu verarbeiten. Dadurch werden weniger Shell-Prozesse ausgeführt:

find /vmfs/volumes/datastore1/ -regex '.*\.\(vmx\|nvram\|vmsd\|vmdk\)$' ! -name '*-flat.vmdk' -exec /path/to/helper_script_2 {} +

Und das helper_script_2:

#!/bin/sh

addrs=XX.XX.XX.XX

for pth; do
   drctry="$(printf '%s' "${pth%/*}" | sed "s/'/'\"'\"'/g")"
   ssh "root@$addrs" "mkdir -p '$drctry'"
   scp -pr "$pth" "$addrs:'$drctry/'"
done

-exec sh -c '…'Es ist möglich, mit (oder ) einen eigenständigen Befehl (ohne Bezug auf ein Hilfsskript) zu erstellen -exec sh -c "…". Wegen der meisten äußeren Anführungszeichen würde dies in einem Anführungszeichen- und/oder Escape-Wahnsinn münden. Der folgende Trick mit Befehlsersetzung und diesem Dokument ist nützlich, um dies zu vermeiden:

find /vmfs/volumes/datastore1/ \
   -type f \
   -regex '.*\.\(vmx\|nvram\|vmsd\|vmdk\)$' \
 ! -name '*-flat.vmdk' \
   -exec sh -c "$(cat << 'EOF'

addrs=XX.XX.XX.XX

for pth; do
   drctry="$(printf '%s' "${pth%/*}" | sed "s/'/'\"'\"'/g")"
   ssh "root@$addrs" "mkdir -p '$drctry'" \
   && scp -pr "$pth" "$addrs:'$drctry/'"
done

EOF
   )" sh {} +

Um dies (und einige Fragmente in den vorherigen Snippets) im Kontext der Variablenerweiterung vollständig zu verstehen, müssen Sie Folgendes wissen:Anführungszeichen in AnführungszeichenUndwarum EOFwird zitiert(die verlinkte Antwort zitiert man bash, aber das ist allgemeinerPOSIX-Verhalten). Beachten Sie auch, dass ich hinzugefügt habe, -type fum mögliche Verzeichnisse auszuschließen, die dem regulären Ausdruck entsprechen; und ich habe geschrieben ssh … && scp …, sodass, wenn Ersteres fehlschlägt (was auch den Fall einschließt, dass es mkdir -pfehlschlägt), Letzteres nicht ausgeführt wird.

Antwort2

Verschieben Sie den Inhalt rechts vom Pipe-Zeichen ( |) in ein Shell-Skript und führen Sie dann Folgendes aus:

find /vmfs/volumes/datastore1/ -regex '.*\.\(vmx\|nvram\|vmsd\|vmdk\)$' ! -name *-flat.vmdk -exec /path/to/shell/script {} \;

Das {}wird jeden einzelnen Dateinamen, der erfolgreich ist, ordnungsgemäß maskieren findund dann Ihr Skript aufrufen und den maskierten/in Anführungszeichen gesetzten Dateinamen als erstes Argument übergeben. Greifen Sie einfach $1in Ihrem Skript darauf zu.

Antwort3

Erleben Sie die Magie des Arrays:

$ line="meh bleh"
$ dir="hello\ world"
$ cmd=$(echo "$line" "$dir")
$ for i in $cmd; do echo "$i"; done
meh
bleh
hello\
world
$ for i in "$cmd"; do echo "$i"; done
meh bleh hello\ world
$ cmd=("$line" "$dir")
$ for i in "${cmd[@]}"; do echo "$i"; done
meh bleh
hello\ world
$

Das Problem, wenn man alles in eine einfache Variable packt, ist, dass niemand mehr erkennen kann, was jedes Argument ist.

verwandte Informationen