Package: xontrib-cheatsheet
Cheat sheet for thexonsh shellwith copy-pastable examples. This is a good level of knowledge to start being productive.If you like the cheatsheet click ⭐ on the repo andtweetabout it.Full screen readingWhat is xonsh?Xonsh is a Python-powered, cross-platform, Unix-gazing shell language and command prompt. The language is a superset of Python 3.6+ with additional shell primitives that you are used to fromBashandIPython. It works on all Python-compatible systems, including Linux, macOS, and Windows. Tryright off the bat examples.What does xonsh mean?The "xonsh" word sounds likeconch [kɑːntʃ]- a common name of a number of different sea snails or shells (🐚). Thus xonsh is the reference to the shell word that is commonly used to namecommand shells.You will also face with "xontrib" word. The xontrib is short version of"(contrib)ution"word and points to extensions, community articles and other materials around xonsh.Install xonshThere are three ways to use xonsh:Simple xonsh install. You can use the system installed Python to install xonsh and dependencies. This is a good option if you don't plan to manage Python versions or virtual environments.Install xonsh with package and environment management system. In this way you can flexibly manage the Python version, dependencies, and virtual environments, but because xonsh is a Python-based shell you have to understand what you're doing and the section below will provide some guidance.Try xonsh without installation. Use Docker or the Linux AppImage to run and try xonsh.Simple xonsh installMost modern operating systems havePythonandPyPi (pip)that are preinstalled or that can be installed easily. By installing from PyPi you will getthe latest version of the xonsh shell. We highly recommend using thefullversion of the xonsh PyPi-package withprompt-toolkiton board:python -m pip install 'xonsh[full]'Another way is to install xonsh from the package manager that is supplied by the operating system. This way isnotrecommended in operating systems without therolling release conceptthe xonsh shell version may be very old (check latest xonsh releaseorversions of xonsh across platforms) because the averagerelease cycle for the xonsh shellis quarter.# Not recommended but possible
apt install xonsh     # Debian/Ubuntu
dnf install xonsh     # Fedora
brew install xonsh    # OSXArch Linuxisa rolling release system, so you are fine using that:pacman -S xonsh python-prompt_toolkit  # Arch LinuxOn any system you can installpythonand then install xonsh from pip i.e.,any_pkg_manager install python && python -m pip install 'xonsh[full]'This is the preferable way.Install xonsh with package and environment management systemXonsh is a Python-based shell, and to run xonsh you must have Python installed. The Python version and its packages can be installed and located anywhere: in the operating system directories, as part of a virtual environment, as part of the user directory, or as a virtual drive created temporarily behind the scenes by the Linux AppImage.The first thing you have to remember is that when you executeimportor any other Python code during a xonsh session it will be executed in the Python environment that was used to run current instance of xonsh. Use thexc aliasto check the xonsh context.In other words, you can activate a virtual environment during a xonsh session (using conda, pyenv, pipx) but the current session will continue to use packages from the environment that was used to run xonsh. And if you want to run xonsh with the packages from the currently activated virtual environment you have to install xonsh in that environment and run it directly.Thus the second thing you should remember is that when you run xonsh in virtual environment it will try to loadxonsh RC files(i.e.~/.xonshrc) and because the virtual environment is different from the environement you ordinarily use, the loading of the RC file will tend to fail because of the lack of the appropriate set of packages. When you write your~/.xonshrcit's good practice to check the existing external dependencies before loading them. See alsoxontrib-rc-awesome.Install xonsh on macOS or Linux using condaYou can useCondawithConda-forgeto install and use xonsh.#
# Install python using brew
#
zsh  # Default macOS shell
# Install brew from https://brew.sh/
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install python  # or `python@3.11`

#
# Install Miniconda from https://docs.conda.io/en/latest/miniconda.html 
# (example for Mac, use the link for your platform)
#
cd /tmp
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh
chmod +x Miniconda3-latest-MacOSX-arm64.sh
./Miniconda3-latest-MacOSX-arm64.sh
# Add conda init code that was printed to `~/.zshrc` and restart zsh.
# Or run `/Users/username/miniconda3/bin/conda init zsh` to add init to ~/.zshrc and restart zsh.

# After restarting zsh you will see `(base)` in prompt.
# This means that you're in the conda `base` environment.

# Switch to Conda-forge channel
conda config --add channels conda-forge
conda config --set channel_priority strict 
conda update --all --yes

# Install xonsh to the `base` environment
conda install xonsh
conda init xonsh  # Add init to ~/.xonshrc. You can also add `$CONDA_AUTO_ACTIVATE_BASE='false'` to avoid conda loading at start

which xonsh
# /Users/username/miniconda3/bin/xonsh

