
A Tutorial on Portable Makefiles - signa11
http://nullprogram.com/blog/2017/08/20/
======
erlehmann_
An issue I have with make is that it can not handle non-existence
dependencies. DJB noted this in 2003 [1]. To quote myself on this [2]:

> Especially when using C or C++, often target files depend on nonexistent
> files as well, meaning that a target file should be rebuilt when a previosly
> nonexistent file is created: If the preprocessor includes
> /usr/include/stdio.h because it could not find /usr/local/include/stdio.h,
> the creation of the latter file should trigger a rebuild.

I did some research on the topic using the repository of the game Liberation
Circuit [3] and my own redo implementation [4] … it turns out that a typical
project in C or C++ has lots of non-existence dependencies. How do make users
handle non-existence dependencies – except for always calling “make clean”?

[1] [http://cr.yp.to/redo/honest-nonfile.html](http://cr.yp.to/redo/honest-
nonfile.html)

[2] [http://news.dieweltistgarnichtso.net/posts/redo-gcc-
automati...](http://news.dieweltistgarnichtso.net/posts/redo-gcc-automatic-
dependencies.html)

[3] [https://github.com/linleyh/liberation-
circuit](https://github.com/linleyh/liberation-circuit)

[4] [http://news.dieweltistgarnichtso.net/bin/redo-
sh.html](http://news.dieweltistgarnichtso.net/bin/redo-sh.html) (redo-dot
gives a graph of dependencies and non-existence dependencies)

~~~
tinus_hn
Make has no memory so it can't remember things. It simply compares the dates
of files. If a dependency is newer than a target the target is rebuilt.

If you want to keep some kind of memory you have to build and keep track of it
yourself.

But the problem you point at is simply poor design. It is not a normal
occurrence for system header files to move around like you state. If they do,
a full rebuild is indeed required. It shouldn't so often that that is an
issue.

~~~
beagle3
If an apt-get upgrade fixed an issue in a system header or a library, but the
date of the fix predates the last build (quite common; last build from
yesterday, fix from two days ago but downloaded today) then make will do
nothing (or any subset of the right things but not all) and make clean; make
will do the right thing.

Relying on time stamps is a design decision that was good for its time, but it
is no longer robust (or sane) an a constantly connected, constantly updated,
everything networked world.

djb redo takes it to one logical conclusion (use cryptographic hashes to
verify freshness)

There are other ways in which make is lacking: operations are non atomic (redo
fixes that too), dependency granularity is file level (so dependency on
compiler flags is very hard, dependency on makefile is too broad; redo fixes
this too); dependency is manual (redo doesn't fix this; AFAIK the only one
that properly does is tup)

~~~
pm215
Typically if a system header has changed or been added due to upgrading a
library package you'll need to rerun any configure script anyway (since it
very likely checks for header features and those decisions will change). So
unless your build system magically makes configure depend on every header file
used in every configure test it runs, you'll need to redo a clean build
anyway, pretty much.

Make has a whole pile of issues, but this one really isn't an aggravation in
practice, I find.

~~~
beagle3
apt-get upgrade does not usually upgrade a package, despite the name; 99.9% of
the time it applies a bug or security fix, almost never changing any
functionality or interface - and would result in the same config script.

And that assumes you actually have a config script, which is also a nontrivial
assumption.

Djb redo lets you track e.g. security fixes that change libc.a if you are
linking statically, but that's not usually done.

The only build system I know that guaranteed a rebuild whenever and only when
it is needed is tup. (Assuming you have only file system inputs)

------
0x09
The problem comes in as soon as you need conditionals, which is likely when
attempting to build something portably. There may be some gymnastics that can
be done to write around the lack of their presence in standard make, but
otherwise your options are:

\- Supply multiple makefiles targeting different implementations

\- Bring in autotools in all its glory (at this point you are depending on an
external GNU package anyway)

