Ich kann per SSH auf das in einer lokalen Variable gespeicherte Verzeichnis zugreifen $out_dir
. Ich kann jedoch nicht herausfinden, warum der find | head
Befehl nicht funktioniert. InVersuch Nr. 2, habe ich am Ende des find
Befehls einen zweiten Backslash hinzugefügt:
Fehler beim Versuch Nr. 1: find: `/dir/on/remote/server': No such file or directory
ssh $user@$ip << EOF
discard=$( find $out_dir -exec basename {} \; | head -n -1 )
for f in $discard; do
echo "rm $f"
done
logout
EOF
Fehler bei Versuch Nr. 2: syntax error near unexpected token `|'
ssh $user@$ip << EOF
discard=$( find $out_dir -exec basename {} \\; | head -n -1 )
for f in $discard; do
echo "rm $f"
done
logout
EOF
Antwort1
Der Grund, warum Sie bei Versuch Nr. 1 die Meldung „Keine solche Datei oder kein solches Verzeichnis“ erhalten, liegt darin, dass die gesamte Variablen- und Befehlsinterpolation für das Heredoc (das <<EOF
) lokal erfolgt, bevor es an den Remote-Host gesendet wird.
Wie Sie richtig bemerken, $out_dir
wird interpoliert; Sie sehen das Verzeichnis, das Sie sehen möchten. Dies geschieht auf Ihrem lokalen Computer, bevor Sie den SSH-Aufruf durchführen. Die Befehlssubstitution ( $()
) erfolgt ebenfalls lokal (aber Sie beabsichtigen, dies remote durchzuführen). Anders gesagt, der gesamte Fund wird lokal verarbeitet, bevor er an den Remotecomputer gesendet wird – alles, was darin verpackt ist, $()
wird vom Heredoc verarbeitet. Daher scheint es ${out_dir}
auf Ihrem lokalen Computer nicht vorhanden zu sein – „Keine solche Datei oder kein solches Verzeichnis“.
Wenn Sie sich das selbst genauer ansehen möchten, schränken wir das Beispiel ein. Versuchen Sie Folgendes:
$ ssh foo@localhost <<EOF
echo "using account: $(whoami)"
EOF
Pseudo-terminal will not be allocated because stdin is not a terminal.
<truncated>
using account: vagrant
Offensichtlich wird der Inhalt von $() lokal ausgeführt, da mein lokales Konto „vagrant“ ist. Wenn es richtig funktionieren würde, wäre das Konto „foo“, weil ich angegeben habe ssh foo@localhost
.
Versuch Nr. 2 ist fehlgeschlagen, weil Sie das '\' (anstelle des ';') maskiert haben. Find muss -exec
beendet werden, und das ist es jetzt nicht. Stattdessen endet der Befehl abrupt und Sie haben ein hängendes ';', das den Bash-Befehl beendet. '|' erwartet, eine Eingabe weiterzuleiten, aber es ist nichts angegeben. Tatsächlich haben Sie Folgendes erstellt:
$ | cat
-bash: syntax error near unexpected token `|'
Also, was ist eine funktionierende Lösung?
Gut, find
ich kann das Löschen für Sie erledigen:
ssh $user@$ip <<EOF
find $out_dir -delete
EOF
Es ist einfach und direkt. Wenn Sie Bedenken haben, einige Dateien auszuwählen und andere auszuschließen, überarbeiten Sie die find
.
Ich gebe den find
Rat, das zu überarbeiten, weil es in Ihrer aktuellen Version so aussieht, als würden Sie versuchen, einige Sonderfälle zu behandeln:
- die Verwendung von Basisnamen
- die Verwendung von
head -n -1
Ich erwarte jedoch nicht, dass diese Handhabung wie beabsichtigt funktioniert.
Erstens basename
wird der Aufruf den größten Teil des Pfads löschen, und in Ihrem SSH-Befehl tun Sie nichts, um das Verzeichnis zu ändern. Ihre SSH-Ausführung wird versuchen, alles relativ zum Home-Verzeichnis von ${user} zu löschen – und bei der Angabe von ${out_dir} scheinen Sie ein anderes Verzeichnis als das Home-Verzeichnis verwenden zu wollen! Auch hier lassen Sie find
das Löschen einfach für Sie erledigen.
Zweitens, und vielleicht liege ich damit falsch, vermute ich, head -n -1
dass Sie ${out_dir} aus Ihrer Liste der Zieldateien entfernen.
$ find junk/
junk/
junk/one
junk/two
junk/three
Zu Recht möchten Sie nicht löschen junk/
.
Versuchen Sie jedoch Folgendes:
$ seq 1 4
1
2
3
4
$ seq 1 4 | head -n -1
1
2
3
Dadurch bleibt nicht die erste Zeile erhalten, sondern die letzte. Versuchen Sie tail:
$ seq 1 4 | tail -n +2
2
3
4
Aber nochmals, wir lassen find
das Löschen einfach für Sie erledigen.
Wenn Sie mit find nur Dateien entfernen möchten, sehen Sie sich an -type f
. Es gibt aber auch Ausschluss- und Einschlussmuster für erweitertes Filtern.
Antwort2
Versuche dies:
ssh "$user@$ip" "out_dir=$out_dir;" '
discard=$( find $out_dir -exec basename {} \; | head -n -1 )
for f in $discard; do
echo "rm $f"
done
'
Notiz:Der vorgesehene Fernbefehl könnte einfacher und sicherer gestaltet werden, aber daran herumzunörgeln würde nur Verwirrung stiften.
Grundsätzlich sollten Sie die Teile weitergeben, dieA)sollenAucherweitert werden auf dielokalMaschine und solche, dieB)sollte seinnurerweitert werden auf dieFernbedienungMaschine alsseparateArgumente für ssh, erstere in doppelte Anführungszeichen und letztere in einfache Anführungszeichen.
Ssh verbindet alle „Befehls“-Argumente mit Leerzeichen, bevor sie als einzelnes Argument an die -c
Option der Login-Shell des Benutzers auf dem Remote-Computer übergeben werden.
Wenn der Remote-Befehl selbst einfache Anführungszeichen enthalten soll, können Sie die Kombination aus Befehlsersetzung + here doc verwenden. Wenn der Wert der „lokalen“ Variable Leerzeichen und andere Sonderzeichen enthalten kann, sollten Sie diese maskieren:
out_dir='/path/to/ * happiness * '
out_dir=$(printf %s "$out_dir" | sed 's/[^a-zA-Z0-9_/.]/\\&/g')
ssh localhost out_dir="$out_dir;" "$(cat <<'EOT'
echo '<$out_dir>' = "<$out_dir>"
EOT
)"
Beachten Sie, dass, wenn einfache Anführungszeichen in <<'EOF'
; weggelassen werden, das $out_dir
hier im Dokument erweitert wird (auf dem lokalen Computer).
Neuere Versionen von Bash verfügen über die ${var@Q}
Form, daher können Sie dort einfach "out_dir=${outdir@Q};"
anstelle des hässlichen verwenden echo | sed
.
Die Übergabe des Remote-Skripts über stdin ist selten (wenn überhaupt!) notwendig.