[ -z "$PS1" ] && return
sshb() {
scp ~/.bashrc ${1}:
ssh $1
}
# the rest of the .bashrc
alias c=cat
...
Dies ist ein sehr naiver Weg mit mehreren offensichtlichen Nachteilen:
- Sie können eine vorhandene .bashrc überschreiben
- Anstelle einer Verbindung stellen wir 2 her
- Infolgedessen müssen Sie sich auch zweimal anmelden.
- Das Funktionsargument kann nur die Adresse des Remote-Computers sein
Verbesserte Option:
[ -z "$PS1" ] && return
sshb() {
local ssh="ssh -S ~/.ssh/control-socket-$(tr -cd '[:alnum:]' < /dev/urandom|head -c8)"
$ssh -fNM "$@"
$ssh placeholder "cat >~/.bash-ssh" <~/.bashrc
$ssh "$@" -t "bash --rcfile ~/.bash-ssh -i"
$ssh placeholder -O exit >/dev/null 2>&1
}
# the rest of the .bashrc
alias c=cat
...
Jetzt verwenden wir nur eine Verbindung durch Multiplexen. .bashrc wird in eine Datei kopiert, die standardmäßig nicht von bash verwendet wird, und wir geben sie explizit über die Option --rcfile an. Das Funktionsargument kann nicht nur die Adresse des Remote-Computers sein, sondern auch andere ssh-Optionen.
Im Prinzip könnte man damit aufhören, aber die resultierende Lösung hat einen unangenehmen Nachteil. Wenn Sie screen oder tmux ausführen, wird die .bashrc-Datei auf dem Remotecomputer verwendet und alle Ihre Aliase und Funktionen gehen verloren. Glücklicherweise kann dies überwunden werden. Dazu müssen wir ein Wrapper-Skript erstellen, das wir als unsere neue Shell deklarieren. Nehmen wir der Einfachheit halber an, dass wir bereits ein Wrapper-Skript auf dem Remote-Computer haben und es sich in ~ / bin / bash-ssh befindet. Das Skript sieht folgendermaßen aus:
#!/bin/bash
exec /bin/bash --rcfile ~/.bash-ssh “$@”
Und .bashrc so:
[ -n "$SSH_TTY" ] && export SHELL="$HOME/bin/bash-ssh"
[ -z "$PS1" ] && return
sshb() {
local ssh="ssh -S ~/.ssh/control-socket-$(tr -cd '[:alnum:]' < /dev/urandom|head -c8)"
$ssh -fNM "$@"
$ssh placeholder "cat >~/.bash-ssh" <~/.bashrc
$ssh "$@" -t "bash --rcfile ~/.bash-ssh -i"
$ssh placeholder -O exit >/dev/null 2>&1
}
# the rest of the .bashrc
alias c=cat
...
Wenn die Variable SSH_TTY vorhanden ist, verstehen wir, dass wir uns auf dem Remotecomputer befinden, und überschreiben die Variable SHELL. Von diesem Moment an, wenn eine neue interaktive Shell gestartet wird, wird ein Skript gestartet, das Bash mit einer nicht standardmäßigen Konfiguration startet, die beim Einrichten einer SSH-Sitzung gespeichert wurde.
Um eine bequeme Arbeitslösung zu erhalten, müssen Sie noch herausfinden, wie Sie ein Wrapper-Skript auf einem Remotecomputer erstellen. Im Prinzip können Sie es in der Bash-Konfiguration erstellen, die wir wie folgt speichern:
[ -n "$SSH_TTY" ] && {
mkdir -p "$HOME/bin"
export SHELL="$HOME/bin/bash-ssh"
echo -e '#!/bin/bash\nexec /bin/bash --rcfile ~/.bash-ssh "$@"' >$SHELL
chmod +x $SHELL
}
Tatsächlich können Sie jedoch mit einer einzigen ~ / .bash-ssh-Datei auskommen:
#!/bin/bash
[ -n "$SSH_TTY" ] && [ "${BASH_SOURCE[0]}" == "${0}" ] && exec bash --rcfile "$SHELL" "$@"
[ -z "$PS1" ] && return
sshb() {
local ssh="ssh -S ~/.ssh/control-socket-$(tr -cd '[:alnum:]' < /dev/urandom|head -c8)"
$ssh -fNM "$@"
$ssh placeholder "cat >~/.bash-ssh" <~/.bashrc
$ssh "$@" -t 'SHELL=~/.bash-ssh; chmod +x $SHELL; bash --rcfile $SHELL -i'
$ssh placeholder -O exit >/dev/null 2>&1
}
# the rest of the .bashrc
alias c=cat
...
Jetzt ist die Datei ~ / .bash-ssh sowohl ein eigenständiges Skript als auch eine Bash-Konfiguration. Es funktioniert so. Auf dem lokalen Computer werden Befehle nach [-n "$ SSH_TTY"] ignoriert. Auf dem Remotecomputer erstellt die sshb-Funktion eine ~ / .bash-ssh-Datei und verwendet sie als Konfiguration zum Starten einer interaktiven Sitzung. Mit der Konstruktion ["$ {BASH_SOURCE [0]}" == "$ {0}"] können Sie bestimmen, ob eine Datei von einem anderen Skript hochgeladen oder als eigenständiges Skript gestartet wird. Infolgedessen, wenn ~ / .bash-ssh verwendet wird
- as config - exec wird ignoriert
- Als Skript wird die Steuerung an die Bash übergeben und die Ausführung von ~ / .bash-ssh endet mit der Ausführung.
Wenn Sie jetzt eine Verbindung über ssh herstellen, sieht Ihre Umgebung überall gleich aus. Es ist viel bequemer, auf diese Weise zu arbeiten, aber der Verlauf der Befehlsausführung bleibt auf den Computern erhalten, mit denen Sie verbunden sind. Persönlich möchte ich die Geschichte lokal speichern, damit ich genau auffrischen kann, was ich in der Vergangenheit auf einigen Maschinen getan habe. Dazu benötigen wir folgende Komponenten:
- TCP-Server auf dem lokalen Computer, der Daten von einem Socket empfängt und in eine Datei umleitet
- Leiten Sie den Überwachungsport dieses Servers an den Computer weiter, mit dem wir über ssh eine Verbindung herstellen
- PROMPT_COMMAND in den Bash-Einstellungen, die nach Abschluss des Befehls eine Verlaufsaktualisierung an den weitergeleiteten Port senden
Dies kann folgendermaßen geschehen:
#!/bin/bash
[ -n "$SSH_TTY" ] && [ "${BASH_SOURCE[0]}" == "${0}" ] && exec bash --rcfile "$SHELL" "$@"
[ -z "$PS1" ] && return
[ -z "$SSH_TTY" ] && {
history_port=26574
netstat -lnt|grep -q ":${history_port}\b" || {
umask 077 && nc -kl 127.0.0.1 "$history_port" >>~/.bash_eternal_history &
}
}
HISTSIZE=$((1024 * 1024))
HISTFILESIZE=$HISTSIZE
HISTTIMEFORMAT='%t%F %T%t'
update_eternal_history() {
local histfile_size=$(stat -c %s $HISTFILE)
history -a
((histfile_size == $(stat -c %s $HISTFILE))) && return
local history_line="${USER}\t${HOSTNAME}\t${PWD}\t$(history 1)"
local history_sink=$(readlink ~/.bash-ssh.history 2>/dev/null)
[ -n "$history_sink" ] && echo -e "$history_line" >"$history_sink" 2>/dev/null && return
local old_umask=$(umask)
umask 077
echo -e "$history_line" >> ~/.bash_eternal_history
umask $old_umask
}
[[ "$PROMPT_COMMAND" == *update_eternal_history* ]] || export PROMPT_COMMAND="update_eternal_history;$PROMPT_COMMAND"
sshb() {
local ssh="ssh -S ~/.ssh/control-socket-$(tr -cd '[:alnum:]' < /dev/urandom|head -c8)"
$ssh -fNM "$@"
local bashrc=~/.bashrc
[ -r ~/.bash-ssh ] && bashrc=~/.bash-ssh && history_port=$(basename $(readlink ~/.bash-ssh.history))
local history_remote_port="$($ssh -O forward -R 0:127.0.0.1:$history_port placeholder)"
$ssh placeholder "cat >~/.bash-ssh; ln -nsf /dev/tcp/127.0.0.1/$history_remote_port ~/.bash-ssh.history" < $bashrc
$ssh "$@" -t 'SHELL=~/.bash-ssh; chmod +x $SHELL; bash --rcfile $SHELL -i'
$ssh placeholder -O exit >/dev/null 2>&1
}
# the rest of the .bashrc
alias c=cat
...
Der Block nach [-z "$ SSH_TTY"] funktioniert nur auf dem lokalen Computer. Wir prüfen, ob der Port belegt ist, und führen Netcat darauf aus, dessen Ausgabe in eine Datei umgeleitet wird.
Die Funktion update_eternal_history wird unmittelbar vor der Anzeige der Bash-Eingabeaufforderung aufgerufen. Diese Funktion prüft, ob der letzte Befehl ein Duplikat war, und sendet ihn andernfalls an den weitergeleiteten Port. Wenn der Port nicht konfiguriert ist (im Fall eines lokalen Computers) oder wenn beim Senden ein Fehler aufgetreten ist, wird das Speichern in eine lokale Datei verschoben.
Die sshb-Funktion wurde ergänzt, indem die Portweiterleitung festgelegt und ein Symlink erstellt wurde, der von update_eternal_history zum Senden von Daten an den Server verwendet wird.
Diese Lösung ist nicht ohne Nachteile:
- Port für Netcat ist fest codiert, es besteht die Möglichkeit, dass Konflikte auftreten
- ( - - ), , ,
Meine eigene .bashrc kann hier angesehen werden .
Wenn Sie Ideen zur Verbesserung der vorgeschlagenen Lösung haben, teilen Sie diese bitte in den Kommentaren mit.
Aktualisieren. Unter Ubuntu 16.04 stieß ich auf ein Problem: Netcat friert bei mehreren Verbindungen ein und belegt 100% CPU. Ich wechselte zu socat, vorläufige Tests zeigten, dass alles in Ordnung ist. Außerdem wurde eine Logik zum Verwalten des Symlinks hinzugefügt, die die Adresse bestimmt, an die der Verlauf gesendet wird. Es stellte sich so heraus:
#!/bin/bash
[ -n "$SSH_TTY" ] && [ "${BASH_SOURCE[0]}" == "${0}" ] && exec bash --rcfile "$SHELL" "$@"
[ -z "$PS1" ] && return
[ -z "$SSH_TTY" ] && command -v socat >/dev/null && {
history_port=26574
netstat -lnt|grep -q ":${history_port}\b" || {
umask 077 && socat -u TCP4-LISTEN:$history_port,bind=127.0.0.1,reuseaddr,fork OPEN:$HOME/.bash_eternal_history,creat,append &
}
}
HISTSIZE=$((1024 * 1024))
HISTFILESIZE=$HISTSIZE
HISTTIMEFORMAT='%t%F %T%t'
update_eternal_history() {
local histfile_size=$(stat -c %s $HISTFILE)
history -a
((histfile_size == $(stat -c %s $HISTFILE))) && return
local history_line="${USER}\t${HOSTNAME}\t${PWD}\t$(history 1)"
local history_sink=$(readlink ~/.bash-ssh.history 2>/dev/null)
[ -n "$history_sink" ] && echo -e "$history_line" >"$history_sink" 2>/dev/null && return
local old_umask=$(umask)
umask 077
echo -e "$history_line" >> ~/.bash_eternal_history
umask $old_umask
}
[[ "$PROMPT_COMMAND" == *update_eternal_history* ]] || PROMPT_COMMAND="update_eternal_history;$PROMPT_COMMAND"
sshb() {
local ssh="ssh -S ~/.ssh/control-socket-$(tr -cd '[:alnum:]' < /dev/urandom|head -c8)"
local bashrc=~/.bashrc
local history_command="rm -f ~/.bash-ssh.history"
[ -r ~/.bash-ssh ] && bashrc=~/.bash-ssh && history_port=$(basename $(readlink ~/.bash-ssh.history 2>/dev/null))
$ssh -fNM "$@"
[ -n "$history_port" ] && {
local history_remote_port="$($ssh -O forward -R 0:127.0.0.1:$history_port placeholder)"
history_command="ln -nsf /dev/tcp/127.0.0.1/$history_remote_port ~/.bash-ssh.history"
}
$ssh placeholder "${history_command}; cat >~/.bash-ssh" < $bashrc
$ssh "$@" -t 'SHELL=~/.bash-ssh; chmod +x $SHELL; bash --rcfile $SHELL -i'
$ssh placeholder -O exit >/dev/null 2>&1
}
# the rest of the .bashrc
alias c=cat
...