\- Or explicitly target GNU Make, which is the default make on Linux and
macOS, is very commonly used on *BSD, and is almost certainly portable to
every platform your software is going to be tested and run on. The downside
being that BSD users need a heads up before typing "make" to build your
software. But speaking as a former FreeBSD user, this is pretty easy to figure
out after your first time seeing the flood of syntax errors.

~~~
SingletonIface
> But speaking as a former FreeBSD user, this is pretty easy to figure out
> after your first time seeing the flood of syntax errors.

I seem to be about the only person that makes use of the following feature,
but FreeBSD and GNU make will in addition to looking for a file named
Makefile, also look for BSDmakefile or GNUmakefile respectively.

So when I write a makefile with GNU make specific contents, I name it
GNUmakefile, and when I write one that is specific to FreeBSD make, I name it
BSDmakefile.

The user has to do absolutely nothing different; they simply write

    
    
        make
    

and if their make is GNU make and my makefile is a GNUmakefile then it builds.
Likewise with FreeBSD make and a file named BSDmakefile.

The big win is when someone then has the wrong make. Instead of beginning to
build and then failing at some point kicking and screaming, they will simply
be told

    
    
        make: no target to make.
    
        make: stopped in (path)
    

by FreeBSD make, or

    
    
        make: *** No targets specified and no makefile found.  Stop.
    

by GNU make.

And at that point they will consult the README I have written for the project
in question and they will learn that they need the other make than what they
are using if they want to build this software.

~~~
CJefferson
Nowadays I write a GNUmakefile, then add a Makefile with a message telling
people to use gmake. Same idea.

~~~
contras1970
i have this in a (hand-written) configure script:

    
    
        case "$(uname -s)" in
        Darvin|Linux)
          MAKE=make
          GMAKE=$MAKE
        ;;
        DragonFly|*BSD)
          MAKE=make
          GMAKE=gmake
        ;;
        *)
          MAKE=gmake
          GMAKE=$MAKE
        ;;
        esac
    
        ...
    
        if test "$MAKE" != "$GMAKE"; then
          case "$(uname -s)" in
          DragonFly|*BSD)
            populate $rootdir/Makefile.in Makefile
          ;;
          esac
        fi
    
        cat <<EOF
    
        to build the programs from their sources:
    
          $MAKE
    
        to test their correctness:
    
          $MAKE check
    
        EOF
    

$rootdir/Makefile.in contains

    
    
        all .DEFAULT:
          @@GMAKE@ --no-print-directory "$@"

------
carussell
> Microsoft has an implementation of make called Nmake, which comes with
> Visual Studio. It’s nearly a POSIX-compatible make, but necessarily breaks
> [...] Windows also lacks a Bourne shell and the standard unix tools, so all
> of the commands will necessarily be different.

What I've been mulling over is an implementation of make that accepts only a
restricted subset of the make syntax, eliding the extensions found in either
BSD and GNU make, and disallowing use of non-standard extensions to the
commands themselves (and maybe even further restricted still). In theory, a
make that does this wouldn't even need to depend on a POSIX environment—it
could treat the recipes not as commands but instead as a language. It wouldn't
even take much to bootstrap this; you could use something like BusyBox as your
interpreter. Call it `bake`.

Crucially, this is not another alternative to make: every bake script is a
valid Makefile, which means it _is_ make (albeit a restricted subset).

~~~
_wmd
What you're mulling over is the precept for the autotools toolchain.. spare
the next generation the same pain we bore :)

~~~
adrianratnapala
carussell is suggesting the opposite of autotools in the sense of making a
very restricted tool that only allows portable code (at least within the
domain of that tool). By contrast, autotools tries to be expansive in many
ways (1) it provides all kinds of bits and pieces of the toolchain, (2) it
tries to support all systems by auto-generating build scripts using sniffed
the system properties.

~~~
bandrami
Particularly when it's used in combination with gnulib, which has an
irritating tendency to build most of glibc if it doesn't recognize your build
environment.

