Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 

Repository files navigation

XWRAP / XWARP

A pair of utilities for running commands from an interactive shell that allow child processes to affect the parent shell's environment.

XWRAP

xwrap executes a command within a wrapped environment, enabling the use of xwarp.

XWARP

xwarp captures a command and requests its execution in the parent shell that invoked xwrap.

Why?

Programs launched from an interactive shell cannot directly modify the shell's environment (e.g., changing the working directory via cd or updating PS1). Ordinarily, such changes must be performed by shell builtins or sourced scripts.

xwrap and xwarp provide a mechanism to bridge this gap, allowing external programs to "talk back" to the shell.

How it works

  • xwrap:
    • Creates a temporary file.
    • Sets an environment variable so xwarp can locate this file.
    • Extends the PATH to make xwarp available to child processes.
    • Executes the target command.
    • Once the command exits, it reads the temporary file, executes any recorded commands in the current shell, and deletes the file.
  • xwarp:
    • Writes the specified command to the temporary file.
    • These commands are subsequently executed by the enclosing xwrap process.

This mechanism allows child processes to request environment changes that take effect in the original interactive shell session.

Security

The core purpose of xwrap/xwarp is to execute arbitrary commands in the shell. Consequently, it is highly dangerous to pass untrusted strings to these utilities. It is generally safe to provide arguments for a specified command, provided the command itself is trusted.

For example, assuming "$@" contains untrusted data, the following are unsafe:

  • xwrap "$@"
  • xwrap unsafe-command "$@"
  • xwarp "$@"
  • xwarp unsafe-command "$@"

The following usage patterns are safe:

  • xwrap safe-command "$@"
  • xwrap --allow "safe-command" safe-command-that-may-emit-unsafe-xwarp "$@"
  • xwarp safe-command "$@"

xwrap and xwarp communicate via a temporary file created with mktemp. Any process that can write to this file can cause xwrap to execute arbitrary commands. To mitigate this, use --allow <command> to restrict which commands xwarp may request.

Installation

Clone this repository, source xwrap.sh in your .bashrc or .zshrc, and you're ready to go.

Usage

Usage:
  xwrap [OPTIONS] <COMMAND> [ARGS]...

Arguments:
  <COMMAND>  Command to run
  [ARGS]...  Arguments for the command

Options:
  -a, --allow <CMD>      Allow only this command (exact match). Repeatable.
      --concurrent       Enable safe concurrent xwarp calls (uses flock).
  -h, --help             Show this help message.

Examples

source xwrap.sh

# xwrap runs the given command.
xwrap 'echo' 'foo'
#=> foo

# xwarp allows xwrap to execute the given command.
xwrap 'xwarp' 'echo' 'foo'
#=> foo

# Use `eval` to supply multiple commands.
# Note: The output order flips because the xwarped command runs after
# the main process finishes.
xwrap eval "xwarp echo 'foo'; echo 'bar'"
#=> bar
#   foo

# Change the current working directory using xwarp.
xwrap 'xwarp' 'cd' '..'
# You are now in the parent directory.

# xwarp can be called from a nested process (e.g., Python).
xwrap python -c 'import subprocess; subprocess.run(["xwarp", "cd", ".."]);'
# You are now in the parent directory.

# Modify environment variables using `eval`.
xwrap xwarp eval "foo=bar"; echo $foo; unset foo
#=> bar

# Specify a regex filter for commands that `xwarp` is permitted to execute.
xwrap --allow "ls" --allow "cd" xwarp cd ..
# Success: you are now in the parent directory.

xwrap --allow "ls" xwarp cd ..
# Results in an error.

Integration Examples

Fuzzy Finder (television)

To integrate with television, set up an alias:

alias tv="xwrap tv"

Then, modify the cd action in dirs.toml:

[actions.cd]
description = "Change the current working directory to the selected directory"
command = "xwarp cd {}"

Zsh Line Editor (ZLE)

Define a ZLE widget that propagates the necessary variables:

function my_widget() {
  xwrap eval 'LBUFFER="$LBUFFER" RBUFFER="$RBUFFER" python ./hook.py'
}

zle -N my_widget
bindkey '^y' my_widget

Perform the logic in an external program:

#!/usr/bin/env python
import os
import subprocess

# Surround the cursor with `<` and `>`
subprocess.run([
    'xwarp',
    'eval',
    # Note: In reality, ensure strings are properly escaped.
    'LBUFFER="' + os.environ['LBUFFER'] + '<" RBUFFER=">' + os.environ['RBUFFER'] + '"'
])

Contribution

By contributing to this project, you agree that your contributions are licensed under the MIT License. Additionally, you grant the project maintainer a non-exclusive, perpetual, irrevocable, royalty-free, and sublicensable license to use, modify, and redistribute your contributions under any other license at the maintainer's discretion.

About

Execute commands that can modify parent shell

Resources

Stars

Watchers

Forks

Used by

Contributors

Languages