# Run xonsh from the `base` environment
xonshHow to work and understand the environments in conda:# `xpip` is used to install packages to the current xonsh session location (now it's `base` environment)
xpip install ujson  

# Example of creating the environment with a certain version of Python
conda search python | grep 3.10
conda create -n "py310" python=3.10 xonsh

conda activate py310
# Now the environment is `py310` but current xonsh session is still in `base` environment

which xonsh
# /Users/username/miniconda3/envs/py310/bin/xonsh

which pip
# /Users/username/miniconda3/envs/py310/bin/pip  # pip from `py310`

which xpip
# /Users/username/miniconda3/bin/pip  # pip from `base` environment from where xonsh ran

# Run xonsh that installed in `py310` environment from xonsh runned in `base` environment
xonsh
conda activate py310
# Now xonsh session is in `py310` environment and the current environment is also `py310`

import ujson
# No module named 'ujson'   # YES because ujson was installed in `base` environmentOn Mac we also recommend to installGNU coreutilsto use the Linux default tools (i.e.ls,grep):brew install coreutils
$PATH.append('/opt/homebrew/opt/coreutils/libexec/gnubin')  # add to ~/.xonshrcHow to understand the xonsh locationWhich xonsh and which Python used to run thecurrentxonsh session:import sys
[sys.argv[0], sys.executable]
# ['/opt/homebrew/bin/xonsh', '/opt/homebrew/opt/python@3.11/bin/python3.11']


@(sys.executable) -m site
# Full info about pathsWhichxonshand whichpythonthat will be executed to runnew instancesdepends on the list of directories in$PATHor virtual environment:$PATH
# ['/home/user/miniconda3/bin', '/opt/homebrew/bin]

[$(ls -la @$(which xonsh)), $(ls -la @$(which python)), $(python -V)]
# ['/home/user/miniconda3/bin/xonsh', '/home/user/miniconda3/bin/python -> python3.11', 'Python 3.11.1']

python -m site
# Full info about pathspipx and xonshThepipxtool is also good to install xonsh in case you need certain Python version:# Install Python before continuing
pip install pipx
pipx install --python python3.8 xonsh  # Here `python3.8` is the path to installed python. 
pipx run xonsh 
# or add /home/$USER/.local/bin to PATH (/etc/shells) to allow running just the `xonsh` commandTry xonsh without installationDocker# Docker with specific Python version and latest release of xonshdockerrun--rm-itpython:3.11-slim/bin/bash\-c"pip install 'xonsh[full]' && xonsh"# Docker with specific Python version and xonsh from the master branchdockerrun--rm-itpython:3.11-slim/bin/bash\-c"apt update && apt install -y git && pip install -U git+https://github.com/xonsh/xonsh && xonsh"# Official xonsh docker image may has an old versiondockerrun--rm-itxonsh/xonsh:slimLinux-portable AppImage contains bothPython 3 and xonsh in one filewgethttps://github.com/xonsh/xonsh/releases/latest/download/xonsh-x86_64.AppImage-Oxonshchmod+xxonsh./xonsh# Then if you don’t have Python on your host, you can acccess it from the AppImage by running:$PATH=[$APPDIR+'/usr/bin']+$PATHpython-mpipinstalltqdm--user# the `tqdm` package will be installed to ~/.local/importtqdmYou canbuild your own xonsh AppImagewith the packages you need in 15 minutes.Xonsh basicsThe xonsh language is a superset of Python 3 with additional shell support. As result you can mix shell commands and Python code as easily 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

