Jak SSH może pracować z warunkiem if?

8

Mam ifinstrukcję do obliczania plików i usuwania wszystkich oprócz trzech ostatnich plików. Ale chcę uruchomić to polecenie zdalnie. Jak mogę połączyć sshz ifwarunkiem?

Próbowałem tego, ale bez powodzenia.

#!/bin/bash

ssh -t  test@192.168.94.139 "cd /var/www/test.com/backup ;

if [ $(ls  | wc -l) -lt 3  ]
then
    echo "Less"
else [ $(ls -t *.tgz|awk 'NR >3'|xargs rm -f) ]
    echo "deleted"
fi"

Wystąpił błąd:

ls: nie można uzyskać dostępu * .tgz: nie ma takiego pliku ani katalogu

Janith
źródło
Jak mogę to dostosować?
Janith,
@ user68186 Dlaczego to jest? Polecenie jest w cudzysłowie.
Wyścigi lekkości na orbicie
2
$( )Część polecenia jest wykonywany przez lokalną powłoce, zanim jeszcze rozpocznie sshkomendę. Dotyczy to zarówno sytuacji $( )samodzielnych, jak i otoczonych przez "s. Jednak gdyby $( )był w środku ', nie byłby wykonywany przez lokalną powłokę.
kasperd,

Odpowiedzi:

3

UWAGA: w rzeczywistości pytanie ma dwie warstwy. Jednym z nich jest „Chcę wykonać nietrywialne zadanie na zdalnym serwerze dostępnym za pośrednictwem SSH”. Drugi to „Próbuję przekazać złożony ciąg do polecenia, a argument jest inny niż zamierzony”. Odpowiadam na pytanie niskiego poziomu, nie dyskutując, czy zastosowane podejście jest „właściwe” (wygodne, nie podatne na błędy, bezpieczne itp.) Do rozwiązania wysokiego poziomu. Jak wskazują inne odpowiedzi i komentarze, prawdopodobnie nie jest.

Twoja linia poleceń ma w większości rację; musisz tylko trochę zmienić cytat.

Główny problem polega na tym, że łańcuchy cudzysłowów są rozszerzane przez lokalną powłokę, a zatem $(...)części będą oceniane w systemie lokalnym. Aby przekazać je do systemu zdalnego, musisz umieścić skrypt w pojedynczym cudzysłowie.

Masz również osadzone znaki cudzysłowu. W twoim oryginalnym skrypcie istnieją argumenty za tymi dwoma echo; jeśli zmienisz zewnętrzny cytat na pojedyncze, będzie to skrypt awk. Skutecznie skutkuje to pominięciem cudzysłowu, co nie przeszkadza echos, ale zepsuje skrypt awk, ponieważ znak większy niż stanie się przekierowaniem wyjścia. Więc po zmianie zewnętrznych znaków cudzysłowu na pojedyncze cudzysłowy zmień je na podwójne cudzysłowy.

To jest twój skrypt ze stałym cytowaniem. Skrypt może mieć inne problemy, właśnie naprawiłem składnię.

#!/bin/bash

ssh -t  test@192.168.94.139 'cd /var/www/test.com/backup ;

if [ $(ls  | wc -l) -lt 3  ]
then
  echo "Less"
else [ $(ls -t *.tgz|awk "NR >3"|xargs rm -f) ]
  echo "deleted"
fi'
pt314
źródło
Chcę przypisać /var/www/test.com/backupdo zmiennej o nazwie „BACKUPDEST” i cd $BACKUPDESTczy możesz mi powiedzieć, jak to zrobić?
Janith,
1
Gdzie przypisujesz wartość BACKUPDEST? Jeśli zdarzy się to po stronie zdalnej, po prostu włącz go normalnie do skryptu. Jeśli chcesz ustawić go lokalnie (np. Obliczyć w skrypcie lokalnym lub przekazać jako argument wiersza poleceń), możesz zmienić pierwszy wiersz na np. "cd $BACKUPDEST"' ;- powłoka rozwija część podwójnego cudzysłowu, zachowuje cudzysłowy część nienaruszona, łączy dwa i przekazuje wynik jako ostatni argument do ssh.
pt314
Zrób to, "cd '$BACKUPDEST'"' ;jeśli ścieżka w tej zmiennej zawiera jakieś dziwności (np. Spację). Ponownie: po prostu mówiąc, że można to zrobić w ten sposób, a nie że należy to zrobić w ten sposób.
pt314
10

Tak, możesz wyekstrahować złożone skrypty za pośrednictwem ssh

#!/bin/bash -e

ssh_cmd="$(cat <<-EOF
    cd /var/www/test.com/backup;

    if [ \$(ls  | wc -l) -lt 3  ]; then
        echo "less"
    elif [ \$(ls -t *.tgz|awk 'NR >3'|xargs rm -f) ]; then
        echo "deleted"
    else
        echo "something else"
    fi
EOF
)"

ssh -t test@192.168.94.139 "$ssh_cmd"

W tym przykładzie użyto dokumentu bash tutaj do wygenerowania ciągu poleceń. W każdym razie przekazywanie skryptów przez ssh jest podatne na błędy, ponieważ cytowanie i ucieczka zmiennych jest trudne (zwróć uwagę na odwrotny ukośnik przed poleceniami). Jeśli skrypt staje się zbyt skomplikowany, lepiej go skopiować, scpa następnie uruchomić na hoście docelowym.

Nie próbowałem naprawić skryptu , ale oto przykład, jak może działać liczenie i usuwanie na zdalnym hoście:

#!/bin/bash -e

tmp_dir="$(mktemp -d)"

ssh_cmd="$(cat <<-EOF
    cd "$tmp_dir"

    cnt_tgz=(\$(find . -type f -name "*.tgz"))
    if [[ \${#cnt_tgz[@]} -lt 3 ]]; then
        echo "less"
    else
        rm "\${cnt_tgz[@]}"
        echo "deleted"
    fi
EOF
)"

touch "$tmp_dir/1.tgz"
ssh -t localhost "$ssh_cmd"
touch "$tmp_dir/2.tgz" "$tmp_dir/3.tgz"
ssh -t localhost "$ssh_cmd"

ls -t *.tgzNie będzie działać, ponieważ masek dzieje się tylko w systemie lokalnym. Korzystając również lsdla plików liczenia nie jest dobrym pomysłem, ponieważ również zwraca wpisy jak ., ..i katalogów.

Simon Sudler
źródło
Mam kilka błędów. syntax error near unexpected token elif',elif [ $(ls -t |awk 'NR >3'|xargs rm -f) ]; then'
Janith,
W ;pierwszej instrukcji if brakowało. Ale nie naprawiłem ci skryptu! Jest o wiele więcej problemów, np.ls *.tgz
Simon Sudler
1
„Plik ls -t * .tgz nie będzie działał, ponieważ globowanie odbywa się tylko w systemie lokalnym.” - stanie się to na zdalnym systemie, jeśli odpowiednio go unikniesz. Oryginalna wersja zawierała goły $(...)tekst w cudzysłowie, więc lokalna powłoka go oceniła. Ucieczka od niego, jak \$(...)pokazano w pierwszym przykładzie, lub użycie pojedynczych cudzysłowów wokół całego skryptu, zapobiega rozszerzeniu go przez lokalną powłokę, więc zostaje on wysłany do systemu zdalnego, jest rozszerzony przez zdalną powłokę i skrypt działa jak zamierzono.
pt314
W takiej sytuacji zdecydowanie polecam stosowanie pojedynczych cudzysłowów zamiast dokumentu tutaj. Nawet jeśli masz jakieś zmienne, które chcesz wstawić, lepiej jest użyć ciągu pojedynczego cudzysłowu i printfrozwinąć zmienne - nadal będzie to łatwiejsze do zarządzania niż próba ucieczki od wszystkich zmiennych powłoki w całym tekście. Ponadto uniknęłoby to użycia cattylko do przechwycenia dokumentu tutaj w zmiennej!
Daniel Pryden,
4

Myślę, że całe to skomplikowane cytowanie jest wystarczającym dowodem, aby go nie używać, ale zamiast tego użyć skryptu. Jeśli chcesz uniknąć wielu sshpołączeń, przenieś skrypt do drugiego hosta i pozwól mu działać w jednym poleceniu:

Plik lokalny, powiedz myscript.sh:

cd /var/www/test.com/backup;

if [ $(ls  | wc -l) -lt 3  ]; then
    echo "less"
elif [ $(ls -t *.tgz|awk 'NR >3'|xargs rm -f) ]; then
    echo "deleted"
else
    echo "something else"
fi

Następnie:

cat myscript.sh | \
    ssh -t test@192.168.94.139 \
    "cat - > /tmp/ms.sh && sh /tmp/ms.sh && rm /tmp/ms.sh"

Lub (unikając bezużytecznego użycia kota ):

ssh -t test@192.168.94.139 \
    "cat - > /tmp/ms.sh && sh /tmp/ms.sh && rm /tmp/ms.sh" \
    < myscript.sh

Spowoduje to przeniesienie lokalnego skryptu myscript.shna stronę zdalną, gdzie zostanie przekierowany do (tymczasowego) pliku /tmp/ms.sh, wykonany i ostatecznie usunięty.

Uwaga: nie sprawdziłem oryginalnego skryptu pod kątem błędów, ale chciałem tylko pokazać pomysł. Nie jest konieczne cytowanie podatne na błędy, a wszystkie polecenia w skrypcie są wykonywane po stronie zdalnej.

PerlDuck
źródło
Nie musisz też zapisywać skryptu w pliku tymczasowym, po prostu podłącz go do odpowiedniej powłoki, tj ssh user@host bash <myscript.sh.
pt314
2
Pamiętaj jednak, że przekierowanie działa tylko wtedy, gdy sam skrypt nie próbuje odczytać ze standardowego wejścia.
chepner,
@ pt314 Tak, ale zobacz Przeniesienie skryptu w bash?
PerlDuck,
Tak, przekierowanie ma swoje ograniczenia. Pozwala to również uniknąć szeregu problemów związanych z bezpieczeństwem związanych z pisaniem (a następnie wykonywaniem) pliku tymczasowego o przewidywalnej nazwie. Jeśli używasz tymczasowego skryptu (czy to z wyboru, czy z konieczności), utwórz go za pomocą mktemplub czegoś podobnego bezpiecznego.
pt314
3

Wolałbym umieścić skrypt w zdalnej instancji i po prostu wykonać go za pomocą ssh, ale oto moja sugestia, jak można to zrobić tak, jak chcesz:

#!/bin/bash

HOST='[email protected]'
REMOTE_PATH='/var/www/test.com/backup'

COMMAND1="ssh \"$HOST\" 'ls \"$REMOTE_PATH\" | wc -l'"
COMMAND2="ssh \"$HOST\" 'ls -t \"$REMOTE_PATH\"/*.tgz'"
COMMAND3="xargs ssh \"$HOST\" rm -rf"

if [[ $(eval "$COMMAND1") -le 3 ]]
then
    echo "Less"
else
    eval "$COMMAND2" | awk 'NR > 3' | eval "$COMMAND3" && echo "Deleted"
fi

Uwagi:

  • Wyrażenie warunkowe -ltjest zastępowane przez -le.
  • eval - konstruuj polecenia, łącząc argumenty.
  • Nie jestem pewien, czy naprawdę potrzebujemy tych dodatkowych cytatów \"w $COMMAND{1..3}wyrażeniu, ale postanawiam je dodać.
pa4080
źródło