Make Shell Scripts Chroot-Aware with ischroot
You will finish with a small, reliable check for whether a command is running inside a chroot, plus a clear response to the case where detection is not possible. The examples use ischroot from Debianutils 5.17, installed here as package version 5.17build1.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about ten minutes. You need a shell and the Debianutils package. The checks are ordinary, read-only commands and do not need sudo. This guide detects the current process environment; it does not create, enter, leave or repair a chroot.
1. Check the installed command
Confirm that the command on your PATH is the one you expect, then read its version:
$ command -v ischroot
/usr/bin/ischroot
$ dpkg-query -W -f='${Package} ${Version}\n' debianutils
debianutils 5.17build1
$ ischroot --version
Debian ischroot, version 5.17
Package revisions vary by distribution, but the installed command's contract is simple: exit status 0 means that the process is in a chroot, 1 means that it is not, and 2 means detection was not possible. The command normally prints nothing for a detection result, so its exit status is the useful output.
Checkpoint: do not test this with ischroot && echo yes alone. That only distinguishes status 0 from every non-zero status, so it hides the difference between "not in a chroot" and "could not detect".
2. Read the result without losing status 2
Capture the status immediately after running the command. The assignment itself must not come first, because $? changes after every command:
$ ischroot
$ status=$?
$ case "$status" in
> 0) echo 'inside a chroot' ;;
> 1) echo 'not inside a chroot' ;;
> 2) echo 'chroot status could not be detected' ;;
> *) echo "unexpected ischroot status: $status" >&2; exit 1 ;;
> esac
not inside a chroot
The prompt characters in this block show a multi-line shell command; do not type the leading > characters. On a different host, the final line may instead say inside a chroot, or the command may return status 2.
For a script, turn the check into a function so callers can decide what an uncertain result means:
is_chroot() {
ischroot
case "$?" in
0) return 0 ;;
1) return 1 ;;
2) return 2 ;;
*) return 2 ;;
esac
}
if is_chroot; then
echo 'use the chroot-safe path'
else
status=$?
if [ "$status" -eq 2 ]; then
echo 'detection is uncertain' >&2
exit 1
fi
echo 'use the host path'
fi
That structure treats uncertainty as a deliberate policy decision. If your operation is harmless in either environment, you may choose to continue on status 2. If it touches host files, package databases or service controls, stopping is the safer default.
3. Choose a fallback only when detection fails
The long options --default-false and --default-true change only the response to status 2. They do not force the command to report that the current process is, or is not, in a chroot when detection succeeds.
$ ischroot --default-false
$ printf 'status: %s\n' "$?"
status: 1
$ ischroot --default-true
$ printf 'status: %s\n' "$?"
status: 1
In this ordinary host environment detection succeeds, so both options still return 1. That is expected. The option names become relevant only when the command cannot determine the answer. Use one when a caller cannot handle a third result:
if ischroot --default-false; then
echo 'inside a chroot'
else
echo 'treat as outside a chroot'
fi
This is useful for a compatibility branch, but it deliberately discards the distinction between "not in a chroot" and "detection failed". Prefer the three-way check when that distinction affects safety.
4. Keep detection separate from changes
ischroot does not enter a chroot and does not alter mounts, processes or files. It is therefore suitable as a guard before a command that has different behaviour in a build environment. The guard itself is not a security boundary: a script must still validate paths, privileges and inputs before changing anything.
For example, this prints a message and stops when the answer is uncertain, without running either branch:
status=0
ischroot
status=$?
case "$status" in
0) echo 'chroot branch selected' ;;
1) echo 'host branch selected' ;;
2) echo 'cannot safely select a branch' >&2; exit 1 ;;
*) echo 'unexpected detector failure' >&2; exit 1 ;;
esac
There is nothing to undo because these examples make no state changes. If you later add a package install, file removal or service restart after the guard, treat that later command as a separate operation: preview its target, use the least privilege needed and provide its own recovery path.
5. Diagnose the common mistakes
If the shell says ischroot: command not found, the program is not available through PATH. Check with command -v ischroot and install or repair Debianutils through your normal package-management process. Do not replace the detector with a guessed file test.
If a script treats every non-zero result as "outside", it has silently converted status 2 into status 1. That may be acceptable for a low-risk informational message, but it is a poor default before host-sensitive work. Record the original status before printing diagnostics or calling another command.
If output appears when you expected none, run ischroot --help or ischroot --version separately. Those options intentionally print to standard output and exit successfully. A normal detection result communicates through the status code, not a line of text.
Done means
- You verified the installed Debianutils and
ischrootversions. - Your check distinguishes status 0, status 1 and status 2.
- You capture
$?immediately after the detector runs. - You use a default option only when collapsing uncertainty is an intentional policy.
- You keep any later privileged or destructive action outside the detection step.