p'/etc/passwd'.read_text().find('root')  # path-string returns Path 
                                         # (https://docs.python.org/3/library/pathlib.html)

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 a pip-installable package 
# with the extensions you need on board.Looks nice?Install xonsh!Three most frequent things that newcomers overlook1.Shell commands, also known as subprocess commandsThe first thing you should remember is that the shell commands are not the calls of another shell (i.e. bash). Xonsh has its own parser implementation for subprocess commands, and this is why a command likeecho {1..5} \;(brace expansion and escape characters in bash) won't work. Most sh-shell featurescan be replacedby sane Python alternatives. For example, the earlier command could be expressed asecho @(range(1,6)) ';'.If you think that only xonsh has the sh-uncompatible elements in its parser, you are mistaken. If we compare Bash and Zsh we will find thatpip install package[subpackage]command will work in Bash but in Zsh the error will be raised because Zsh has a special meaning for square braces. It's normal to have an evolution in the syntax and features.Be calm and accept the sane and self-consistent Python-driven mindset.Note:Most of novice try to copy and paste sh-lang commands that contain special characters and get syntax errors in xonsh. If you want to run environment agnostic sh-lang's commands that you copy from the internet just use the macro call in xonshbash -c! echo {123}or usexontrib-shto run context-free bash commands in xonsh by adding!at the beginning of the command.We highly recommend to taking a look at the sectionInstall xonsh with package and environment management system.2.Strings and arguments in shell commandsThe second potential misunderstanding comes from the first. To escape special charecters, the special meaning of braces, or pass a string as an argument, use quotes. When in doubt, use quotes!You should clearly understand the difference:sh-lang shellsxonsh1. Has an escape character:echo 123\ 456# 123 4561. Use quotes:echo "123 456"# 123 456Escape characterto wrap and so on:echo "123\
456"# 1234562. Open the quotes:echo --arg="val"# --arg=val# and:echo --arg "val"# --arg val2. Save quotes:echo --arg="val"# --arg="val"# But if argument quoted entirely:echo --arg "val"# --arg val3. Brackets have no meaning:echo {123} [456]# {123} [456]3. Brackets have meaning:echo {123} [456]# SyntaxErrorecho "{123}" '[456]'# {123} [456]Note:You can wrap any argument into Python string substitution:name='snail'echo@('--name='+name.upper())# --name=SNAILYou can use theshowcmdcommand to show the arguments list:showcmdechoThe@('arguments')@(['list','is'])$(echohere)"and"--say="hello"toyou# ['echo', 'The', 'arguments', 'list', 'is', 'here\n', 'and', '--say="hello"', 'to', 'you']]3. The process substitution operator$()returns output withuniversal new linesIn sh-compatible shells, theprocess substitution operator$()executes the command and then splits the output and uses those parts as arguments. The commandecho $(echo -e "1 2\n3")will have three distinct arguments,1,2and3that will passed to the firstecho.In xonsh shell the$()operator returns the output of the command. The commandecho $(echo -e "1 2\n3")will have one argument,1 2\n3\nthat will be passed to the firstecho.Note:To do what sh-compatible shells are doing with the$()operator, the xonsh shell has the@$()operator that will be described in the next chapter.showcmdecho@$(echo"1\n2 3\n4")# ['echo', '1', '2', '3', '4']To transform output to the lines for the arguments list you can usesplitlinesfunction and the python substitution:showcmdecho@($(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 isa thread to discussingthis and theXonsh Enhancement Proposal #2.Operators$()- capture and return output without printing stdout and stderrCaptures stdout and returns output withuniversal 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 modeoutput# '1\n2\n3 4\n5\n'!()- capture all and return object without printing stdout and stderrCaptures stdout and returnsCommandPipeline. Truthy if successful (returncode == 0), compares to, iterates over lines of stdout:ret=!(echo123)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#)ifret:print('Success')#Successforlinret:print(l)#123#Note! In some cases, to get the output you need to convert an object to a string or invoke.end()manually or use the.out:r = !(ls /)
r.output
# ''

r.end()
r.output
# 'bin\netc\n...'

r = !(ls /)
r.out                # out is forcing ending
# 'bin\netc\n...'

r = !(ls /)
print(r)             # r will be converted to str and the ending will be forced
# bin
# etc
# ...$[]- not capturing (returnNone), print stdout and stderrPasses stdout to the screen and returnsNone:ret=$[echo123]# 123repr(ret)# 'None'This is the same asecho 123, but this syntax allows explicitly running a subprocess command.![]- capture all and return hidden object, print stdout and stderrNote! The behavior may be different if$XONSH_CAPTURE_ALWAYSis True or False (default).Passes stdout to the screen and returnsHiddenCommandPipeline:with__xonsh__.env.swap(XONSH_CAPTURE_ALWAYS=True):ret=![echo-e'1\n2\r3 4\r\n5']# 1               # Stream output of the command# 3 4# 5ret# No return value because it's HiddenCommandPipeline objectret.out# But it has the properties from CommandPipeline# '1\n2\r3 4\n5\n'This operator is used under the hood for running commands at the interactive xonsh prompt.@()- use Python code as an argument or a callable aliasEvaluates Python and passes the arguments:showcmd'Supported:'@('string')@(['list','of','strings'])#['Supported:', 'string', 'list', 'of', 'strings']echo-n'!'|@(lambdaargs,stdin:'Callable'+stdin.read())#Callable!@$()- split output of the command by white spaces for arguments listshowcmd@$(echo-e'1\n2\r3 4\r\n5')#['1', '2\r3', '4', '5']This is mostlywhat bash's$()operator does.Environment Variables${...}# Get the list of environment variables__xonsh__.env# Get the list of environment variables using Python syntax$VAR='value'# Set environment variableENV=${...}# short typingENV.get('VAR','novalue')# the good practice to have a fallback for missing value# 'value'ENV.get('VAR2','novalue')# the good practice to have a fallback for missing value# 'novalue''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 blockprint($VAR)print($VAR)#value#another value#value$VAR='new value'xonsh-cr'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 $HOMEprint("my home is "+$HOME)# Python mode# my home is /home/snailecho"my home is $HOME"aswellas'$HOME'# Subprocess mode# my home is /home/snail as well as /home/snailWork 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 by prepending to path via a loop in.xonshrc:importos.pathfromosimportpath$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',]fordirin$user_bins:ifpath.isdir(dir)andpath.exists(dir):$PATH.add(dir,front=True,replace=True)See also the list ofxonsh default environment variables.AliasesSimple aliasesaliases['g']='git status -sb'# Add alias as stringaliases['e']='echo @(2+2)'# Add xonsh executable alias (ExecAlias)aliases['gp']=['git','pull']# Add alias as list of argumentsaliases['b']=lambda:"Banana!\n"# Add alias as simple callable lambdaaliases|={'a':'echo a','b':'echo b'}# Add aliases from the dictdelaliases['b']# Delete aliasEasy wrapping a command by usingExecAliaswith built-in$args(or$arg0,$arg1, etc) variable:aliases['echo-new']="echo @($args) new"$(echo-newhello)# 'hello new\n'$(echo-new-nhello)# 'hello new'Also with handy"""-string to use"and'without escaping:aliases['scmd']="""showcmd @([a for a in $args if a != "cutme"])"""scmd# usage: showcmd [-h|--help|cmd args]# Displays the command and arguments as a list ...scmd12cutme3#['1', '2', '3']Callable aliasesdef_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']=_myargs1del_myargs1args123#['1', '2', '3']Simple definition withdecorator:@aliases.register("hello")
def __hello():
    echo world
    
