Skip to content

install.sh silently installs to the wrong location when piped to sh (dash) #68

@juliangall

Description

@juliangall

Summary

install.sh uses $EUID to decide whether to install to /usr/local/bin (root) or ~/.local/bin (user). $EUID is a bash-only variable — under POSIX shells like dash (the default /bin/sh on Debian/Ubuntu), it expands to an empty string. The script doesn't notice and falls through to the user-mode install path, even when run as root.

The script's shebang is #!/bin/bash, so executing it directly is fine. The problem only appears when it's piped to sh, which is a common copy-paste idiom across the ecosystem (e.g. curl ... | sudo sh).

Reproduce

On Debian 13 (trixie), as root, with /bin/sh symlinked to dash:

# curl -fsSL https://raw.githubusercontent.com/xdevplatform/xurl/main/install.sh | sh
sh: 10: [: Illegal number:
>> Starting installation...
>> Downloading latest release: xurl_Linux_arm64.tar.gz...
>> Installing to /root/.local/bin...
>> Installation complete! You can now run 'xurl' from anywhere.
>> /root/.local/bin is not in your PATH.
# echo "exit: $?"
exit: 0
# which xurl
(nothing)

The [: Illegal number: warning on line 1 is from dash failing to evaluate [ "$EUID" -eq 0 ] (because $EUID is empty). The script reports success and exits 0, but the binary lands in ~/.local/bin rather than /usr/local/bin as intended for root.

Expected

When run as root via any shell, the binary should be installed to /usr/local/bin.

If the shell is unsupported, the script should fail loudly and exit non-zero — not report a successful install.

Actual

  • [ "$EUID" -eq 0 ] errors under dash but is treated as a false branch (because set -e doesn't trip inside if conditions).
  • INSTALL_DIR falls through to ${HOME}/.local/bin.
  • The script reports "Installation complete!" and exits 0.
  • Anyone running ... | sudo sh ends up with the binary in root's home, not on the system PATH.

Root cause

if [ "$EUID" -eq 0 ]; then
    INSTALL_DIR="/usr/local/bin"
else
    INSTALL_DIR="${HOME}/.local/bin"
fi

$EUID is a bash builtin variable. It does not exist in dash, ash, or other POSIX sh implementations.

Suggested fix

Replace $EUID with the POSIX-portable equivalent:

if [ "$(id -u)" -eq 0 ]; then
    INSTALL_DIR="/usr/local/bin"
else
    INSTALL_DIR="${HOME}/.local/bin"
fi

id -u is in POSIX and works identically under bash, dash, ash, and zsh.

Optionally, add an explicit shell guard near the top so users piping to sh get a clear error instead of silent misbehaviour:

if [ -z "$BASH_VERSION" ]; then
    echo "Error: this script requires bash. Run it with: curl ... | bash" >&2
    exit 1
fi

Environment

  • OS: Debian 13 (trixie), arm64
  • Shell: /bin/sh → dash 0.5.12
  • Install command: curl -fsSL .../install.sh | sh
  • xurl install.sh: current main (commit at time of report)

Why it matters

  • The README correctly says | bash, but | sh and | sudo sh are widespread copy-paste patterns from other tools' install instructions, and users often substitute without noticing.
  • The failure is silent — exit 0, success message, no binary on PATH — which makes it hard to debug. A loud failure would be much friendlier.
  • The fix is one line.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions