xonsh-cheatsheet icon indicating copy to clipboard operation
xonsh-cheatsheet copied to clipboard

Cheat sheet for xonsh shell with copy-pastable examples. The best doc for the new users.

Cheat sheet for the xonsh shell with copy-pastable examples. This is a good level of knowledge at start.

If you like the cheatsheet click ⭐ on the repo and tweet.

Full screen reading

What does xonsh mean?

The "xonsh" word sounds like conch / kɑːntʃ - a common name of a number of different sea snail or shells (🐚). Thus xonsh is the reference to the shell word that commonly used to name command shells.

Install xonsh

Recommended way to install xonsh

Most of the modern operating systems has Python 3 and PyPi (pip) that preinstalled or you can install it easily. This way is recommended, because you will get the latest version of the xonsh shell from PyPi:

python -m pip install 'xonsh[full]'

If you're using Conda the package from Conda-forge is also fresh:

conda config --add channels conda-forge && conda install xonsh

The pipx is also good to install xonsh with certain Python version:

# Install Python 3.8 i.e. for Ubuntu: apt install python3.8
pip install pipx
pipx install --python python3.8 xonsh
pipx run xonsh 
# or add /home/$USER/.local/bin to PATH (/etc/shells) to running just `xonsh` command

Install xonsh on Mac OS or Linux

Following the article "Installing Python on macOS (without going insane)":

zsh  # Default MacOS shell

# Install brew from https://brew.sh/
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Install Python via pyenv
brew install pyenv
echo 'export PATH="$HOME/.pyenv/shims:$PATH"' >> ~/.zshrc
# restart zsh
pyenv install --list | grep ' 3.' | tail  # Choose the Python version
pyenv install 3.10.6
pyenv global 3.10.6

# The article recommend to use pipx to install global Python tools
brew install pipx  
pipx install 'xonsh[full]'

# Use xonsh `xpip` to install xontribs to appropriate packages location
xonsh
xpip install xontrib-prompt-bar xontrib-back2dir

On Mac we also recommend to install GNU coreutils to use the Linux default tools (i.e. ls, grep):

brew install coreutils
$PATH.append('/opt/homebrew/opt/coreutils/libexec/gnubin')

Install from package managers

Note! In the operating systems without rolling release concept the xonsh shell version may be very old because the average release cycle for the xonsh shell is two months.

apt install xonsh    # Debian/Ubuntu
pacman -S xonsh      # Arch Linux
dnf install xonsh    # Fedora
brew install xonsh   # OSX

Or on any system you can install python and then install xonsh from pip i.e. any_pkg_manager install python && python -m pip install 'xonsh[full]'.

Try without installation

Docker:

# Docker with certain Python version and latest release of xonsh
docker run --rm -it python:3.9-slim /bin/bash \
 -c "pip install 'xonsh[full]' && xonsh"

# Docker with certain Python version and xonsh from the master branch
docker run --rm -it python:3.9-slim /bin/bash \
 -c "apt update && apt install -y git && pip install -U git+https://github.com/xonsh/xonsh && xonsh"

# Official xonsh docker image has old version
docker run --rm -it xonsh/xonsh:slim

Linux-portable AppImage contains both Python 3 and xonsh:

wget https://github.com/xonsh/xonsh/releases/latest/download/xonsh-x86_64.AppImage -O xonsh
chmod +x xonsh
./xonsh

# Then if you don’t have Python on your host, you can acccess it from AppImage by running:
$PATH = [$APPDIR + '/usr/bin'] + $PATH
python -m pip install tqdm --user  # the `tqdm` package will be installed to ~/.local/
import tqdm

Basics

The xonsh language is a superset of Python 3 with additional shell support. As result you can mix shell commands and Python code as easy as possible. Right off the bat examples:

cd /tmp && ls                     # shell commands

21 + 21                           # python command

for i in range(0, 42):            # mix python 
    echo @(i+1)                   # and the shell

len($(curl https://xon.sh))       # mix python and the shell

$PATH.append('/tmp')              # using environment variables

for line in $(cat /etc/passwd).splitlines():  # read the lines from the output
    echo @(line.split(':')[0])                # prepare line on Python and echo

for file in gp`*.*`:              # reading the list of files as Path-objects
    if file.exists():             # using rich functionality of Path-objects
        du -sh @(file)            # and pass it to the shell command

import json                       # python libraries are always at hand
if docker_info := $(docker info --format '{{json .}}'):
    print('ContainersRunning:', json.loads(docker_info)['ContainersRunning'])

xpip install xontrib-prompt-bar   # xonsh has huge amount of powerful extensions
xontrib load prompt_bar           # follow the white rabbit - https://github.com/topics/xontrib

# Finally fork https://github.com/anki-code/xontrib-rc-awesome
# to convert your ~/.xonshrc into pip-installable package 
# with extesnsions you need on board.

Three most frequent things that newcomers missed

1. Shell commands also known as subprocess commands

The first thing you should remember that the shell commands are not the calls of another shell (i.e. bash). The xonsh has it's own parser implementation for subprocess commands and this is the cause why the command like echo {1..5} \; (brace expansion and escape character in bash) is not working. Most of sh-shell features can be replaced by the sane Python alternatives and the example before will be solved by echo @(range(1,6)) ';'.

If you think that only xonsh has the sh-uncompatible elements in the parser, you are tend to mistaken. If we compare Bash and Zsh we can found that pip install package[subpackage] command will work in Bash but in Zsh the error will be raised because Zsh has special mening for square braces. It's normal to have an evolution in the syntax and features.

Be calm and ready to the sane and self-consistent Python-driven mindset.

Note:

  • Most of novices try to copy and paste sh-lang commands that contains special characters and get the syntax error in xonsh. If you want to run environment agnostic sh-lang's command that you copy from the internet page just use macro call in xonsh bash -c! echo {123} or use xontrib-sh to run context-free bash commands in xonsh by adding ! at the beginning of the command.

2. Strings and arguments in shell commands

The second thing comes from the first. To escaping the special charecters, special meaning of braces or passing the string to the arguments use quotes. When in doubt, use quotes!

You should clearly understand the difference:

sh-lang shells xonsh
1. Have escape character:
echo 123\ 456
# 123 456
1. No escape character. Use quotes:
echo "123 456"
# 123 456
2. Open the quotes:
echo --arg="val"
# --arg=val
# and:
echo --arg "val" # --arg val
2. Save quotes:
echo --arg="val"
# --arg="val"
# But if argument quoted entirely:
echo --arg "val" # --arg val
3. Brackets have no meaning:
echo {123} [456]
# {123} [456]


3. Brackets have meaning:
echo {123} [456]
# SyntaxError
echo "{123}" '[456]' # {123} [456]

Note:

  • You can wrap any argument into Python string substitution:
    name = 'snail'
    echo @('--name=' + name.upper())
    # --name=SNAIL
    
  • You can use showcmd command to show the arguments list:
    showcmd echo The @('arguments') @(['list', 'is']) $(echo here) "and" --say="hello" to you
    # ['echo', 'The', 'arguments', 'list', 'is', 'here\n', 'and', '--say="hello"', 'to', 'you']]    
    

3. The process substitution operator $() returns output with universal new lines

In sh-compatible shells the process substitution operator $() executes the command and then split the output and use these parts as arguments. The command echo $(echo -e "1 2\n3") will have three distinct arguments 1, 2 and 3 that will passed to the first echo.

In xonsh shell the $() operator returns the output of the command. The command echo $(echo -e "1 2\n3") will have one argument 1 2\n3\n that will be passed to the first echo.

Note:

  • To make what sh-compatible shells are doing by $() operator the xonsh shell has @$() operator that will be described in the next chapter.
    showcmd echo @$(echo "1\n2 3\n4")
    # ['echo', '1', '2', '3', '4']
    
  • To transform output to the lines for the arguments list you can use splitlines function and the python substitution:
    showcmd echo @($(echo "1\n2 3\n4").splitlines())  # the first echo will get three arguments: "1", "2 3", "4"
    # ['echo', '1', '2 3', '4']
    
  • Not all xonsh users like this behavior of $() operator and in the future this may be changed. There are the thread to discussing this and the Xonsh Enhancement Proposal #2.

Switching from $ to @

By default the xonsh shell has the bash-like appearance for the interactive prompt: user@host ~ $. After reading the previous section you can understand that we highly recommended to replace $ to @ by adding this line to your ~/.xonshrc (or use xontrib-prompt-bar):

$PROMPT_FIELDS['prompt_end'] = '@'

Now the prompt appearance will be: user@host ~ @. This appearance will remind you of what you are in the xonsh shell.

Operators

$() - capture and return output without printing stdout and stderr

Captures stdout and returns output with universal new lines:

showcmd $(echo -e '1\n2\r3 4\r\n5')    # Subproc mode
# ['1\n2\n3 4\n5\n']

output = $(echo -e '1\n2\r3 4\r\n5')   # Python mode 
output
# '1\n2\n3 4\n5\n'

!() - capture all and return object without printing stdout and stderr

Captures stdout and returns CommandPipeline. Truthy if successful (returncode == 0), compares to, iterates over lines of stdout:

ret = !(echo 123)
ret
#CommandPipeline(
#  pid=404136,
#  returncode=0,
#  args=['echo', '123'],
#  alias=None,
#  timestamps=[1604742882.1826484, 1604742885.1393967],
#  executed_cmd=['echo', '123'],
#  input='',
#  output='123\n',
#  errors=None
#)   

if ret:
      print('Success')     
#Success

for l in ret:
      print(l)     
#123
#

$[] - not capturing (return None), print stdout and stderr

Passes stdout to the screen and returns None:

ret = $[echo 123]
#123
repr(ret)
'None'

This is the same as echo 123 but this syntax allows to explicitly run a subprocess command.

![] - capture all and return hidden object, print stdout and stderr

Passes stdout to the screen and returns HiddenCommandPipeline:

ret = ![echo -e '1\n2\r3 4\r\n5']
#1
#3 4
#5
ret               # No return value because it's hidden CommandPipeline object
ret.out           # But it has the properties from CommandPipeline
'1\n2\r3 4\n5\n'

This operator is used under the hood to running commands in the interactive xonsh prompt.

@() - use Python code as an argument or a callable alias

Evaluates Python and pass the arguments:

showcmd 'Supported:' @('string') @(['list','of','strings']) 
#['Supported:', 'string', 'list', 'of', 'strings']

echo -n '!' | @(lambda args, stdin: 'Callable' + stdin.read())
#Callable!

@$() - split output of the command by white spaces for arguments list

showcmd @$(echo -e '1\n2\r3 4\r\n5')
#['1', '2\r3', '4', '5']

This is mostly what bash's $() operator do.

Environment Variables

${...}            # Get the list of environment variables
__xonsh__.env     # Get the list of environment variables using Python syntax

$VAR = 'value'    # Set environment variable

'VAR' in ${...}   # Check environment variable exists
#True

${'V' + 'AR'}     # Get environment variable value by name from expression
#'value'

print($VAR)
with ${...}.swap(VAR='another value', NEW_VAR='new value'):  # Change VAR for commands block
    print($VAR)
print($VAR)
#value
#another value
#value

$VAR='new value' xonsh -c r'echo $VAR'   # Change variable for subprocess command
#new value

__xonsh__.env.get('VAR', 'novalue')  # the way to call environment using the __xonsh__ builtin
# 'value'

Python and subprocess mode:

print("my home is $HOME")                        # Python mode
# my home is $HOME

print("my home is " + $HOME)                     # Python mode
# my home is /home/snail

echo "my home is $HOME" as well as '$HOME'       # Subprocess mode
# my home is /home/snail as well as /home/snail

Work with $PATH:

$PATH
# EnvPath(
# ['/usr/bin',
#  '/sbin',
#  '/bin']
# )

$PATH.add(p"~/bin", front=True, replace=True))   # Insert path '~/bin' at front of $PATH list and replace existing entries
$PATH.add(p"~/bin", front=True)                  # Insert path '~/bin' at front of $PATH list
$PATH.add(p"~/bin", front=False, replace=True))  # Insert path '~/bin' at end of $PATH list and replace existing entries
$PATH.insert(0, '/tmp')                          # Insert path '/tmp' at front of $PATH list
$PATH.append('/tmp')                             # Append path '/tmp' at end of $PATH list
$PATH.remove('/tmp')                             # Remove path '/tmp' (first match)

Setup local paths for prepending to path by default via loop in .xonshrc:

import os.path
from os import path
$user_bins = [
    f'{$HOME}/.cargo/bin',
    f'{$HOME}/.pyenv/bin',
    f'{$HOME}/.poetry/bin',
    f'{$HOME}/bin',
    f'{$HOME}/local/bin',
    f'{$HOME}/.local/bin', 
]

for dir in $user_bins:
    if path.isdir(dir) and path.exists(dir):
        $PATH.add(dir,front=True, replace=True)

See also the list of xonsh default environment variables.

Aliases

Simple aliases

aliases['g'] = 'git status -sb'           # Add alias as string
aliases['e'] = 'echo @(2+2)'              # Add xonsh executable alias (ExecAlias)
aliases['gp'] = ['git', 'pull']           # Add alias as list of arguments
aliases['b'] = lambda: "Banana!\n"        # Add alias as simple callable lambda
aliases |= {'a': 'echo a', 'b':'echo b'}  # Add aliases from the list
del aliases['b']                          # Delete alias

Wrap command arguments using ExecAlias, built-in $args variable that contains the list of arguments and handy """-string:

aliases['p'] = """showcmd @([a for a in $args if a != 'cutme'])"""

p
# usage: showcmd [-h|--help|cmd args]
# Displays the command and arguments as a list ...

p 1 2 cutme 3
#['1', '2', '3']

p 3 4 5
#['3', '4', '5']

Callable aliases

def _myargs1(args):
#def _myargs2(args, stdin=None):
#def _myargs3(args, stdin=None, stdout=None):
#def _myargs4(args, stdin=None, stdout=None, stderr=None):
#def _myargs5(args, stdin=None, stdout=None, stderr=None, spec=None):
#def _myargs6(args, stdin=None, stdout=None, stderr=None, spec=None, stack=None):
    print(args)
    
aliases['args'] = _myargs1
del _myargs1

args 1 2 3
#['1', '2', '3']

Read stdin and write to stdout (real life example - xontrib-pipeliner):

def _exc(args, stdin, stdout):
    for line in stdin.readlines():
        print(line.strip() + '!', file=stdout, flush=True)

aliases['exc'] = _exc

echo hello | exc
# hello!

Path strings

The p-string returns Path object:

path = p'~/.xonshrc'
path
# Path('/home/snail/.xonshrc')

[path.name, path.exists(), path.parent]
# ['.xonshrc', True, Path('/home/snail')]

[f for f in path.parent.glob('*') if 'xonsh' in f.name]
# [Path('/home/snail/.xonshrc')]

dir1 = 'hello'
dir2 = 'world'
path = p'/tmp' / dir1 / dir2 / 'from/dir' / f'{dir1}'
path
# Path('/tmp/hello/world/from/dir/hello')

Simple way to read and write the file content using Path string:

text_len = p'/tmp/hello'.write_text('Hello world')
content = p'/tmp/hello'.read_text()
content
# 'Hello world'

Globbing - get the list of files from path by mask or regexp

To Normal globbing add g before back quotes:

ls *.*
ls g`*.*`

for f in gp`/tmp/*.*`:  # `p` is to return path objects
    print(f.name)
      
for f in gp`/tmp/*/**`:  # `**` is to glob subdirectories
    print(f)

To Regular Expression Globbing add r before back quotes:

ls `.*`
ls r`.*`

for f in rp`.*`:          # `p` is to return path instances
      print(f.exists())

To Custom function globbing add @ and the function name before back quotes:

def foo(s):
    return [i for i in os.listdir('.') if i.startswith(s)]
cd /
@foo`bi`
#['bin']

Macros

Simple macros

def m(x : str):
    return x

# No macro calls:
[m('me'), m(42), m(m)]
# ['me', 42, <function __main__.m>]

# Macro calls:
[m!('me'), m!(42), m!(identity), m!(42), m!(  42 ), m!(import os)]
# ["'me'", '42', 'identity', '42', '42', 'import os']

m!(if True:
    pass)
# 'if True:\n    pass'

Subprocess Macros

echo! "Hello!"
# "Hello!"

bash -c! echo "Hello!"
# Hello!

docker run -it --rm xonsh/xonsh:slim xonsh -c! 2+2
# 4

Inside of a macro, all additional munging is turned off:


echo $USER
# lou

echo! $USER
# $USER

Macro block

Builtin macro Block

from xonsh.contexts import Block
with! Block() as b:
    qwe
    asd
    zxc

b.macro_block
# 'qwe\nasd\nzxc\n\n'
b.lines
# ['qwe', 'asd', 'zxc', '']

Custom JSON block

import json

class JsonBlock:
    __xonsh_block__ = str

    def __enter__(self):
        return json.loads(self.macro_block)

    def __exit__(self, *exc):
        del self.macro_block, self.macro_globals, self.macro_locals


with! JsonBlock() as j:
    {
        "Hello": "world!"
    }
    
j['Hello']
# world!

Custom Docker block

The example is from xontrib-macro-lib:

from xonsh.contexts import Block

class Doxer(Block):
    """Run xonsh codeblock in docker container."""

    def __init__(self):
       self.docker_image = 'xonsh/xonsh:slim'

    def __exit__(self, *a, **kw):
        $[docker run -it --rm @(self.docker_image) /usr/local/bin/xonsh -c @(self.macro_block)]


with! Doxer() as d:
   pip install lolcat
   echo "We're in docker container now!" | lolcat

Macro blocks library

See also xontrib-macro-lib.

Tab-Completion

completer list  # List the active completers

# Create your own completer:
def dummy_completer(prefix, line, begidx, endidx, ctx):
    '''
    Completes everything with options "lou" and "carcolh",
    regardless of the value of prefix.
    '''
    return {"lou", "carcolh"}
    
completer add dummy dummy_completer  # Add completer: `completer add <NAME> <FUNC>`
# Now press Tab key and you'll get {"lou", "carcolh"} in completions
completer remove dummy

Bind hotkeys in prompt toolkit shell

Uncover the power of prompt_toolkit by binding the hotkeys. Run this snippet or add it to ~/.xonshrc:

from prompt_toolkit.keys import Keys

@events.on_ptk_create
def custom_keybindings(bindings, **kw):

    # Press F1 and get the list of files
    @bindings.add(Keys.F1)
    def run_ls(event):
        ls -l
        event.cli.renderer.erase()
    
    # Press F3 to insert the grep command
    @bindings.add(Keys.F3)
    def say_hi(event):
        event.current_buffer.insert_text('| grep -i ')
        

Xontrib - extension or plugin for xonsh

Xontrib lists:

To install xontribs xonsh has xpip - a predefined alias pointing to the pip command associated with the Python executable running this xonsh. It's the right way to install xontrib via xpip to be confident that the xontrib will be installed in the right environment.

If you want to create your own xontrib using xontrib-template is the best way:

pipx install copier
pipx inject copier jinja2-time
pipx inject copier cookiecutter

copier gh:xonsh/xontrib-template .

Xonsh Script (xsh)

Real life example of xsh script that have: arguments, tab completion for arguments (using xontrib-argcomplete), subprocess calls with checking the result, colorizing the result and exit code:

#!/usr/bin/env xonsh
# PYTHON_ARGCOMPLETE_OK                                  
import argparse
import argcomplete  # Tab completion support with xontrib-argcomplete
from argcomplete.completers import ChoicesCompleter

argp = argparse.ArgumentParser(description=f"Get count of lines in HTML by site address.")
argp.add_argument('--host', required=True, help="Host").completer=ChoicesCompleter(('xon.sh', 'github.com'))
argcomplete.autocomplete(argp)
args = argp.parse_args()

if result := !(curl -s -L @(args.host)):  # Python + Subprocess = ♥
    lines_count = len(result.out.splitlines())
    printx(f'{{GREEN}}Count of lines on {{#00FF00}}{args.host}{{GREEN}}: {{YELLOW}}{lines_count}{{RESET}}')
else:
    printx(f'{{RED}}Error while reading {{YELLOW}}{args.host}{{RED}}! {{RESET}}') # Colorizing messages
    exit(1)  # Exit with code number 1

Try it in action:

xonsh
pip install argcomplete xontrib-argcomplete
xontrib load argcomplete
cd /tmp
wget https://raw.githubusercontent.com/anki-code/xonsh-cheatsheet/main/examples/host_lines.xsh
xonsh host_lines.xsh --ho<Tab>
xonsh host_lines.xsh --host <Tab>
xonsh host_lines.xsh --host xon.sh  # OR: chmod +x host_lines.xsh && ./host_lines.xsh --host xon.sh
# Count of lines on xon.sh: 568

History

There are two history backends: json and sqlite which xonsh has by default. The json backend creates json file with commands history on every xonsh session. The sqlite backend has one file with SQL-database.

We recommend to use sqlite backend because it saves the command on every execution and the querying of the history using SQL is very handy.

echo 123
# 123

__xonsh__.history[-1]
# HistoryEntry(cmd='echo 123', out='123\n', rtn=0, ts=[1614527550.2158427, 1614527550.2382812])

history info
# backend: sqlite
# sessionid: 637e577c-e5c3-4115-a3fd-99026f113464
# filename: /home/user/.local/share/xonsh/xonsh-history.sqlite
# session items: 2
# all items: 8533
# gc options: (100000, 'commands')

sqlite3 $XONSH_HISTORY_FILE  "SELECT inp FROM xonsh_history ORDER BY tsb LIMIT 1;"
# echo 123

pip install sqlite_web
sqlite_web $XONSH_HISTORY_FILE
# Open the database in the browser

There is third party history backends that's supplied as xontribs: xontrib-history-encrypt.

Interactive mode events

When you're in xonsh interactive mode you can register an event i.e.:

@events.on_chdir
def mychdir(olddir, newdir, **kw):
    echo Jump from @(olddir) to @(newdir)
    
cd /tmp
# Jump from /home/snail to /tmp

Help

Add ? (regular help) or ?? (super help) to the command:

ls?
# man page for ls

import json
json?
# json module help
json??
# json module super help

Known issues and workaround

ModuleNotFoundError

Sometimes when you're using PyPi, Conda or virtual environments you can forget about current version and location of Python and try to import packages in xonsh with ModuleNotFoundError error. Ordinary you installed the package in other environment and didn't realise it. To solve this case there is xpip alias that you can use to install PyPi packages in the Python environment that was used to run current xonsh session.

The example of how to get the path to Python:

# Getting current active Python version
python --version   # Python 3.8.5
which python       # /opt/miniconda3/bin/python
pip install tqdm   # Will be installed to /opt/miniconda3/lib

# Getting the Python version that used to run xonsh
import sys  
sys.executable                  # '/usr/bin/python'
@(sys.executable) --version     # Python 3.9.2
xpip install tqdm               # Will be installed to /usr/lib

Freezed terminal in interactive tools

If you run the console tool and got the freezed terminal (Ctrl+c, Ctrl+d is not working) this looks like the tool was interpreted as threaded and capturable program but the tool has interactive elements that expect the input from the user. There are four workarounds now:

  1. Disable THREAD_SUBPROCS:

    with ${...}.swap(THREAD_SUBPROCS=False):
          ./tool.sh
    
  2. Run the tool in uncaptured mode:

    $[./tool.sh]
    
  3. Set the unthreadable predictor:

    __xonsh__.commands_cache.threadable_predictors['tool.sh'] = lambda *a, **kw: False  # use the pure name of the tool
    ./tool.sh
    

Finally check $XONSH_CAPTURE_ALWAYS.

Uncaptured output

If you want to capture the output of the tool but it's not captured there are three workarounds now:

  1. Add the head tool at the end of pipe to force using the threadable mode:

    !(echo 123 | head -n 1000)
    #CommandPipeline(
    #  returncode=0,
    #  output='123\n',
    #  errors=None
    #)
    
  2. Change threading prediction for this tool:

    __xonsh__.commands_cache.threadable_predictors['ssh'] = lambda *a, **kw: True
    
    !(ssh host -T "echo 1")
    #CommandPipeline(
    #  returncode=0,
    #  output='1\n',
    #  errors=None
    #)
    
  3. Wrap the tool into bash subprocess:

    !(bash -c "echo 123")
    #CommandPipeline(
    #  returncode=0,
    #  output='123\n',
    #  errors=None
    #)
    

Bad file descriptor

In case of using callable aliases in the long loop the error Bad file descriptor will be raised. Workaround is to avoid using callable aliases in the loop and move the code from callable alias to the loop or mark callable alias as unthreadable:

from xonsh.tools import unthreadable

@unthreadable
def _e():
    execx('echo -n 1')
aliases['e'] = _e

for i in range(100):
      e

Unexpected issues around the bashisms xontrib

Sometimes the bashisms xontrib can be the cause of unexpected issues (4250). We recommend to avoid using this xontrib. Instead of trying to bring bash into xonsh we recommend to dive into xonsh. In case of you will need some bash syntax the best way is to implement this manually to have the clean understanding of what you do.

Tips and tricks

Make your own installable xonsh RC file

Start from fork xontrib-rc-awesome.

Using text block in command line

Creating file:

echo @("""
line 1
line 2
line 3
""".strip()) > file.txt

$(cat file.txt)
# 'line 1\nline 2\nline 3\n'

Run commands in docker:

docker run -it --rm xonsh/xonsh:slim xonsh -c @("""
pip install --disable-pip-version-check -q lolcat
echo "We're in docker container now!" | lolcat
""")

Don't forget that Alt+Enter can run the command from any place where cursor is.

Using xonsh wherever you go through the SSH

You stuffed command shell with aliases, tools and colors but you lose it all when using ssh. The mission of xxh project is to bring your favorite shell wherever you go through the ssh without root access and system installations.

How to modify command before execution?

To change the command between pressing enter and execution there is on_transform_command event:

pip install lolcat

@events.on_transform_command
def _(cmd, **kw):
    if cmd.startswith('echo') and 'lolcat' not in cmd:  
        # Be careful with the condition! The modified command will be passed 
        # to `on_transform_command` event again and again until the event 
        # returns the same command. Newbies make a mistakes and facing with looping.
        return cmd.rstrip() + ' | lolcat'
    else:
        return cmd
        
echo 123456789 # <Enter>
# Execution: echo 123456789 | lolcat

How to paste and edit the multiple line of code being in interactive mode

In some terminals (Konsole in Linux or Windows Terminal for WSL) you can press ctrl-x ctrl-e to open up an editor (nano in Linux) in the terminal session, paste the code there, edit and then quit out. Your multiple line code will be pasted and executed.

Waiting for the job done

sleep 100 &  # job 1
sleep 100 &  # job 2
sleep 100 &  # job 3

while $(jobs):
    time.sleep(1)

print('Job done!')

Copy current session commands to clipboard using xclip

aliases['hist-to-clip'] = lambda: $[echo @('\n\n'.join([h.cmd for h in __xonsh__.history])) | xclip]
hist-to-clip

How to trace the xonsh code?

Trace with hunter:

pip install hunter
$PYTHONHUNTER='depth_lt=10,stdlib=False' $XONSH_DEBUG=1 xonsh -c 'echo 1'

Or try xunter for tracing and profiling.

From Bash to Xonsh

Read Bash to Xonsh Translation Guide, run bash -c! echo 123 or install xontrib-sh.

Answers to the holy war questions

Bash is everywhere! Why xonsh?

Python is everywhere as well ;)

Xonsh is slower! Why xonsh?

Significant much more time you spending on Googling and debugging the sh-based solutions as well as significant much more time takes the payload work after running a command. Yeah, xonsh is a bit slower but you will not notice that in real life tasks :)

Also:

My fancy prompt in other shell is super duper! Why xonsh?

The fancy prompt is the tip of the iceberg. We love the xonsh shell entirely: sane language, powerful aliases, agile extensions, history backends, fully customisable tab completion, magic macro blocks, behaviour customisation via environment variables and more, and more, and more :)

Xonsh has an issues! Why xonsh?

In comparing with the 15-20 years old shells yeah, xonsh is a 5 years old young man. But we use it for this 5 years day by day to solve our tasks with success and happiness :)

Thank you!

Thank you for the reading! This cheatsheet is the tip of the iceberg of the xonsh shell and you can find more in the official documentation.

Also you can install cheatsheet xontrib:

xpip install -U git+https://github.com/anki-code/xonsh-cheatsheet
xontrib load cheatsheet
cheatsheet
# Opening: https://github.com/anki-code/xonsh-cheatsheet/blob/main/README.md

If you like the cheatsheet click ⭐ on the repo and tweet.

Credits