hello
# worldRead stdin and write to stdout (real-life example -xontrib-pipeliner):# Add an exclamation point to each line
def _exc(args, stdin, stdout):
    for line in stdin.readlines():
        print(line.strip() + '!', file=stdout, flush=True)

aliases['exc'] = _exc

echo hello | exc
# hello!# JSON to YAML
@aliases.register("j2y")
def __j2y(args, stdin, stdout):
    import json, yaml
    print(yaml.dump(json.loads(stdin.read())), file=stdout)

# YAML to JSON
@aliases.register("y2j")
def __y2j(args, stdin, stdout):
    import yaml, json
    json.dump(yaml.safe_load(stdin), stdout, indent=4)

echo '{"hello":{"world":"42"}}' | j2y
# hello:
#   world: 42

echo 'hello:\n  world: 42' | y2j
# {
#     "hello": {
#         "world": "42"
#     }
# }AbbrevsThere isxontrib-abbrevsas alternative to aliases. You can create abbrev and set the position of editing:xpip install xontrib-abbrevs
xontrib load abbrevs

abbrevs['gst'] = 'git status'
gst  # Once you hit <space> or <return> 'gst' gets expanded to 'git status'.

abbrevs['gp'] = "git push <edit> --force"  # Set the edit position.
abbrevs['@'] = "@(<edit>)"  # Make shortcut.
abbrevs['...'] = "cd ../.."  # Workaround for syntax intersections with Python i.e. `elepsis` object from Python here.

