Jaki jest najczystszy sposób na ssh i uruchamianie wielu poleceń w Bash?

349

Mam już skonfigurowanego agenta ssh i mogę uruchamiać polecenia na serwerze zewnętrznym w skrypcie Bash, wykonując takie czynności jak:

ssh blah_server "ls; pwd;"

Teraz naprawdę chciałbym uruchomić wiele długich poleceń na serwerze zewnętrznym. Umieszczenie wszystkich tych znaków w cudzysłowie byłoby dość brzydkie i naprawdę wolałbym unikać wielokrotnego ssh'owania, aby tego uniknąć.

Czy jest więc sposób, aby to zrobić za jednym razem w nawiasach? Szukam czegoś w stylu:

ssh blah_server (
   ls some_folder;
   ./someaction.sh;
   pwd;
)

Zasadniczo będę zadowolony z każdego rozwiązania, o ile będzie ono czyste.

Edytować

Aby to wyjaśnić, mówię o tym, że jest to część większego skryptu bash. Inni ludzie mogą potrzebować poradzić sobie ze skryptem, więc chciałbym go utrzymać w czystości. Nie chcę mieć skryptu bash z jedną linią, która wygląda następująco:

ssh blah_server "ls some_folder; ./someaction.sh 'some params'; pwd; ./some_other_action 'other params';"

ponieważ jest wyjątkowo brzydki i trudny do odczytania.

Eli
źródło
2
Hmm, co powiesz na umieszczenie tego wszystkiego w skrypcie na serwerze i wywołanie go jednym sshwywołaniem?
Nikolai Fetissov,
@Nikolai jeśli polecenia zależy od strony klienta, mogą być napisane w skrypcie, a następnie scp, sshi uruchomić. Myślę, że to będzie najczystszy sposób.
khachik,
6
Jest to część większego skryptu bash, więc wolałbym nie dzielić go z połową życia na moim komputerze osobistym, a drugą połowę na serwerze i uruchamianie przez ssh. Jeśli to w ogóle możliwe, naprawdę chciałbym zachować to jako jeden skrypt uruchamiany z mojego komputera osobistego. Czy naprawdę nie ma czystego sposobu na umieszczenie wielu poleceń w ssh?
Eli
Najlepszym sposobem jest nie używanie bash, ale Perl, Python, Ruby itp.
salva
1
Dlaczego chcesz unikać wpisywania zdalnych poleceń w cudzysłów? W cudzysłowie możesz umieszczać nowe wiersze, tyle ile chcesz; a użycie ciągu zamiast standardowego wejścia oznacza, że ​​standardowe wejście jest dostępne np. do odczytu wejścia do zdalnego skryptu. (Chociaż w Uniksie, pojedyncze cudzysłowy są zwykle
lepsze

Odpowiedzi:

456

Co powiesz na dokument Bash Here :

ssh otherhost << EOF
  ls some_folder; 
  ./someaction.sh 'some params'
  pwd
  ./some_other_action 'other params'
EOF

Aby uniknąć problemów wspomnianych przez @Globalz w komentarzach, możesz (w zależności od tego, co robisz na zdalnej stronie) uniknąć zamiany pierwszego wiersza na

ssh otherhost /bin/bash << EOF

Pamiętaj, że w dokumencie Tutaj możesz zastępować zmienne, ale możesz mieć problemy z cytowaniem. Na przykład, jeśli cytujesz „limit string” (tj. EOFPowyżej), nie możesz wykonywać podstawień zmiennych. Ale bez cytowania łańcucha limitu zmienne są podstawiane. Na przykład, jeśli zdefiniowałeś $NAMEpowyżej w skrypcie powłoki, możesz to zrobić

ssh otherhost /bin/bash << EOF
touch "/tmp/${NAME}"
EOF

i utworzyłby plik w miejscu docelowym otherhost z nazwą tego, do czego został przypisany $NAME. Obowiązują również inne zasady dotyczące cytowania skryptów powłoki, ale są zbyt skomplikowane, aby przejść tutaj.

Paul Tomblin
źródło
6
To wygląda dokładnie tak, jak chcę! Jak to działa Czy miałbyś link do strony, która to wyjaśnia?
Eli
9
+1 Właśnie myślałem, że sam - oto jedno miejsce, aby o tym przeczytać: tldp.org/LDP/abs/html/here-docs.html
bosmacs
14
Dostaję również ten wynik do mojego lokalnego: Pseudo-terminal nie zostanie przydzielony, ponieważ standardowe wejście nie jest terminalem.
Globalz,
14
Może być ważne, aby zacytować to słowo (tj. Pierwsze „EOF”), aby zapobiec rozszerzaniu się linii poleceń. Zobacz: man bash | mniej + / „Here Documents”
zebranie
6
Paul, sugeruję to tylko dlatego, że przy prawie 200 tysiącach wyświetleń tego pytania, wygląda na to, że wiele osób przyjeżdża tutaj podczas pisania skryptów za pomocą ssh. Często trzeba wstrzykiwać wartości podczas pisania skryptu. Gdyby nie hałas w innych pytaniach i komentarzach, zrobiłbym jedno i byłbym w drodze, ale na tym etapie raczej nie będzie go widać. Przypis w jednym wierszu może uratować ludzi przed poważnymi bólami głowy.
koyae
122

Edytuj skrypt lokalnie, a następnie potokuj go do ssh, np

cat commands-to-execute-remotely.sh | ssh blah_server

gdzie commands-to-execute-remotely.shwygląda jak twoja lista powyżej:

ls some_folder
./someaction.sh
pwd;
bosmacs
źródło
7
Ma to tę wielką zaletę, że dokładnie wiesz , co jest wykonywane przez skrypt zdalny - bez problemów z cytowaniem. Jeśli potrzebujesz dynamicznych poleceń, możesz użyć skryptu powłoki z podpowłoką, nadal ( echo $mycmd $myvar ; ...) | ssh myhostprzesyłającego potoki do ssh, tj. - podobnie jak w przypadku użycia cat, wiesz dokładnie, co dzieje się w strumieniu poleceń ssh. I oczywiście podpowłoka w skrypcie może być wielowierszowa dla czytelności - patrz linuxjournal.com/content/bash-sub-shells
RichVel
6
Można to zrobić z argumentami w commands-to-execute-remotely.sh?
elaRosca
1
Myślę, że jest to o wiele bardziej przydatne niż rozwiązanie paultomblin. Ponieważ skrypt można przekierować na dowolny serwer ssh bez konieczności otwierania pliku. Jest też znacznie bardziej czytelny.
Patrick Bassut
3
tak, ale używając echa lub dokumentu tutaj (patrz odpowiedź na górę): użyj: $localvardo interpretacji zmiennej zdefiniowanej lokalnie, \$remotevardo zdalnej interpretacji zmiennej zdefiniowanej zdalnie, \$(something with optionnal args)aby uzyskać wynik działania czegoś na zdalnym serwerze. Przykład, że możesz ssh przez 1 (lub, jak pokazano tutaj, wiele poleceń ssh):echo " for remotedir in /*/${localprefix}* ; do cd \"\$remotedir\" && echo \"I am now in \$(pwd) on the remote server \$(hostname) \" ; done " | ssh user1@hop1 ssh user2@hop2 ssh user@finalserver bash
Olivier Dulac
2
Hmm ... czym to się różni od używania przekierowania wejściowego, tj. ssh blah_server < commands-to-execute-remotely.sh?
flow2k
41

Aby dopasować swój przykładowy kod, możesz zawinąć swoje polecenia w pojedyncze lub podwójne polecenia qoute. Na przykład

ssh blah_server "
  ls
  pwd
"
Andrei B.
źródło
Podoba mi się ten format, ale niestety nie jest przydatny do przechowywania standardowych danych w zmiennej.
Signus,
1
Signus, co rozumiesz przez „przechowywanie standardowych danych w zmiennej”?
Andrei B
1
@Signus Zupełnie możliwe jest robienie tego, co opisujesz, chociaż prawdopodobnie będziesz chciał użyć pojedynczych cudzysłowów zamiast podwójnych cudzysłowów wokół poleceń zdalnych (lub uniknij operatorów, które muszą być poprzedzone podwójnymi cudzysłowami, aby zapobiec przechwyceniu przez lokalną powłokę i interpolując je).
tripleee
Nie jestem w stanie wstawić w cudzysłowie kodu takiego polecenia fileToRemove = $ (find. -Type f -name 'xxx.war'). fileToRemove powinien mieć wewnątrz nazwę pliku, ale zamiast tego ma pusty ciąg. Czy coś trzeba uciec?
jkonst
37

Widzę dwa sposoby:

Najpierw utwórz gniazdo kontrolne w ten sposób:

 ssh -oControlMaster=yes -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip>

i uruchom swoje polecenia

 ssh -oControlMaster=no -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip> -t <yourcommand>

W ten sposób możesz napisać polecenie ssh bez ponownego łączenia się z serwerem.

Drugim byłoby dynamiczne wygenerowanie skryptu, scpuruchomienie go i uruchomienie.

stacja końcowa
źródło
20

Można to również zrobić w następujący sposób. Umieść swoje polecenia w skrypcie, nazwijmy go polecenia-inc.sh

#!/bin/bash
ls some_folder
./someaction.sh
pwd

Zapisz plik

Teraz uruchom go na zdalnym serwerze.

ssh user@remote 'bash -s' < /path/to/commands-inc.sh

Nigdy dla mnie nie zawiodło.

RJ
źródło
Podobne do tego, co pierwotnie myślałem! Ale dlaczego jest bash -spotrzebny?
flow2k
Czy #!/bin/bashnaprawdę jest używany?
flow2k
2
Opcja -s zapewnia zgodność. Z man bash -s Jeśli opcja -s jest obecna lub po przetworzeniu opcji nie pozostały żadne argumenty, polecenia są odczytywane ze standardowego wejścia. Ta opcja umożliwia ustawienie parametrów pozycyjnych podczas wywoływania powłoki interaktywnej.
RJ
1
RJ, dzięki! Z moim pierwszym komentarzem wygląda na to, że po prostu to robimy ssh user@remote < /path/to/commands-inc.sh(wydaje mi się, że działa). Sądzę, że twoja wersja zapewnia, że ​​używamy bashpowłoki, a nie jakiejkolwiek innej powłoki - taki jest cel, prawda?
flow2k
2
Dzięki. Tak, niektórzy z nas mają swoje notatki i nawyki ze starszych wersji bash i innych powłok. :-)
RJ
10

Umieść wszystkie polecenia w skrypcie i można go uruchomić tak jak

ssh <remote-user>@<remote-host> "bash -s" <./remote-commands.sh
Jai Prakash
źródło
Nieco dziwne, ale działa dobrze. Argumenty mogą być przekazywane do skryptu (przed lub po przekierowaniu). np. „ssh <użytkownik-zdalny> @ <host-zdalny>„ bash -s ”arg1 <./ remote-commands.sh arg2” np. „ssh <użytkownik-zdalny> @ <host-zdalny>„ bash -s ”- - -arg1 <./ remote-commands.sh arg2 '
gaoithe
Miałem podobne pytanie z inną odpowiedzią powyżej, ale jaki jest cel włączenia bash -s?
flow2k
1
@ flow2k -s oznacza, że ​​bash odczytuje polecenia ze standardowego wejścia. Oto opis ze manstronIf the -s option is present, or if no arguments remain after option processing, then commands are read from the standard input. This option allows the positional parameters to be set when invoking an interactive shell.
Jai Prakash
@JaiPrakash Dzięki za to, ale tak naprawdę myślałem, czy możemy po prostu bashcałkowicie zrezygnować z polecenia, tj ssh remote-user@remote-host <./remote-commands.sh. Próbowałem swoich sił i wydawało się, że to działa, ale nie jestem pewien, czy to w jakiś sposób jest wadliwe.
flow2k
@ flow2k z mojego zrozumienia stron podręcznika nie będzie żadnych braków bez tej opcji. Jest to wymagane, jeśli próbujesz przekazać jakiekolwiek argumenty. - dzięki
Jai Prakash
9

SSH i uruchamianie wielu poleceń w Bash.

Oddziel polecenia średnikami w ciągu, przekazywane do echa, wszystkie przesyłane do polecenia ssh. Na przykład:

echo "df -k;uname -a" | ssh 192.168.79.134

Pseudo-terminal will not be allocated because stdin is not a terminal.
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/sda2       18274628 2546476  14799848  15% /
tmpfs             183620      72    183548   1% /dev/shm
/dev/sda1         297485   39074    243051  14% /boot
Linux newserv 2.6.32-431.el6.x86_64 #1 SMP Sun Nov 10 22:19:54 EST 2013 x86_64 x86_64 x86_64 GNU/Linux
arnab
źródło
arnab, w przyszłości opisz krótko, co robi twój kod. Następnie porozmawiaj o tym, jak to działa, a następnie wprowadź kod. Następnie umieść kod w bloku kodu, aby był łatwy do odczytania.
Eric Leschinski
Z pytania podałeś dokładnie to , czego OP chce uniknąć: „Nie chcę mieć skryptu bash z jedną linią, która wygląda jak ...”
jww
6

Dla każdego, kto potknie się tutaj, jak ja - udało mi się uciec przed średnikiem i znakiem nowej linii:

Pierwszy krok: średnik. W ten sposób nie łamiemy polecenia ssh:

ssh <host> echo test\;ls
                    ^ backslash!

Wymieniono zdalny katalog hosts / home (zalogowany jako root), podczas gdy

ssh <host> echo test;ls
                    ^ NO backslash

wymieniono bieżący katalog roboczy.

Następny krok: zerwanie linii:

                      v another backslash!
ssh <host> echo test\;\
ls

To ponownie wyświetlało zdalny katalog roboczy - ulepszone formatowanie:

ssh <host>\
  echo test\;\
  ls

Jeśli naprawdę ładniejszy niż tutaj dokument lub cytaty wokół linii przerywanych - cóż, nie ja decyduję ...

(Używając bash, Ubuntu 14.04 LTS.)

Aconcagua
źródło
1
Co jeszcze lepszego możesz używać && i || W ten sposób, za - echo test \ & \ & ls
Miro Kropáček
@MiroKropacek To też jest miłe. Lepszy? Zależy od tego, czego chcesz; uruchom drugie polecenie warunkowo , a następnie tak, uruchom je bezwarunkowo (bez względu na to, czy pierwsze się powiedzie, czy nie), a następnie nie ...
Aconcagua
@MiroKropacek, nie musiałem uciekać przed &&, umieszczając podwójne cudzysłowy wokół poleceń:ssh host "echo test && ls"
not2savvy
5

Wysłane odpowiedzi przy użyciu ciągów wielowierszowych i wielu skryptów bash nie działały dla mnie.

  • Długie wielowierszowe struny są trudne do utrzymania.
  • Oddzielne skrypty bash nie utrzymują zmiennych lokalnych.

Oto funkcjonalny sposób ssh i uruchamiania wielu poleceń przy zachowaniu lokalnego kontekstu.

LOCAL_VARIABLE=test

run_remote() {
    echo "$LOCAL_VARIABLE"
    ls some_folder; 
    ./someaction.sh 'some params'
    ./some_other_action 'other params'
}

ssh otherhost "$(set); run_remote"
Michael Petrochuk
źródło
3
Myślicie o tym tak, jakby jedynym powodem, dla którego ktoś chciałby to zrobić, jest siedzenie w linii poleceń. Istnieją również inne typowe przyczyny tego pytania, takie jak wykonywanie poleceń w środowiskach rozproszonych za pomocą narzędzi takich jak rundeck, jenkins itp.
Charles Addis
to działa, ale otrzymuję niechciane komunikaty o błędach
Annahri
5

Nie jestem pewien, czy najczystszy z długich poleceń, ale z pewnością najłatwiejszy:

ssh user@host "cmd1; cmd2; cmd3"
DimiDak
źródło
Najlepsze w prostocie!
not2savvy
4

Działa to dobrze przy tworzeniu skryptów, ponieważ nie musisz dołączać innych plików:

#!/bin/bash
ssh <my_user>@<my_host> "bash -s" << EOF
    # here you just type all your commmands, as you can see, i.e.
    touch /tmp/test1;
    touch /tmp/test2;
    touch /tmp/test3;
EOF
sjas
źródło
Część „$ (which bash) -s” podaje lokalizację bash na komputerze lokalnym, a nie na komputerze zdalnym. Myślę, że zamiast tego potrzebujesz „$ (który bash) -s” (pojedyncze cudzysłowy, aby stłumić podstawianie parametrów lokalnych).
jesears,
1
Paul Tomblin udzielił odpowiedzi Bash Here Document 6 lat wcześniej. Jaką wartość dodaje ta odpowiedź?
jww
-sflagę, cytuję go , być może uda ci się zastąpić pierwszą linię ... i nie byłem w stanie uciec od zwykłego wywołania bashu, którego ledwie pamiętam.
sjas,
4

Najłatwiejszym sposobem skonfigurowania systemu do domyślnego korzystania z pojedynczych sesji ssh z multipleksowaniem.

Można to zrobić, tworząc folder dla gniazd:

mkdir ~/.ssh/controlmasters

A następnie dodając następujące elementy do konfiguracji .ssh:

Host *
    ControlMaster auto
    ControlPath ~/.ssh/controlmasters/%r@%h:%p.socket
    ControlMaster auto
    ControlPersist 10m

Teraz nie musisz modyfikować żadnego kodu. Pozwala to na wiele wywołań ssh i scp bez tworzenia wielu sesji, co jest przydatne, gdy zachodzi potrzeba większej interakcji między komputerami lokalnymi i zdalnymi.

Dzięki odpowiedzi @ terminus, http://www.cyberciti.biz/faq/linux-unix-osx-bsd-ssh-multiplexing-to-speed-up-ssh-connections/ oraz https://en.wikibooks.org / wiki / OpenSSH / Cookbook / Multiplexing .

Jonathan
źródło