A shell script and a small C program both run correctly on Linux but misbehave on another Unix system such as macOS or AIX. What does POSIX actually guarantee, and what does it deliberately leave unspecified that would explain failures like these?
answer
- source portability, not binary
- the standard names a minimum
- vendor flags sit outside it
- GNU userland versus BSD userland
- locale silently changes sort order
basics
~20 sPOSIX guarantees source-level portability: a C system-interface API, the shell command language, and a set of utilities with a required minimum of options and behaviours. It says nothing about vendor extensions, binary compatibility, or kernel-specific interfaces, which is where portable-looking code breaks.
solid answer
~50 sPOSIX, IEEE Std 1003.1, standardises three surfaces: the C system interfaces, the shell command language, and a catalogue of utilities together with the options each must support. Crucially it specifies a *minimum* and it specifies *source* portability — recompiling, not running the same binary. Everything a vendor adds beyond that minimum is outside the standard, and that is nearly always the cause of these failures. Linux distributions ship the GNU userland, whose utilities carry many extensions; macOS and the BSDs ship a BSD-derived userland that does not have them. So `sed -i` without an argument, `date -d`, GNU long options and `echo -e` all work on one and fail on the other. On the C side, `epoll` is Linux-only and `kqueue` is BSD and macOS only — neither is POSIX, which offers `select` and `poll`. Locale settings change sorting and character ranges too, so the same script sorts differently under a different `LC_COLLATE`.
code
bash · 8 lines# Unspecified by POSIX: echo's handling of options and escapes
echo -e "a\tb"
# Portable across conforming Unix systems
printf 'a\tb\n'
# Deterministic ordering regardless of the machine's locale
LC_ALL=C sort file.txtgo deeper
Know that POSIX is the written standard for Unix interfaces — a C API, the shell language and a set of utilities — and that macOS and Linux ship different versions of the same-named commands.
Explain that the standard defines a minimum and covers source rather than binary portability, and give a real example of a vendor extension such as an option that exists in GNU sed or date but not in the BSD equivalent.
Diagnose from symptoms: identify whether the break is a utility extension, unspecified behaviour, a locale difference or a kernel-only interface, and describe how you would verify a fix on a second userland rather than assuming it.
Set the policy. Decide which platforms are genuinely supported, how that is enforced in build and test, when to abstract over kernel-specific interfaces versus standardising on one target image, and what portability actually costs the team.
## What POSIX is POSIX is IEEE Std 1003.1, developed by the Austin Group and published jointly with The Open Group, where it also forms the core of the Single UNIX Specification. It exists because the Unix family fragmented in the 1980s into incompatible commercial variants, and customers wanted a written contract for what "Unix" meant. The current edition is POSIX.1-2024, also known as Issue 8 of the Single UNIX Specification; POSIX.1-2017 is the previous edition and is still what a great deal of documentation cites. It covers three volumes' worth of surface: - **System interfaces** — the C API: `open`, `read`, `fork`, `execve`, `waitpid`, `sigaction`, `pthread_*`, `select`, `poll`, and the headers that declare them, with defined error numbers and behaviours. - **Shell and utilities** — the shell command language itself (grammar, expansion, quoting, redirection) plus a catalogue of utilities such as `sed`, `awk`, `grep`, `find`, `sort`, `printf`, each with the options and semantics an implementation must provide. - **Environment and locale behaviour** — environment variables, locale categories, character-set handling. ## What it deliberately does not cover **Binary compatibility.** POSIX is a *source*-level contract. There is no promise that an executable built on one conforming system runs on another; that is an ABI question, and ABIs are per-platform. Two POSIX systems can differ in calling convention, object format and libc entirely. **Anything above the minimum.** The standard says what an implementation must accept, not what it must refuse. A vendor may add options freely, and every major vendor has. GNU coreutils in particular is far larger than the POSIX catalogue. Writing against the union of everything installed on your machine is the single commonest way to produce non-portable code that looks fine. **Kernel-specific interfaces.** Event notification is the standard example: POSIX gives you `select` and `poll`; Linux adds `epoll`, FreeBSD and macOS add `kqueue`, and neither is in the standard. Filesystem-change notification, process introspection through a `/proc` filesystem, containers, capability models and resource-control mechanisms are all outside POSIX entirely. **System organisation.** Package management, service supervision and init, the location of most files, the network configuration model, and the machine's boot process are not POSIX concerns. Only a handful of paths are actually specified — `/dev/null`, `/dev/tty`, `/dev/console` and `/tmp` among them. ## The concrete failures behind the question **GNU userland versus BSD userland.** macOS ships BSD-derived utilities. `sed -i 's/a/b/' f` works on Linux because GNU sed makes the backup suffix optional; BSD sed requires an explicit argument, so it consumes the script as the suffix and fails. `date -d 'yesterday'` is a GNU extension; BSD `date` uses `-v` for adjustment and `-j -f` for parsing. GNU long options such as `--recursive` are extensions. `grep -P` for Perl-compatible patterns is GNU-only. **Unspecified behaviour that happens to work.** `echo -e` is the classic: POSIX leaves `echo`'s treatment of options and backslash escapes unspecified precisely because implementations disagreed, and directs you to `printf` when you need escapes. Code that relies on `echo -e` is not portable even though it is ubiquitous. ```sh # unspecified across Unix systems echo -e "a\tb" # portable printf 'a\tb\n' ``` **Which shell `/bin/sh` actually is.** POSIX defines the shell *language*; it does not say which program provides it. Some systems point `/bin/sh` at a small strictly-POSIX shell, others at a larger shell running in a compatibility mode. A script whose shebang says `sh` but which uses non-POSIX syntax works on one and dies on the other. **Locale.** Sorting order, character ranges in bracket expressions, and case conversion are locale-dependent. The same `sort` on the same input yields different order under a different `LC_COLLATE`, which is why deterministic pipelines set `LC_ALL=C`. **C-side surprises.** Functions that feel standard often are not: `strlcpy` originated in BSD and was absent from GNU's C library for a very long time. The presence of a `/proc` filesystem, the exact set of `errno` values a call can return, and sizes of types beyond what the language standard fixes are all places where "it compiled on my machine" fails. ## How to actually get portability Decide your target set explicitly, then test against it — a conformance claim is not a substitute for running the code on a second system. Prefer the POSIX-specified option set and `printf` over `echo`; pin locale where ordering matters; feature-test rather than assume in C, and guard platform-specific calls behind a compile-time selection with a portable `poll`-based fallback. And be honest in the interview that in current practice most teams choose *one* target and control it with an image, rather than chasing full portability — knowing where the standard's edge is matters most for the moments when you cross it.
- If both systems are POSIX-conforming, why can't I just ship the same compiled binary?Because POSIX is a source-level contract. It defines the interfaces your code calls, not the object format, calling convention, dynamic linker or C library the platform uses. Two conforming systems can differ in every one of those. Binary portability requires a shared ABI, which is a per-platform guarantee made by the platform, not by POSIX.
- How would you find out whether a utility option you are relying on is actually standard?Check the utility's own POSIX specification rather than the manual page in front of you. GNU manual pages document the union of standard and extension behaviour, so they cannot tell you which is which; the standard's utility description lists exactly the options an implementation must support. Testing the script against a second, non-GNU userland is the practical confirmation.
- Why do teams so often set LC_ALL=C in scripts that must produce stable output?Because collation, character ranges and case handling are locale-dependent. Under one locale a sort groups accented characters with their base letters and ignores punctuation; under the C locale it compares bytes. Any pipeline whose output feeds a diff, a checksum or an ordered comparison needs the ordering pinned, or the same input yields different results on different machines.
- Where does POSIX leave you with nothing at all on a modern system?Anywhere the platform innovated after the standard settled: scalable event notification, filesystem-change notification, process and system introspection filesystems, resource control and isolation, service supervision, and package management. For those you write to a specific kernel's interface and abstract it yourself, keeping a poll-based fallback if you genuinely need a second platform.
saying these in an interview costs you the question
- Thinks POSIX conformance means binaries are interchangeable
- Assumes any option in the GNU manual page is standard
- Says macOS is not POSIX because it lacks GNU tools
- Believes echo -e is portable because it works everywhere they tried
- Ignores locale as a source of differing output