# You can set a callback that receives current command buffer and word that triggered abbrev
abbrevs['*'] = lambda buffer, word: "asterisk" if buffer.text.startswith('echo') else word
ls *  # will stay
echo *  # will be transformed to `echo asterisk`Path stringsThe p-string returnsPath object:path=p'~/.xonshrc'path# Path('/home/snail/.xonshrc')[path.name,path.exists(),path.parent]# ['.xonshrc', True, Path('/home/snail')][fforfinpath.parent.glob('*')if'xonsh'inf.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')A 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 regexpToNormal globbingaddgbefore back quotes:ls*.*lsg`*.*`forfingp`/tmp/*.*`:# `p` is to return path objectsprint(f.name)forfingp`/tmp/*/**`:# `**` is to glob subdirectoriesprint(f)ToRegular Expression Globbingaddrbefore back quotes:ls`.*`lsr`.*`forfinrp`.*`:# `p` is to return path instancesprint(f.exists())ToCustom function globbingadd@and the function name before back quotes:deffoo(s):return[iforiinos.listdir('.')ifi.startswith(s)]cd/@foo`bi`#['bin']MacrosSimple macrosdefm(x:str):returnx# 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!(importos)]# ["'me'", '42', 'identity', '42', '42', 'import os']m!(ifTrue:pass)# 'if True:\n    pass'Subprocess Macrosecho!"Hello!"# "Hello!"bash-c!echo"Hello!"# Hello!dockerrun-it--rmxonsh/xonsh:slimxonsh-c!2+2# 4Inside of a macro, alladditional mungingis turned off:echo$USER# louecho!$USER# $USERMacro blockBuiltin macro Blockfromxonsh.contextsimportBlockwith!Block()asb:qweasdzxcb.macro_block# 'qwe\nasd\nzxc\n\n'b.lines# ['qwe', 'asd', 'zxc', '']Custom JSON blockimportjsonclassJsonBlock:__xonsh_block__=strdef__enter__(self):returnjson.loads(self.macro_block)def__exit__(self,*exc):delself.macro_block,self.macro_globals,self.macro_localswith!JsonBlock()asj:{"Hello":"world!"}j['Hello']# world!Custom Docker blockThe example is fromxontrib-macro-lib:fromxonsh.contextsimportBlockclassDoxer(Block):"""Run xonsh codeblock in docker container."""def__init__(self):self.docker_image='xonsh/xonsh:slim'def__exit__(self,*a,**kw):$[dockerrun-it--rm@(self.docker_image)/usr/local/bin/xonsh-c@(self.macro_block)]with!Doxer()asd:pipinstalllolcatecho"We're in docker container now!"|lolcatMacro blocks librarySee alsoxontrib-macro-lib.Tab-Completioncompleterlist# List the active completersTake a look intoxontrib-fish-completer- it provides more rich completion than default bash completer.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 dummyBind hotkeys in prompt toolkit shellUncover the power ofprompt_toolkitbybindingthehotkeys. Run this snippet or add it to~/.xonshrc:fromprompt_toolkit.keysimportKeys@events.on_ptk_createdefcustom_keybindings(bindings,**kw):# Press F1 and get the list of files@bindings.add(Keys.F1)# or for Mac try `@bindings.add("c-k")  # control+k`defrun_ls(event):ls-levent.cli.renderer.erase()# Press F3 to insert the grep command@bindings.add(Keys.F3)# or for Mac try `@bindings.add("c-k")  # control+k`defadd_grep(event):event.current_buffer.insert_text('| grep -i ')# Clear line by pressing `Escape` key@bindings.add("escape")defclear_line(event):event.current_buffer.delete_before_cursor(1000)See also:more about key bindings,event.current_buffer.Xontrib- extension or plugin for xonshXontrib lists:Github topicGithub repositoriesawesome-xontribsTo install xontribs xonsh hasxpip- a predefined alias pointing to the pip command associated with the Python executable running this xonsh. Usingxpipis the right way to install xontrib to be confident that the xontrib will be installed in the right environment.If you want to create your own xontribusing xontrib-templateis the best way:xpipinstallcopierjinja2-timecookiecuttercopiergh:xonsh/xontrib-template.Xonsh Script (xsh)Real-life example of xsh script that has: arguments, tab completion for arguments (usingxontrib-argcomplete), subprocess calls with checking the result, colorizing the result and exit code:#!/usr/bin/env xonsh# PYTHON_ARGCOMPLETE_OKimportargparseimportargcomplete# Tab completion support with xontrib-argcompletefromargcomplete.completersimportChoicesCompleter$RAISE_SUBPROC_ERROR=True# Raise an error if a subprocess returns a non-zero exit status.# Read more: https://xon.sh/envvars.html#raise-subproc-errorargp=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()ifresult:=!(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 messagesexit(1)# Exit with code number 1Try it in action:xonshpipinstallargcompletexontrib-argcompletexontribloadargcompletecd/tmpwgethttps://raw.githubusercontent.com/anki-code/xonsh-cheatsheet/main/examples/host_lines.xshchmod+xhost_lines.xsh./host_lines.xsh--ho<Tab>./host_lines.xsh--host<Tab>./host_lines.xsh--hostxon.sh# Count of lines on xon.sh: 568To make the interaction with script more awesomeuseclick,typerandrich:"""Example of CLI application."""

import click

class OrderCommands(click.Group):
  def list_commands(self, ctx: click.Context) -> list[str]:
    return list(self.commands)


class CliCtx:
    """CLI application sharable context."""
    def __init__(self, debug):
        self.debug = debug

        if self.debug:
            # Show arguments in debug mode.
            from pprint import pprint
            pprint(vars(self))


@click.group(cls=OrderCommands)  # The list of commands in `--help` will be the same as the functions declarations order.
@click.option("--debug", default=False, is_flag=True, envvar='PROJECT_DEBUG', help="Debug mode.")
@click.pass_context
def cli(ctx, debug):
    """
    Description of the CLI app.
    """
    ctx.obj = CliCtx(debug)

    if debug:
        $XONSH_TRACE_SUBPROC = True


@cli.command()
@click.option("--use-print", default=False, is_flag=True, envvar='PROJECT_HELLO_PRINT', help="Use print() function.")
@click.pass_obj
def hello(ctx, use_print):
    if ctx.debug:
        print('debug: run hello')

    if use_print:
        print('world')
    else:
        echo world


