'iTerm2: How can I trigger a local command from a remote session?

iTerm2 shell integration has some neat tricks, such as its it2copy command, which copies into the local clipboard, even if I'm logged into a remote machine via ssh.

Can it be used to run arbitrary shell commands?

For instance, while I'm logged in over ssh, I want to execute a command to open an editor on my local machine. VSCode can open a remote directory with this command:

code --remote ssh-remote+myserver /home/stuart/some-directory

I want to trigger that command locally, from an ssh session on the remote machine.


PS -- I know there's an alternative: Create a (nested) ssh connection back to my local machine to execute the commands over ssh, rather than using iTerm2's backchannel. But that has various downsides, hence this question.

I'm also aware of the PermitLocalCommand option in ~/.ssh/config, which allows me to send an escape code (~C), followed by a local command (!code --remote ...). But I'm hoping for a solution I can use in a script or bash alias.

For instance, if it2local existed, I would use it like this:

alias code_here='it2local "code --remote ssh-remote+$(uname -n) $(pwd)"'

If that is possible with ssh alone, I'd love to hear about it.



Solution 1:[1]

The proper way to do this is via iTerm2 Triggers, which can run an arbitrary command (among other options) whenever a specific pattern appears in your terminal output.

The hypothetical it2local command I described above would just have to echo some predefined trigger pattern to your terminal, along with the command you want to execute.

In my case, I didn't implement the generic it2local command. (Maybe I'll update this answer later.) For now, I've implemented a script that serves my specific use-case: Opening a remote file with VSCode. The code I'm using is shown below.

#!/bin/sh

#
# This file contains the code and instructions to set up an iTerm2 "Trigger" from a
# remote ssh session that will open up VSCode on your local machine to edit a
# file on the remote server over ssh.
#
# Author: Stuart Berg
#         https://github.com/stuarteberg
#         [email protected]
#         https://stackoverflow.com/questions/61699447

# SETUP OVERVIEW
# --------------
# - Install the VS Code Remote Development Extension Pack
# - Ideally, setup passwordless ssh access to the remote machines you want to access
# - Place this script somewhere on your local machine (and make sure it's executable).
# - Copy the localcode() shell function below into your remote machine's .bashrc
# - Define the Trigger in iTerm2 as defined below.
#
# Notes:
#   Docs for iTerm2 Triggers: https://iterm2.com/documentation-triggers.html
#   Docs for VSCode Remote Extension: https://code.visualstudio.com/docs/remote/remote-overview
#   - CLI: https://github.com/microsoft/vscode-remote-release/issues/585#issuecomment-536580102

# iTerm2 Preferences Setup
# ------------------------
#
# In your iTerm2 preferences, set up a Trigger (Profiles > Advanced > Triggers > Edit)
#
# Regular Expression:  .*ITERM-TRIGGER-open-with-local-vscode-remote ([^ ]+) ([^ ]+) (([^ ]+ ?)+)
#             Action:  Run Command...
#         Parameters:  /path/to/this/script \1
#
# Tip: For additional feedback, try adding a duplicate entry with a "Post Notifcation" action.

# HOW TO TEST
# -----------
#
# NOTE: The new trigger will not be active for already-open terminal sessions.
#       Open a new terminal after you add the trigger to your preferences.
#
# To test it, ssh into the remote machine, and try the 'localcode' function:
#
#   localcode .
#   localcode /some/dir
#   localcode /some/file
#   localcode /some/file remote-machine-name
#
# If something is going wrong, inspect /tmp/iterm-vscode-trigger.log

#
# Put this in your remote ~/.bashrc
#
localcode() (
    # Tell zsh to use bash-style arrays
    setopt ksh_arrays 2> /dev/null || true

    CMD=ITERM-TRIGGER-open-with-local-vscode-remote
    MACHINE=${LOCALCODE_MACHINE-submit}
    FILENAMES=( "$@" )

    if [[ ${#FILENAMES[@]} == 0 ]]; then
        FILENAMES=.
    fi

    if [[ ${#FILENAMES[@]} == 1 && -d ${FILENAMES[0]} ]]; then
            FILENAMES[0]=$(cd ${FILENAMES[0]}; pwd)
            FTYPE=directory
    else
        # Convert filenames to abspaths
        for (( i=0; i < ${#FILENAMES[@]}; i++ )); do
            FN=${FILENAMES[i]}
            if [[ -f ${FN} ]]; then
                DIRNAME=$(cd $(dirname ${FN}); pwd)
                FILENAMES[i]=${DIRNAME}/$(basename ${FN})
                FTYPE=file
            else
                1>&2 echo "Not a valid file: ${FN}"
                exit 1
            fi
        done
    fi

    echo ${CMD} ${FTYPE} ${MACHINE} ${FILENAMES[@]}
)
export -f localcode

#
# Copy this whole file onto your local machine, or at least the following lines.
# Make sure it is executable (chmod +x /path/to/this/script)
#
trigger_vscode_remote_editing() (
    # Tell zsh to use bash-style arrays
    setopt ksh_arrays 2> /dev/null || true

    # The git extension runs 'git status -z -u' on the remote machine,
    # which takes a very long time if the remote directory is a git repo
    # with a lot of untracked files.
    # That can be fixed if you configure .gitignore appropriately,
    # but for my purposes it's easier to just disable git support when editing remote files.
    # If you want git support when using remote SSH, then comment out this line.
    # See: https://github.com/microsoft/vscode-remote-release/issues/4073
    VSCODE='/usr/local/bin/code'
    VSCODE="${VSCODE} --disable-extension vscode.git --disable-extension vscode.github --disable-extension waderyan.gitblame"
    LOGFILE=/tmp/iterm-vscode-trigger.log
    FTYPE=$1
    MACHINE=$2
    FILEPATHS=( "$@" )
    FILEPATHS=( "${FILEPATHS[@]:2}" )

    TS="["$(date "+%Y-%m-%d %H:%M:%S")"]"
    echo "${TS} Triggered: ""$@" >> ${LOGFILE}

    # https://github.com/microsoft/vscode-remote-release/issues/585#issuecomment-536580102
    if [[ "${FTYPE}" == "directory" ]]; then
        CMD="${VSCODE} --remote ssh-remote+${MACHINE} ${FILEPATHS[@]}"
        echo "${TS} ${CMD}" >> ${LOGFILE}
        ${CMD}
    elif [[ "${FTYPE}" == "file" ]]; then
        for FN in ${FILEPATHS[@]}; do
            CMD="${VSCODE} --file-uri vscode-remote://ssh-remote+${MACHINE}${FN}"
            echo "${TS} ${CMD}" >> ${LOGFILE}
            ${CMD}
        done
    else
        echo "${TS} Error: Bad arguments." >> ${LOGFILE}
        exit 1
    fi
)

trigger_vscode_remote_editing "$@"

Solution 2:[2]

For anyone who still wants to do this:

My solution is inspired by @StuartBerg. THX a lot.

  1. In iTerm2, find triggers: iTerm2 -> Perenfences -> Profiles -> Advanced -> Triggers -> Edit and set the triggers as follows(remember to restart the iTerm2 after set):
# open a folder
- Regular Expression: ^open this folder in local vscode\(s\):(.+)
- Action: Run Command...
- Parameters: /path/to/your/vscode --folder-uri=vscode-remote://ssh-remote+${your remote ip}\1

# open a file
- Regular Expression: ^open this file in local vscode\(s\):(.+)
- Action: Run Command...
- Parameters: /path/to/your/vscode --file-uri=vscode-remote://ssh-remote+${your remote ip}\1
  1. Set the command alias in your remote server, usually in ~/.bashrc for linux(most behaviors of the code alias are the same as those in local terminal, except that any file that does not exit in the server will not be opened):
# pay attention to " and '
alias code='func(){
if [[ "$1" == "." ]]; then
        CODE_PATH=$(pwd | tr -d "\n\r");
else
        if [[ "$1" == /* ]]; then
                CODE_PATH="$1";
        else
                CODE_PATH=$(pwd | tr -d "\n\r")/"$1";
        fi;
fi;
if [[ -d $CODE_PATH ]]; then
        echo open this folder in local vscode"("s")":$CODE_PATH;
else
        if [[ -f $CODE_PATH ]]; then
                echo open this file in local vscode"("s")":$CODE_PATH;
        else
                echo No such file or directory: $CODE_PATH;
        fi;
fi;
};func'
  1. make the alias take effect:
$ source ${path/to/your/alias/file}
  1. In the ssh terminal, cd to the directory you want to open in local vscode (or use absolute pathes start with /) and do:
# open the current folder
$ code .
# output:
# open this folder in local vscode(s):/folder/you/want/to/open


# open a file or a folder in current directory
$ code ${filename or folder}
# output:
# open this file(folder) in local vscode(s):/path/you/want/to/open

# open an absolute path, i.e, any path that starts with `/`
$ code `/${absolute/path}`

If you've set the ssh key to the remote server locally, then the directory /path/you/want/to/open will be opened in your local vscode.

PS: ^?\(s\) and '('s')' are added on purpose as to make a little difference between the config file and the trigger Regular Expression so that the trigger won't fired accidentally when you open the alias config file. You can modify it any way you like.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2