------
brian-armstrong
Honestly, just use Cmake. It is far easier to make it work cross playform and
better yet cross compile. There's no good reason to write a Makefile by hand
and no large projects do it anyway

~~~
chubot
Hm you could argue the _largest_ project did it that way... The Android
platform used to be tens of thousands of GNU Make (including the GNU Make
Standard Library). For 5+ years. I think they are migrating or have migrated
to a custom system now.

Also, the Linux kernel is pure GNU Make (no autotools, since that doesn't
really make sense).

But yes those are platforms/OSes and not applications. Applications typically
work on multiple Unix flavors and Windows, so something like cmake makes
sense. Although honestly it's a shame that they made the same mistake as GNU
make -- not paying enough attention to the programming language design and
ending up with a crappy macro language.

~~~
cyphar
> Also, the Linux kernel is pure GNU Make (no autotools, since that doesn't
> really make sense).

While that is true, they have a lot of wrappers and pseudo-meta-programming
that makes it very simple to add a module to the kernel. It's unlike any other
"pure GNU make" project I've seen.

------
kccqzy
No one wants to manually do dependency management in even a moderately sized
project. I really haven't found an ideal way to have these -MM -MT flags
integrated into Makefiles; I've tried having an awk script automatically
modify the Makefile as the build is happening, but of course the updated
dependencies will only work for later builds, so it's only good for updating
the dependencies. Any other approaches HNers used and really liked?

~~~
weeber
Simply add:

    
    
      -include $(OBJS:%.o=%.d)
    

in your makefile (with -MMD in CFLAGS).

~~~
chubot
Don't you need a pattern rule for %.d? GNU Make doesn't seem to come with this
rule.

There is a profoundly ugly example here:

[https://www.gnu.org/software/make/manual/html_node/Automatic...](https://www.gnu.org/software/make/manual/html_node/Automatic-
Prerequisites.html)

    
    
        %.d: %.c
            @set -e; rm -f $@; \
             $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
             sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
             rm -f $@.$$$$
    

I copied it into a running example here for anyone curious:

[https://github.com/oilshell/blog-
code/tree/master/dotd](https://github.com/oilshell/blog-code/tree/master/dotd)

~~~
rossy
If you have -MD or -MMD in your CFLAGS, GCC (and Clang) will generate the .d
files during compilation without you having to add anything else to the
Makefile, and without requiring ugly sed magic.

~~~
chubot
I just tried this, and it seems wrong on two counts:

1) The sed magic is required for adding the dependencies of the .d file
itself. For example, if the timestamp of your header changes, it may have
acquired new dependencies, and a new .d file has to be generated BEFORE make
determines if the .c file is out of date.

See:

[https://www.gnu.org/software/make/manual/html_node/Automatic...](https://www.gnu.org/software/make/manual/html_node/Automatic-
Prerequisites.html)

The purpose of the sed command is to translate (for example):

    
    
        main.o : main.c defs.h
    

into:

    
    
        main.o main.d : main.c defs.h
    

The first line isn't correct because the .d file itself has no dependencies.

2) By the time you are compiling, it's too late to generate .d. The .d files
are there to determine IF you need to compile.

EDIT: I am trying to generate a test case that shows this fails, but it seems
to actually work.

Hm yes I'm convinced it works, but I have to think about why. I guess one way
of saying it is that the previous .d file is always correct. Hm.

~~~
rossy
(2) is not quite correct. The old .d file from the previous compilation is
actually all you need to determine whether the .c file needs to be recompiled.
It works in all cases. If the .c file is new (or you're doing a clean rebuild
of the whole project,) it will always be compiled, because there will be no
corresponding .o. If the .c file, or any of the .h files in the old .d gain
new header dependencies, they must have been modified, so their timestamps
will be newer than the .o file from the last build, hence the .c file will be
recompiled and a new up-to-date .d file will be generated (because a new .d
file is _always_ generated when the .c file is compiled.)

If (2) is not correct, then (1) is not needed either. The old .d files from
the last compilation pass are sufficient to know which files need to be
recompiled in the current compilation pass. Make does not need to know the
dependencies of the .d files themselves, it just needs to load all the
existing .d files at startup.

EDIT: Yep, I'm fairly confident this works :D. I don't know if whoever wrote
that manual page knew about -MD, but I think it might be newer than -M, which
would explain it.

~~~
chubot
Hm yes I just figured that out the hard way -- _< scratching head>_.

This feels hacky, but yes it seems to work. I'll think about it a bit more. (I
might clone this feature for a build tool -- since the gcc/Clang support is
already there, it seems like any serious build tool needs this. Although some
have their own #include scanners which is odd.)

Thanks for the information!

~~~
rossy
I guess a simple way of explaining it is that if there are any new header
dependencies, one of the files that make already knows about must have been
modified to add the #include statement, so make will correctly rebuild the .c
file and generate the new .d file, even though it's working on outdated
dependency information.

Though, I guess I wasn't quite correct either. See plorkyeran's sibling
comment re: generated header files.

------
ainar-g
Doesn't cmake take care of most of this? Is there any reason not to use cmake
on middle to large scale projects?

I am genuinely curious. I've only recently started looking at cmake, and it
seems like they should generate portable Makefiles, or at least have an option
to generate them.

~~~
to3m
Yes it does, and no there isn't, in that order. CMake is pretty horrid in many
respects - I won't deny that - but it gets a few key things right, and it has
a good-size ecosystem. That's enough to make up for its notable deficiences.

------
thetic
> The bad news is that inference rules are not compatible with out-of-source
> builds. You’ll need to repeat the same commands for each rule as if
> inference rules didn’t exist. This is tedious for large projects, so you may
> want to have some sort of “configure” script, even if hand-written, to
> generate all this for you. This is essentially what CMake is all about.
> That, plus dependency management.

This isn't a case for CMake. It's a case against POSIX Make. The proposed
"portability" and "robustness" of adherence to the POSIX standard are not
worth hamstringing the tool. GNU Make is ubiquitous and is leaps and bounds
ahead of _pure_ Make.

------
harrywinks04
Are you desperately in need of a hacker in any area of your life???

then you can contact certifiedhacker04@gmail.com

he will help you at affordable prices, i offer services like -hack into your
cheating partner's phone(whatsapp,bbm.gmail,icloud,facebook and others) -Sales
of Blank ATM cards.

-hack into email accounts and trace email location -all social media accounts,

-school database to clear or change grades,

-Retrieval of lost file/documents

-DUIs -company records and systems,

-Bank accounts,Paypal accounts -Credit cards hacker

-Credit score hack -Monitor any phone and email address

-Websites hacking, pentesting.

-IP addresses and people tracking.

-Hacking courses and classes.

my services are the best on the market and 100% security and discreet work is
guaranteed.

------
kayamon
I love that their definition of "portable" is software that runs exclusively
on UNIX.

------
c3d
This article barely addresses what really causes trouble in practice, namely
non-portable tools. Sed for example has different switches on macos and linux.
MinGW is another world.