if __name__ == "__main__":
    cli()Save this to./cli.xshand run:chdmo +x ./cli.xsh
./cli.xsh --debug hello --use-print
# {'debug': True}
# worldHow to get the script pathGet the script path from$ARGS[0]:echo @("""echo This script is in @(pf"{$ARGS[0]}".parent)""") > /tmp/getpath.xsh
chmod +x /tmp/getpath.xsh
/tmp/getpath.xsh
# This script is in /tmpHistoryThere are two history backends:jsonandsqlitewhich xonsh has by default. Thejsonbackend creates a json file with commands history on every xonsh session. Thesqlitebackend has one file with SQL-database.We recommend using thesqlitebackend because it saves the command on every execution, and querying of the history using SQL is very handy i.e.history-search, history-pull.echo123# 123__xonsh__.history[-1]# HistoryEntry(cmd='echo 123', out='123\n', rtn=0, ts=[1614527550.2158427, 1614527550.2382812])historyinfo# 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 123aliases['history-search']="""sqlite3 $XONSH_HISTORY_FILE @("SELECT inp FROM xonsh_history WHERE inp LIKE '%" + $arg0 + "%' AND inp NOT LIKE 'history-%' ORDER BY tsb DESC LIMIT 10");"""cd/tmphistory-search"cd /"# cd /tmphistory-search!cd/# macro call# cd /tmppipinstallsqlite_websqlite_web$XONSH_HISTORY_FILE# Open the database in the browserhistorypull# Pull the history from parallel sessions and add to the current session. [xonsh -V > 0.13.4]There is a third party history backend that's supplied in xontribs:xontrib-history-encrypt.Interactive mode eventsWhen you're in xonsh interactive mode you can register an event, i.e.:@events.on_chdirdefmychdir(olddir,newdir,**kw):echoJumpfrom@(olddir)to@(newdir)cd/tmp# Jump from /home/snail to /tmpHelpAdd?(regular help) or??(super help) to the command:ls?# man page for lsimportjsonjson?# json module helpjson??# json module super helpKnown issues and workaroundsModuleNotFoundErrorSometimes when you're using PyPi, Conda, or virtual environments you can forget about the current version and location of Python and try to import packages in xonsh resulting in aModuleNotFoundErrorerror. Often this means you installed the package in another environment and didn't realise it. To avoid this read the section about xonsh installation above.Intersection of console tools or shell syntax with Python builtinsIn case of names or syntax intersection try to use aliases orabbrevsto resolve the conflict.The case withelepsis:aliases['...'] = 'cd ../..'  # looks nice, but
...
# Elepsis

del aliases['...']
abbrevs['...'] = 'cd ../..'
...  # becomes `cd ../..`The case withimport:cd /tmp
$PATH.append('/tmp')
echo 'echo I am import' > import && chmod +x import

import  # Run subprocess `./import`
# I am import

import args  # Run Python import of `args` module
# ModuleNotFoundError: No module named 'args'

aliases['imp'] = "import"
imp
# I am importFrozen terminal in interactive toolsIf you run a console tool and get a frozen terminal (Ctrl+c, Ctrl+d is not working) this can be that the tool was interpreted as threaded and capturable program but the tool actually has interactive elements that expect the input from the user. There are four workarounds now:DisableTHREAD_SUBPROCS:with${...}.swap(THREAD_SUBPROCS=False):./tool.shRun the tool in uncaptured mode:$[./tool.sh]Set the unthreadable predictor:__xonsh__.commands_cache.threadable_predictors['tool.sh']=lambda*a,**kw:False# use the pure name of the tool./tool.shFinally, check$XONSH_CAPTURE_ALWAYSvalue.Uncaptured outputIf you want to capture the output of a tool but it's not captured, there are three workarounds:Add theheadorcattool at the end of the pipeline to force using the threadable mode:!(echo123|head-n1000)#CommandPipeline(#  returncode=0,#  output='123\n',#  errors=None#)Change threading prediction for this tool:__xonsh__.commands_cache.threadable_predictors['ssh']=lambda*a,**kw:True!(sshhost-T"echo 1")#CommandPipeline(#  returncode=0,#  output='1\n',#  errors=None#)Wrap the tool into a bash subprocess:!(bash-c"echo 123")#CommandPipeline(#  returncode=0,#  output='123\n',#  errors=None#)Bad file descriptorUsing callable aliases in a long loop can cause theBad file descriptorerror to be raised. The workaround is to avoid using callable aliases in the loop and moving the code from callable alias directly into the loop ormarking the callable alias as unthreadable:fromxonsh.toolsimportunthreadable@unthreadabledef_e():execx('echo -n 1')aliases['e']=_eforiinrange(100):eTips and tricksMake your own installable xonsh RC fileStart by forkingxontrib-rc-awesome.Triple quotesTo avoid escape character (i.e.echo "\"hello\"") and make string more elegant use triple quotes:echo """{"hello":'world'}"""
# {"hello":'world'}Python warlus operator in subprocess modeecho Hello @(_name := input('Name: '))  # Use `_` to keep env clean.
echo Hello again @(_name)
# Name: Mike
# Hello Mike
# Hello again MikeUsing a text block in the command lineThe first way is to use multiline strings:echo @("""
line 1
line 2
line 3
""".strip()) > file.txt

$(cat file.txt)
# 'line 1\nline 2\nline 3\n'The second way is to use xonsh macro block viaxontrib-macro:xpip install xontrib-macro

from xontrib.macro.data import Write

with! Write('/tmp/t/hello.xsh', chmod=0o700, replace=True, makedir=True, verbose=True):
    echo world
    
/tmp/t/hello.xsh
# worldRun commands in docker:dockerrun-it--rmxonsh/xonsh:slimxonsh-c@("""pip install --disable-pip-version-check -q lolcatecho "We're in docker container now!" | lolcat""")Don't forget thatAlt+Entercan run the command from any place where cursor is.Using the name of the alias in alias logic@aliases.register(",")
@aliases.register(",,")
@aliases.register(",,,")
@aliases.register(",,,,")
def _superdot():
    cd @("../" * len($__ALIAS_NAME))
    
,    # cd ../
,,   # cd ../../
,,,  # cd ../../../Python sugar: inline import# Run it or add to ~/.xonshrc
imp = type('MyClass', (object,), {'__getattr__':lambda self, name: __import__(name) })()

# Use `imp` as inline import sugar
imp.json.loads('{"a":1}')
# {'a': 1}
imp.datetime.datetime.now().isoformat()
# '2024-02-12T15:29:57.125696'
imp.hashlib.md5(b'Hello world').hexdigest()
# '3e25960a79dbc69b674cd4ec67a72c62'Ask to input argument and with autocompleteAsk simple input:echo @(input('Text: '))
# Text: hello
# hello

echo Hello @(_name := input('Name: '))  # Use `_` to keep env clean.
echo Hello again @(_name)
# Name: Mike
# Hello Mike
# Hello again Mike

$ENV_NAME = input('Name: ')  # Use input to set and reuse env variable
echo Name is $ENV_NAME
# Name: Alex
# Name is AlexAsk input with completion:from prompt_toolkit import PromptSession
from prompt_toolkit.completion import WordCompleter

def ask(arg : str, completions : list = []):
    completer = WordCompleter(completions)
    session = PromptSession(completer=completer)
    user_input = session.prompt(f'{arg}: ')
    return user_input


echo I am saying @(ask('What to say'))
# What to say: hello
# I am saying hello

echo Give @(ask('Fruit', ['apple', 'banana', 'orange'])) to @(ask('To', [$(whoami).strip()]))
# Fruit: <Tab>
# Fruit: apple
# To: <Tab>
# To: user
# Give apple to user

$MY_DIR = ask('Dir', $(ls /).splitlines())
# Dir: <Tab>From the shell to REST API for one stepIf you want to run shell commands from REST API you can create aflaskwrapper usingxontrib-macro:xpip install flask xontrib-macro

cd /tmp

from xontrib.macro.data import Write
with! Write('myapi.xsh', chmod=0o700):
    import json
    from flask import Flask
    app = Flask(__name__)
    @app.route('/echo')
    def index():
        result = $(echo -n hello from echo)  # run subprocess command
        return json.dumps({'result': result})
    app.run()

./myapi.xsh
# Running on http://127.0.0.1:5000

curl http://127.0.0.1:5000/echo
# {"result": "hello from echo"}Don't forgetabout API security.Interactively debugging a scriptIf you want to have a breakpoint to debug a script, use the standard Pythonpdb:xpip install xontrib-macro
from xontrib.macro.data import Write
with! Write('/tmp/run.xsh', chmod=0o700, replace=True, makedir=True):
    echo hello
    $VAR = 1
    var = 2

    import pdb
    pdb.set_trace()   # interactive debug

    echo finish


xonsh /tmp/run.xsh
# hello
# > /tmp/run.xsh(9)<module>()
# -> echo finish
# (Pdb)

var
# 2

__xonsh__.env['VAR']
# 1

exit
# bdb.BdbQuitUsing xonsh wherever you go through the SSHYou've stuffed your command shell with aliases, tools, and colors but you lose it all when using ssh. The mission of thexxh projectis to bring your favorite shell wherever you go through ssh without root access or system installations.How to modify a command before execution?To change the command between pressing enter and execution there is theon_transform_commandevent:xpipinstalllolcat@events.on_transform_commanddef_(cmd,**kw):ifcmd.startswith('echo')and'lolcat'notincmd:# 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 can make a mistake here and# end up with unintended looping.returncmd.rstrip()+' | lolcat'else:returncmdecho123456789# <Enter># Execution: echo 123456789 | lolcatComma separated thousands in output (custom formatter)Here is a snippet from@maxwellfire:50000+50000
# 100000

500+500.123
# 1000.123

import xonsh.pretty
xonsh.pretty.for_type(type(1), lambda int, printer, cycle: printer.text(f'{int:,}'))
xonsh.pretty.for_type(type(1.0), lambda float, printer, cycle: printer.text(f'{float:,}'))

50000+50000
# 100,000

500+500.123
# 1,000.123chdircontext managerfor scriptingfrom xonsh.tools import chdir

cd /tmp
mkdir -p dir1

pwd
with chdir("./dir1"):
    pwd
pwd

# /tmp
# /tmp/dir1
# /tmpJuggling of exit code using python substitutioncd-ing into directory and if count of files less then 100 runls:aliases['cdls'] = "cd @($arg0) && @(lambda: 1 if len(g`./*`) > 100 else 0) && ls"
cdls / && pwd
# bin dev etc ...
# /
cdls /usr/sbin && pwd
# /usr/sbinHow to paste and edit multiple lines of code while in interactive modeIn some terminals (Konsole in Linux or Windows Terminal for WSL) you can pressctrl-x ctrl-eto open up an editor (nanoin 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 donesleep100&# job 1sleep100&# job 2sleep100&# job 3while$(jobs):time.sleep(1)print('Job done!')How to trace xonsh code?Trace withhunter:pipinstallhunter$PYTHONHUNTER='depth_lt=10,stdlib=False'$XONSH_DEBUG=1xonsh-c'echo 1'Or tryxunterfor tracing and profiling.From Bash to XonshReadBash to Xonsh Translation Guide, runbash -c! echo 123or installxontrib-sh.Xonsh and WindowsFirst of all we recommend usingWSL 2withManjaro(that maintains arolling release) on Windows. Don't forget tofix PATH.But if you want to use xonsh in Windows environment:InstallWindows Terminal- the modern terminal emulator for Windows.Installxontrib coreutils,cmdix,pycoreutils- a pure Python implementation of the UNIX coreutils i.e.echo,cat,pwd,ls, etc.ReadWindows-specific tips and tricks.RecipesUsing many profiles with AWS CLI and xonsh aliasesaws configure --profile p1
aws configure --profile p2

aliases['aws-p1'] = "$AWS_DEFAULT_PROFILE='p1' @('aws') @($args)"
aliases['aws-p2'] = "$AWS_DEFAULT_PROFILE='p2' @('aws') @($args)"

aws-p2 s3 ls s3://my-profile1-bucket/  # The same as `aws s3 ls --profile p2 s3://my-profile1-bucket/`Answers to the holy war questionsBash is everywhere! Why xonsh?Python is everywhere as well ;)Xonsh is slower! Why xonsh?You can spend significantly more time Googling and debugging sh-based solutions as well as significantly more time to make the payload work after running a command. Yeah, xonsh is a bit slower but you will not notice that in real life tasks :)Also take a look:Python 3.12: A Game-Changer in Performance and EfficiencyPython 3.11 is up to 10-60% faster than Python 3.10Making Python 5x FASTER with Guido van Rossum.RustPython(performanceofRuff- Python linter on Rust)My fancy prompt in another shell is super duper! Why xonsh?The fancy prompt is the tip of the iceberg. Xonsh shell brings other important features to love:sane language,powerful aliases,agile extensions,history backends,fully customisable tab completion,magic macro blocks,behaviour customisation via environment variables, andmore, andmore, andmore:)Xonsh has issues! Why xonsh?Compared to 15-20 year old shells, yeah, xonsh is a 5 year old youngster. But we've used it over these 5 years day by day to solve our tasks with success and happiness :)Thank you!Thank you for reading! This cheatsheet is the tip of the iceberg of the xonsh shell and you can find more in theofficial documentation.Also you can install the cheatsheet xontrib:xpipinstallxontrib-cheatsheetxontribloadcheatsheetcheatsheet# Opening: https://github.com/anki-code/xonsh-cheatsheet/blob/main/README.mdIf you like the cheatsheet, click ⭐ on the repo andtweet.CreditsXonsh TutorialMost copy-pastable examples prepared byxontrib-hist-formatThe cheat sheet xontrib was created withxontrib cookiecutter template.