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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions