Make Shell Scripts Chroot-Aware with ischroot
A build script that behaves differently inside a chroot needs a reliable way to ask, and ischroot answers through its exit status alone. You will finish with a small, reliable check plus a clear response for the case where detection is not possible at all. 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 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 contract stays simple: exit status 0 means the process is in a chroot, 1 means 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 only useful output.
Checkpoint
Do not test this with ischroot && echo yes alone. That only distinguishes status 0 from every non-zero status, which 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 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 either way, 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 actually 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 only become relevant 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 throws away 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. That makes it a reasonable guard before a command that behaves differently in a build environment, but the guard itself is not a security boundary: a script must still validate paths, privileges and inputs before it changes 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 here, because these examples make no state changes. If you later add a package install, file removal or service restart after the guard, treat that as a separate operation: preview its target, use the least privilege needed and give it its own recovery path.
5. Diagnose the common mistakes
- "ischroot: command not found". The program is not available through
PATH. Check withcommand -v ischrootand install or repair Debianutils through your normal package-management process. Do not replace the detector with a guessed file test. - Every non-zero result treated as "outside". That silently converts status 2 into status 1. It 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.
- Unexpected output appears. Run
ischroot --helporischroot --versionseparately: 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
- Verified the installed Debianutils and
ischrootversions. - Distinguished status 0, status 1 and status 2 in your check.
- Captured
$?immediately after the detector runs. - Used a default option only when collapsing uncertainty was an intentional policy.
- Kept any later privileged or destructive action outside the detection step.