Bash: invite à distance interactive

16

J'ai un script qui se connecte à un serveur distant et vérifie si un paquet est installé:

ssh root@server 'bash -s' < myscript.sh

myscript.sh:

OUT=`rpm -qa | grep ntpdate`
if [ "$OUT" != "" ] ; then
    echo "ntpdate already installed"
else
    yum install $1
fi

Cet exemple pourrait être simplifié. Voici myscript2.shqui a le même problème:

read -p "Package is not installed. Do you want to install it (y/n)?" choise

Mon problème est que bash ne peut pas lire mes réponses de manière interactive.

Existe-t-il un moyen d'exécuter un script local à distance sans perdre la capacité d'inviter l'utilisateur?

Anthony Ananich
la source
Peux-tu élaborer? Vous ne savez pas vraiment ce que vous demandez? Un peu plus de votre code serait utile.
slm
1
Juste un FYI, la raison pour laquelle cela ne fonctionne pas est parce que vous passez votre script sur STDIN. Ainsi, lorsque bash va lire à partir de STDIN, il obtient votre script (ou rien car il a déjà lu tout le script et il n'y a plus rien).
Patrick
1
Ressource utile à ce sujet: backreference.org/2011/08/10/…
slm

Réponses:

22

Essayez quelque chose comme ceci:

$ ssh -t yourserver "$(<your_script)"

Le -tforce une allocation tty, $(<your_script)lit le fichier entier et dans ce cas passe le contenu en un seul argument ssh, qui sera exécuté par le shell de l'utilisateur distant.

Si le script a besoin de paramètres, passez-les après le script:

$ ssh -t yourserver "$(<your_script)" arg1 arg2 ...

Fonctionne pour moi, je ne sais pas si c'est universel cependant.

Tapis
la source
3
C'est exactement ce que je cherchais, merci!
Anthony Ananich
Belle solution, mais existe-t-il un moyen de passer un argument à your script, tout en conservant les avantages de la syntaxe que vous avez utilisée dans votre réponse? Utiliser ssh -t yourserver "$(<your_script --some_arg)"uniquement des résultats bash: --some_arg: command not found.
sampablokuper
1
@sampablokuper: tryssh ... $(<script) script_arg1 script_arg2 ...
Mat
@Mat, merci, WFM :) Peut-être l'ajouter au corps de votre réponse?
sampablokuper
3

Votre problème est que sshdémarre un shell de connexion non interactif sur la machine distante. La solution évidente serait de copier le script sur le serveur distant et de l'exécuter à partir de là:

scp myscript.sh root@server:/tmp && ssh root@server /tmp/myscript.sh

Si la copie n'est pas une option pour une raison quelconque, je modifierais le script pour d'abord me connecter et vérifier s'il $1est installé, puis reconnecter et installer si nécessaire:

OUT=$(ssh root@server rpm -qa | grep "$1");
if [ "$OUT" != "" ] ; then
    echo "$1 already installed"
else
   read -p "Package $1 is not installed. Do you want to install it (y/n)?" choice
   if [ "$choice" -eq "y" ]; then
       ssh root@server yum install "$1"
   fi
fi
terdon
la source
Voilà ce que j'avais peur. La connexion à ssh est l'opération la plus longue et j'essayais de l'éviter.
Anthony Ananich
@AnthonyAnanich Vous pouvez économiser l'heure d' établissement de la connexion en configurant une connexion principale .
Gilles 'SO- arrête d'être méchant'
@Gilles Je pensais suggérer cela, mais je pensais que cela ne fonctionnerait pas avec ma suggestion. Si la 1ère connexion ssh fait office de maître, elle sera fermée après les $OUT=$(ssh root@server rpm -qa | grep "$1");sorties puis la 2ème connexion prendra autant de temps que la première. Ai-je tort?
terdon
1
@terdon Exécutez quelque chose comme ssh -M foo.example.com sleep 99999999ou en ssh -M foo.example.com read <somefifotant que maître et tuez-le explicitement (avec killou echo done >somefifo) lorsque vous avez terminé.
Gilles 'SO- arrête d'être méchant'
0

Voici une bonne explication .

J'ai donc adapté le script

hostname
echo -n "Make your choice :"
read choice
echo "You typed " ${choice}
echo done

et cela n'a pas fonctionné.

j'ai donc déplacé le script vers la télécommande pour éviter la redirection locale sur ssh. (Mes commandes sont dans un fichier nommé f )

cat f | ssh user@remotehost.com 'cat >remf'
ssh user@remotehost bash remf

Cela a fonctionné. Voici la sortie:

christian@clafujiu:~/tmp$ ssh localhost bash tmp/f
christian@localhost's password: 
Linux clafujiu 2.6.32-52-generic #114-Ubuntu SMP Wed Sep 11 19:00:15 UTC 2013 i686 GNU/Linux
Sun Nov 10 14:58:56 GMT 2013
Make your choice :abc
You typed  abc
done

Comme @terdon a mentionné que l'intention d'origine était d'exécuter des scripts locaux à distance, la copie à distance peut être automatisée, ce n'est qu'un exemple sur une seule ligne.

REMID=`cat f |ssh user@remotehost 'cat > remf_$$; echo $$'` ;ssh root@redtoadservices.com "bash remf_${REMID} ; rm -v remf_${REMID}"
X Tian
la source
2
Comment cela a-t-il "fonctionné"? Avez-vous exécuté ce script avec bash -s < script.shcomme l'OP l'a fait? Pourriez-vous inclure l'explication dans votre réponse au lieu de vous y relier?
terdon
Oups, désolé d'avoir oublié d'ajouter quelque chose de critique. J'ai également copié le script sur la télécommande pour éviter la redirection locale. Je vais ajouter ceci à ma réponse pour que ce soit clair.
X Tian
1
Si vous copiez le script sur la télécommande, le problème disparaît. Le problème du PO est qu'il essaie de fournir une entrée interactive à un script local exécuté à distance. Bien sûr, vous allez le copier. L'OP souhaite exécuter ce script lors de la connexion à un serveur distant, vraisemblablement à de nombreux serveurs distants. Je doute que la recopier soit pratique, sauf si vous l'automatisez.
terdon
0

J'ai cherché des solutions à ce problème à plusieurs reprises dans le passé, mais je n'ai jamais trouvé de solution pleinement satisfaisante. Tuyauter dans ssh perd votre interactivité. Deux connexions (scp / ssh) sont plus lentes et votre fichier temporaire peut être laissé traîner. Et tout le script sur la ligne de commande finit souvent par s'échapper de l'enfer.

Récemment, j'ai rencontré que la taille du tampon de ligne de commande est généralement assez grande ('getconf ARG_MAX> 2 Mo où j'ai regardé). Et cela m'a fait réfléchir sur la façon dont je pourrais utiliser cela et atténuer le problème d'évasion.

Le résultat est:

ssh -t <host> /bin/bash "<(echo "$(cat my_script | base64 | tr -d '\n')" | base64 --decode)" <arg1> ...

ou en utilisant un document et un chat ici:

ssh -t <host> /bin/bash $'<(cat<<_ | base64 --decode\n'$(cat my_script | base64)$'\n_\n)' <arg1> ...

J'ai développé cette idée pour produire un exemple de script BASH pleinement fonctionnel sshxqui peut exécuter des scripts arbitraires (pas seulement BASH), où les arguments peuvent également être des fichiers d'entrée locaux, sur ssh. Voyez ici .

SourceSimian
la source
Intéressant, mais comment et / ou quand est-ce mieux que la réponse de Mat ?
Scott
1
La réponse de Mat est une bonne première passe (que j'utilise souvent), mais elle ne peut pas gérer un script de contenu arbitraire, comme des "caractères qui devront être échappés. Et il est également limité aux scripts BASH. Ma solution est un cas général pour tout contenu de script de n'importe quel langage de script installé. De plus, dans sshx, je démontre davantage les fichiers d'arguments de sérialisation, ce qui est assez astucieux.
SourceSimian
(1) Pouvez-vous publier un MCVE d'un script pour lequel la réponse de Mat échoue (et la vôtre fonctionne)? (2) Voir Qu'est-ce qui ne va pas avec "echo $ (stuff)" ou "echo` stuff` "?  Votre echo "$(cat my_script | base64)" | base64 --decodeapparence est équivalente à (-ish) cat my_script | base64 | base64 --decode, ce qui ressemble beaucoup à un no-op.
Scott
Salut @Scott. (1): Considérez le script: echo "ARG1=$1, USER=$USER, END". a) $ ssh host "$(<my_bash)" foo-> ARG1=, USER=user, END foo. b) $ ssh host bash "<(echo $(cat my_bash|base64) | base64 --decode)" foo-> ARG1=foo, USER=user, END. Ici (a) fonctionne efficacement: bash -c $'#!/bin/bash\necho "ARG1=$1, USER=$USER, END" foo'ou quelque chose de similaire. Notez que l'argument ne fonctionne pas. Où (b) est en cours d' exécution: bash <(echo IyEvY...5EIgo= | base64 --decode) foo. (2) J'ai utilisé base64 comme codage de transport.
SourceSimian