Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/bash
- # Version 24 — AI Enhanced™ Edition
- # Unified Python launcher for Linux
- #
- # BEHAVIOR:
- # *.pyw → run inline, open terminal only on error
- # *.py → always open terminal, run Python inside terminal
- #
- # FEATURES:
- # - Auto activation of venv-installed libraries (script-local, cwd-local, global)
- # - Correct error reporting to oh-my-posh (useful if your theme displays status codes), and terminal "Up arrow key" history will contain the command that ran the script.
- # - rcfile auto-cleanup (no leftover /tmp files)
- # - *.pyw script support (for GUI/Tkinter and headless scripts if ye prefer invisible ghost scripts running in the background)
- #
- # INSTALL:
- # 1. $ sudo cp ./python.sh /usr/local/bin/
- # 2. $ sudo chmod +x /usr/local/bin/python.sh
- #
- # USAGE:
- # $ python.sh
- # $ python.sh ./Script.py
- # $ python.sh ./Script.pyw
- # Dolphin Configuration: <-- So you can double click .py and .pyw files to run them.
- #
- # Open Dolphin, right-click any .py file > Properties > Open With > Click Change...
- # Filename Patterns:
- # Click "Add..."
- # Extension: *.pyw
- # Click OK
- #
- # Application Preference Order:
- # Click "Add..."
- # Click [Open file dialog] button > /usr/local/bin/python.sh
- # [x] Remember application association for all files of type "Python 3 script" (text/x-python3)
- # NOTE: "python.sh" needs to be the topmost item in the list for double clicked scripts to run with it.
- # If it isn't then click select "python.sh" and click "Move Up" until it is.
- # Click Apply.
- # Click OK.
- # KDE Menu Editor Configuration: <-- So you can create shortcuts to *.py and *.pyw scripts.
- #
- # Click New > New Item...
- # Name: <Script Name>
- # Icon: <Path to icon.svg>
- # Program: /usr/local/bin/python.sh
- # Command-Line Arguments: <Path to script.py> or <Path to script.pyw>
- # [ ] Enable launch feedback
- # Offtopic notes for creating ~/.venv and installing python libraries there with pip:
- #
- # Venv (virtual environment) creation:
- # $ rm -r ~/.venv <-- Remove any existing venv folder at the destination.
- # $ python3 -m venv ~/.venv <-- Needs to be done on a linux partition (not ntfs apparently) or else "which pip" returns "/usr/bin/pip".
- # $ source ~/.venv/bin/activate <-- Activate the venv.
- # $ which python <-- Verify python is ~/.venv/bin/python
- # $ which pip <-- Verify pip is ~/.venv/bin/pip
- #
- # Install Python libraries into the active .venv with pip:
- # $ source ~/.venv/bin/activate <-- Activate the venv.
- # $ pip install keyboard pyttsx3
- #
- # Making my scripts importable from other scripts: <-- This new venv approach won't break when updating python versions.
- # $ source ~/.venv/bin/activate <-- Activate the venv.
- # $ SITEDIR=$(python -c "import sys; print([p for p in sys.path if 'site-packages' in p][0])") <-- NOTE: The typical command for this doesn't work with venv: $ SITEDIR=$(python -m site --user-site)
- # $ mkdir -p "$SITEDIR" <-- Create site directory if it doesn't exist.
- # $ echo "/media/D/Users/Wolf/Files/Archives/Scripts/Python" >> "$SITEDIR/wolf-scripts.pth"
- # $ python -m site <-- You should see the path you just added in this commands output.
- #
- # Running python scripts from the terminal that rely on venv installed libraries:
- # $ source ~/.venv/bin/activate
- # $ python myscript.py
- #
- # Or
- #
- # $ python.sh myscript.py <-- Relying on the script.
- ###############################################
- # VENV RESOLUTION
- ###############################################
- # Try three possible venv locations in priority order:
- # 1. Script directory's .venv
- # 2. Current directory's .venv
- # 3. Global ~/.venv
- for d in "$(dirname "$(readlink -f "$1" 2>/dev/null)")/.venv" "./.venv" "$HOME/.venv"; do
- # If activate script exists, source it and stop searching.
- [ -f "$d/bin/activate" ] && source "$d/bin/activate" && break
- done
- ###############################################
- # TERMINAL DETECTION
- ###############################################
- # Detect the first available terminal emulator.
- # Each terminal has its own syntax for executing a command.
- for t in kitty konsole alacritty wezterm gnome-terminal xfce4-terminal; do
- if command -v "$t" &>/dev/null; then
- case "$t" in
- kitty|konsole|alacritty)
- TERM=("$t" -e) ;; # These use "-e <cmd>".
- wezterm)
- TERM=(wezterm start --) ;; # WezTerm uses "start -- <cmd>".
- gnome-terminal)
- TERM=(gnome-terminal --) ;; # GNOME Terminal uses "-- <cmd>".
- xfce4-terminal)
- TERM=(xfce4-terminal --execute) ;; # XFCE Terminal uses "--execute <cmd>".
- esac
- break
- fi
- done
- # If no terminal found, abort.
- [ -z "$TERM" ] && echo "No terminal" && exit 1
- ###############################################
- # NO ARGUMENTS → OPEN PYTHON REPL
- ###############################################
- # Running the launcher with no arguments opens an interactive Python session.
- [ $# -eq 0 ] && exec "${TERM[@]}" python
- ###############################################
- # SCRIPT PATH + ARGUMENT RESOLUTION
- ###############################################
- # Resolve script path even if filename contains spaces.
- # We progressively join tokens until we find a valid file.
- _find() {
- local a=("$@") n=${#a[@]}
- for ((i=1;i<=n;i++)); do
- local p="${a[*]:0:i}" # Join tokens 0..i-1 with spaces.
- if [ -e "$p" ]; then
- SCRIPT=$(readlink -f "$p") # Resolve absolute path.
- ARGS=("${a[@]:i}") # Remaining tokens are arguments.
- return
- fi
- done
- # Fallback: treat first token as script.
- SCRIPT=$(readlink -f "${a[0]}")
- ARGS=("${a[@]:1}")
- }
- _find "$@"
- # Quote arguments safely for shell injection.
- # This block is to prevent the creation of an '' empty string arg to the script.
- # printf '%q ' on a zero-length array still runs the format once and
- # quotes a "missing" arg as an empty string, i.e. it emits the literal
- # token '' instead of nothing. Left unguarded, that stray '' gets embedded
- # in the generated rc file / inline command line and is then parsed as a
- # genuine (spurious) empty-string argument to the Python script.
- # Guard it here so 0 args results in an empty ARGS_Q.
- if [ ${#ARGS[@]} -gt 0 ]; then
- ARGS_Q="$(printf '%q ' "${ARGS[@]}")"
- else
- ARGS_Q=""
- fi
- ###############################################
- # .PYW → INLINE EXECUTION (HEADLESS)
- ###############################################
- # Windows-style behavior: .pyw runs without opening a terminal, unless an error occurs.
- if [[ "${1,,}" == *.pyw ]]; then
- # Run Python inline and capture output + exit code.
- out=$(PYTHON_COLORS=1 FORCE_COLOR=1 python -- "$SCRIPT" $ARGS_Q 2>&1)
- st=$?
- # If error, open terminal to display error and drop into prompt.
- if (( st != 0 )); then
- # Create temporary rcfile for bash --init-file.
- rc=$(mktemp)
- cat > "$rc" <<EOF
- # Source user's shell configuration.
- [ -f ~/.bashrc ] && source ~/.bashrc
- # Print Python error output + blank line.
- printf "%s\n\n" "$out"
- # Export exit code for oh-my-posh.
- export PROMPT_LAST_EXIT=$st
- # Insert clean history entry (no stray '' on the end).
- if [ ${#ARGS[@]} -gt 0 ]; then
- history -s "python -- \"$SCRIPT\" $ARGS_Q"
- else
- history -s "python -- \"$SCRIPT\""
- fi
- # Delete this rcfile now that it has finished executing.
- rm -f "\$BASH_SOURCE" >/dev/null 2>&1 || true
- # Exit with Python's exit code so oh-my-posh detects failure.
- (exit $st)
- EOF
- # Open terminal running the rcfile.
- exec "${TERM[@]}" bash --init-file "$rc"
- fi
- # Inline execution exit code.
- exit $st
- fi
- ###############################################
- # .PY → ALWAYS OPEN TERMINAL
- ###############################################
- # Windows-style behavior: .py always opens a console window.
- rc=$(mktemp)
- cat > "$rc" <<EOF
- # Source user's shell configuration.
- [ -f ~/.bashrc ] && source ~/.bashrc
- # Run Python inside terminal (streams output live).
- PYTHON_COLORS=1 FORCE_COLOR=1 python -- "$SCRIPT" $ARGS_Q
- st=\$?
- # Always add a blank line after Python output.
- echo
- # Export exit code for oh-my-posh.
- export PROMPT_LAST_EXIT=\$st
- # Insert clean history entry (no stray '' on the end).
- if [ ${#ARGS[@]} -gt 0 ]; then
- history -s "python -- \"$SCRIPT\" $ARGS_Q"
- else
- history -s "python -- \"$SCRIPT\""
- fi
- # Delete this rcfile now that it has finished executing.
- rm -f "\$BASH_SOURCE" >/dev/null 2>&1 || true
- # Exit with Python's exit code so oh-my-posh detects failure/success.
- (exit \$st)
- EOF
- # Launch terminal running the rcfile.
- exec "${TERM[@]}" bash --init-file "$rc"
Advertisement
Add Comment
Please, Sign In to add comment