Also check out [https://github.com/c3d/build](https://github.com/c3d/build)
for a way to deal with several of the issues the author addresses (but not
posix portability)

------
majewsky
Wait... "%.o: %.c" is nonstandard?!?

~~~
pavlov
Yes, base Make is almost useless for anything larger than a utility.

~~~
zbuf
Not quite; I think '.c.o' is the equivalent in the original Make syntax?

~~~
majewsky
That's nowhere near equivalent. For example, it cannot replace the following
things:

    
    
      pkg/test/migrations/%.sql: pkg/db/migrations/%.sql
    
      build/%.cover.out: prepare-check FORCE
    

(In the second one, $* is then used in the build rule, which is why % does not
need to show up in the prerequisites list.)

Examples from
[https://github.com/sapcc/limes/blob/62e07b430e2019a6c1891443...](https://github.com/sapcc/limes/blob/62e07b430e2019a6c189144350bae34c574b2f55/Makefile)

------
JepZ
It's been a while since a I wrote a make file but as far as I remember it was
very easy to create a full featured cmake file if the project used the layout
which cmake assumed (easy for new projects).

However, porting existing projects from traditional make files to cmake could
be next to impossible.

------
cmm
where, except for Windows, is requiring GNU Make a problem?

~~~
flukus
I've heard BSD and mac both use much older, non-GPLv3 versions of make.
Portability means supporting older versions too.

~~~
plorkyeran
/usr/bin/make on macOS is GNU Make 3.81, which is fairly ancient (2006) but
still a much more featureful target than POSIX make.

Installing GNU make on BSDs is not difficult; for any project with a nonzero
number of dependencies it's likely to be the most trivial one. If you're
writing a C89-zero-non-posix-dependencies library then sure, you can't use GNU
make, but you also probably only have a ten line makefile anyway.

~~~
paulddraper
That's the Ubuntu 14.04 version. Old, but make changes very little.

------
sigjuice
I like Tom Duff's
[http://www.iq0.com/duffgram/com.html](http://www.iq0.com/duffgram/com.html)
for experimental programs contained in a single file.

------
git-pull
More nifty portable Make facts:

\- For portable recursive make(1) calls, use $(MAKE). This has the added
advantage of BSD systems which can electively install GNU Make as gmake being
able to pass in the path to gmake to run GNU Makefiles [1]

\- BSD's don't include GNU Make in base system. BSD's port and build system
uses Make extensively, and has a different dialect [2]

\- In addition to that, you will likely choose to invoke system commands in
your Makefile. These also have the same GNU-specific features that won't work
on BSD's. So keep your commands like find, ls, etc. POSIX-compliant [3]

\- Part of the reasons tools like CMake exist is to abstract not only
library/header paths and compiler extensions, but also the fact POSIX shell
scripting and Makefile's are quite limited.

\- Not only is there a necessity to use POSIX commands and POSIX compatible
Make language, but the shell scripting must also not use Bash-isms and such,
since there's no guarantee the system will have Bash.

\- POSIX Makefiles have no conditionals as of 2017. Here's a ticket from the
issue tracker suggesting it in 2013:
[http://austingroupbugs.net/view.php?id=805](http://austingroupbugs.net/view.php?id=805).

\- You can do nifty tricks with portable Makefile's to get around limitations.
For instance, major dialects can still use commands to grab piped information
and put it into a variable. For instance, you may not have double globs across
all systems, but you can use POSIX find(1) to store them in a variable:

    
    
        FILES= find . -type f -not -path '*/\.*' | grep -i '.*[.]go$$' 2> /dev/null
    

Then access the variable:

    
    
        if command -v entr > /dev/null; then ${WATCH_FILES} | entr -c $(MAKE) test; else $(MAKE) test entr_warn; fi
    

I cover this in detail in my book _The Tao of tmux_ , available for free to
read online. [4]

\- MacOS comes with Bash, and if I remember correctly, GNU Make comes with the
developer CLI tools as make.

\- For file watching across platforms (including with respect for kqueue), I
use entr(1) [5]. This can plop right into a Makefile. I use it to
automatically rerun testsuites and rebuild docs/projeocts. For instance
[https://github.com/cihai/cihai/blob/cebc197/Makefile#L16](https://github.com/cihai/cihai/blob/cebc197/Makefile#L16)
(feel free to copy/paste, it's permissively licensed).

[1] [https://www.gnu.org/software/make/manual/html_node/MAKE-
Vari...](https://www.gnu.org/software/make/manual/html_node/MAKE-
Variable.html#MAKE-Variable)

[2]
[https://www.freebsd.org/cgi/man.cgi?query=make&apropos=0&sek...](https://www.freebsd.org/cgi/man.cgi?query=make&apropos=0&sektion=1&manpath=FreeBSD+11.0-RELEASE+and+Ports&arch=default&format=html#end)

[3]
[http://pubs.opengroup.org/onlinepubs/9699919799/utilities/fi...](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html)

[4] [https://leanpub.com/the-tao-of-tmux/read#tips-and-
tricks](https://leanpub.com/the-tao-of-tmux/read#tips-and-tricks)

[5] [http://entrproject.org](http://entrproject.org)

~~~
tannhaeuser
But there's no way in POSIX make itself to assign a value to FILES dynamically
- you have to assign the FILES environment var or supply via command line
args. POSIX make will expand ${FILES} by the replacement value in commands
(and prerequisites but not targets). It then merely happens to be interpreted
as part of the command it's placed into.

~~~
git-pull
Updated, thank you.

Assigning the results of a command to a variable is not POSIX Make. However,
both FreeBSD's Make and GNU Make support it.

There's the proposal to add it to POSIX Make:
[http://austingroupbugs.net/view.php?id=337](http://austingroupbugs.net/view.php?id=337)

Another thing I wanted to add to the main post: since POSIX Make doesn't have
conditionals, GNU Make and bmake both have different ways of doing them. If
you look through FreeBSD's Make files, you'll see that conditionals exist, but
they begin with dots:

[https://github.com/freebsd/freebsd/blob/c09174d99/share/mk/b...](https://github.com/freebsd/freebsd/blob/c09174d99/share/mk/bsd.dep.mk#L53)

There are going to be some compromises when writing portable scripts where it
won't be POSIX, but could still be considered portable.

~~~
wahern
POSIX Make supports conditional constructs by way of recursive macro
expansion. I've found it to be not much more clunky than GNU Make's
conditional constructs. (Recently I've come to appreciate the simplicity and
consistency of the BSD extensions, even though I don't use them. GNU Make
syntax quickly becomes impenetrable.) And as you say, shell invocation from
macros isn't yet supported by POSIX but _can_ be done portably. GNU Make
supports $(shell COMMAND), everybody else supports $(COMMAND:sh), nobody
supports both, and where not supported they expand to the empty string.[1]
Here's a simplified example from my proof-of-concept library from
[https://github.com/wahern/autoguess/blob/config-
guess/config...](https://github.com/wahern/autoguess/blob/config-
guess/config.mk.guess)

    
    
      BOOL,true = true
      BOOL,1 = true
      BOOL,false = false
      BOOL,0 = false
      BOOL, = false
    
      OS.exec = uname -s | tr '[A-Z]' '[a-z]'
      OS = $(shell $(OS.exec))$(OS.exec:sh)
      OS.darwin.test,darwin = true
      OS.is.darwin = $(BOOL,$(OS.darwin.test,$(OS)))
    
      # Usage: $(SOFLAGS.shared) - for creating regular shared libraries
      SOFLAGS.shared.if.darwin.true = -dynamiclib
      SOFLAGS.shared.if.darwin.false = -shared
      SOFLAGS.shared = $(SOFLAGS.shared.if.darwin.$(OS.is.darwin))
    
      SOFLAGS = $(SOFLAGS.shared)
    
      show:
      	@echo "SOFLAGS=$(SOFLAGS)"
    

I try to keep my stuff portable across AIX, FreeBSD, Linux/glibc, Linux/musl,
macOS, NetBSD, OpenBSD, and Solaris. I used to just require GNU Make, with
makefiles that looked like:

    
    
      .POSIX:
    
      all:
              +gmake -f GNUmakefile all
    
      .DEFAULT:
              +gmake -f GNUmakefile $<
    

but now I'm moving my projects over to the more portable, dependency-less
method. It makes things so much easier to be able to run `make test` without
having to worry about first installing unnecessary dependencies, or often any
dependencies whatsoever. When you're juggling a dozen VMs, trying to track the
latest couple of releases of a platform, simplicity is key. Plus, more easily
programmable environments encourage overly complex solutions which become
maintenance burdens over months and years. With GNU Make I was always unable
to resist the urge to turn every repetitious rule into a template, for
example. Sticking to purely portable constructs means I'm forced to be smarter
about how to approach something, including recognizing when something is best
left un-automated.

[1] It's nice that POSIX will be formalizing the "!=" construct, but
unfortunately it's not supported by Solaris make, and macOS will be forever
stuck on GNU Make 3.81 which also lacks support.

------
adamstockdill
target [target...]: [prerequisite...]

------
susam
We invoke shell commands in a Makefile, and if we are concerned about POSIX
conformance in the Makefile syntax, we need to be equally concerned about
POSIX conformance in the shell commands and the shell scripts we invoke in
Makefile.

While I have not found a foolproof way to test for and prove POSIX conformance
in shell scripts, I usually go through the POSIX.1-2001 documents to make sure
I am limiting my code to features specified in POSIX. I test the scripts with
bash, ksh, and zsh on Debian and Mac. Then I also test the scripts with dash,
posh and yash on Debian. See
[https://github.com/susam/vimer/blob/master/Makefile](https://github.com/susam/vimer/blob/master/Makefile)
for an example.

Here are some resources:

* POSIX.1-2001 (2004 edition home): [http://pubs.opengroup.org/onlinepubs/009695399/](http://pubs.opengroup.org/onlinepubs/009695399/)

* POSIX.1-2001 (Special Built-In Utilities): [http://pubs.opengroup.org/onlinepubs/009695399/idx/sbi.html](http://pubs.opengroup.org/onlinepubs/009695399/idx/sbi.html)

* POSIX.1-2001 (Utilities): [http://pubs.opengroup.org/onlinepubs/009695399/idx/utilities...](http://pubs.opengroup.org/onlinepubs/009695399/idx/utilities.html)

* POSIX.1-2008 (2016 edition home): [http://pubs.opengroup.org/onlinepubs/9699919799/](http://pubs.opengroup.org/onlinepubs/9699919799/)

* POSIX.1-2008 (Special Built-In Utilities): [http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3...](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_14)

* POSIX.1-2008 (Utilities): [http://pubs.opengroup.org/onlinepubs/9699919799/idx/utilitie...](http://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html)

The editions mentioned in parentheses are the editions available at the
mentioned URLs at the time of posting this comment.

Here is a list of the commands specified in POSIX:

Special Built-In Utilities: break, colon, continue, dot, eval, exec, exit,
export, readonly, return, set, shift, times, trap, unset

Utilities: admin, alias, ar, asa, at, awk, basename, batch, bc, bg, c99, cal,
cat, cd, cflow, chgrp, chmod, chown, cksum, cmp, comm, command, compress, cp,
crontab, csplit, ctags, cut, cxref, date, dd, delta, df, diff, dirname, du,
echo, ed, env, ex, expand, expr, false, fc, fg, file, find, fold, fort77,
fuser, gencat, get, getconf, getopts, grep, hash, head, iconv, id, ipcrm,
ipcs, jobs, join, kill, lex, link, ln, locale, localedef, logger, logname, lp,
ls, m4, mailx, make, man, mesg, mkdir, mkfifo, more, mv, newgrp, nice, nl, nm,
nohup, od, paste, patch, pathchk, pax, pr, printf, prs, ps, pwd, qalter, qdel,
qhold, qmove, qmsg, qrerun, qrls, qselect, qsig, qstat, qsub, read, renice,
rm, rmdel, rmdir, sact, sccs, sed, sh, sleep, sort, split, strings, strip,
stty, tabs, tail, talk, tee, test, time, touch, tput, tr, true, tsort, tty,
type, ulimit, umask, unalias, uname, uncompress, unexpand, unget, uniq,
unlink, uucp, uudecode, uuencode, uustat, uux, val, vi, wait, wc, what, who,
write, xargs, yacc, zcat

