summary refs log tree commit diff
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2014-09-18 20:03:23 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2014-09-18 20:03:23 +0000
commitf316a2ed52195135a35e32d7096e876357c48c69 (patch)
tree5f4486b9a5a213a69e66ef574d6bc643a207981c
downloadexecline-f316a2ed52195135a35e32d7096e876357c48c69.tar.gz
execline-f316a2ed52195135a35e32d7096e876357c48c69.tar.xz
execline-f316a2ed52195135a35e32d7096e876357c48c69.zip
initial commit: rc for execline-2.0.0.0
-rw-r--r--.gitignore5
-rw-r--r--AUTHORS16
-rw-r--r--CHANGES266
-rw-r--r--COPYING13
-rw-r--r--INSTALL135
-rw-r--r--Makefile121
-rw-r--r--README28
-rw-r--r--README.macosx4
-rw-r--r--README.solaris12
-rwxr-xr-xconfigure385
-rw-r--r--doc/background.html57
-rw-r--r--doc/backtick.html66
-rw-r--r--doc/cd.html45
-rw-r--r--doc/componentsb.txt41
-rw-r--r--doc/define.html42
-rw-r--r--doc/dieshdiedie.html278
-rw-r--r--doc/dollarat.html86
-rw-r--r--doc/el_pushenv.html173
-rw-r--r--doc/el_semicolon.html124
-rw-r--r--doc/el_substitute.html309
-rw-r--r--doc/el_transform.html204
-rw-r--r--doc/elgetopt.html60
-rw-r--r--doc/elgetpositionals.html94
-rw-r--r--doc/elglob.html68
-rw-r--r--doc/emptyenv.html57
-rw-r--r--doc/exec.html50
-rw-r--r--doc/execline-shell.html53
-rw-r--r--doc/execline-startup.html59
-rw-r--r--doc/execlineb.html246
-rw-r--r--doc/exit.html44
-rw-r--r--doc/export.html43
-rw-r--r--doc/fdblock.html55
-rw-r--r--doc/fdclose.html44
-rw-r--r--doc/fdmove.html55
-rw-r--r--doc/fdreserve.html92
-rw-r--r--doc/forbacktickx.html77
-rw-r--r--doc/foreground.html57
-rw-r--r--doc/forx.html66
-rw-r--r--doc/getpid.html44
-rw-r--r--doc/grammar.html160
-rw-r--r--doc/heredoc.html56
-rw-r--r--doc/homeof.html37
-rw-r--r--doc/if.html68
-rw-r--r--doc/ifelse.html59
-rw-r--r--doc/ifte.html67
-rw-r--r--doc/ifthenelse.html59
-rw-r--r--doc/import.html37
-rw-r--r--doc/importas.html57
-rw-r--r--doc/index.html213
-rw-r--r--doc/loopwhilex.html60
-rw-r--r--doc/multidefine.html69
-rw-r--r--doc/multisubstitute.html121
-rw-r--r--doc/pipeline.html67
-rw-r--r--doc/piperw.html38
-rw-r--r--doc/quine-dam.txt110
-rw-r--r--doc/quine-jriou.txt28
-rw-r--r--doc/quine-prj-2.txt15
-rw-r--r--doc/quine-prj-3.txt13
-rw-r--r--doc/quine-prj.txt13
-rw-r--r--doc/redirfd.html100
-rw-r--r--doc/runblock.html75
-rw-r--r--doc/shift.html68
-rw-r--r--doc/tryexec.html67
-rw-r--r--doc/umask.html44
-rw-r--r--doc/unexport.html46
-rw-r--r--doc/upgrade.html35
-rw-r--r--doc/wait.html53
-rw-r--r--package/deps-build1
-rw-r--r--package/deps.mak110
-rw-r--r--package/info4
-rw-r--r--package/modes41
-rw-r--r--package/targets.mak51
-rwxr-xr-xpatch-for-solaris17
-rw-r--r--src/execline/background.c76
-rw-r--r--src/execline/backtick.c84
-rw-r--r--src/execline/cd.c17
-rw-r--r--src/execline/define.c12
-rw-r--r--src/execline/deps-exe/background2
-rw-r--r--src/execline/deps-exe/backtick2
-rw-r--r--src/execline/deps-exe/cd1
-rw-r--r--src/execline/deps-exe/define2
-rw-r--r--src/execline/deps-exe/dollarat1
-rw-r--r--src/execline/deps-exe/elgetopt2
-rw-r--r--src/execline/deps-exe/elgetpositionals2
-rw-r--r--src/execline/deps-exe/elglob2
-rw-r--r--src/execline/deps-exe/emptyenv2
-rw-r--r--src/execline/deps-exe/exec1
-rw-r--r--src/execline/deps-exe/execlineb2
-rw-r--r--src/execline/deps-exe/exit1
-rw-r--r--src/execline/deps-exe/export1
-rw-r--r--src/execline/deps-exe/fdblock1
-rw-r--r--src/execline/deps-exe/fdclose1
-rw-r--r--src/execline/deps-exe/fdmove1
-rw-r--r--src/execline/deps-exe/fdreserve1
-rw-r--r--src/execline/deps-exe/forbacktickx2
-rw-r--r--src/execline/deps-exe/foreground2
-rw-r--r--src/execline/deps-exe/forx2
-rw-r--r--src/execline/deps-exe/getpid1
-rw-r--r--src/execline/deps-exe/heredoc1
-rw-r--r--src/execline/deps-exe/homeof1
-rw-r--r--src/execline/deps-exe/if2
-rw-r--r--src/execline/deps-exe/ifelse2
-rw-r--r--src/execline/deps-exe/ifte2
-rw-r--r--src/execline/deps-exe/ifthenelse2
-rw-r--r--src/execline/deps-exe/import2
-rw-r--r--src/execline/deps-exe/importas2
-rw-r--r--src/execline/deps-exe/loopwhilex2
-rw-r--r--src/execline/deps-exe/multidefine2
-rw-r--r--src/execline/deps-exe/multisubstitute2
-rw-r--r--src/execline/deps-exe/pipeline2
-rw-r--r--src/execline/deps-exe/piperw1
-rw-r--r--src/execline/deps-exe/redirfd1
-rw-r--r--src/execline/deps-exe/runblock2
-rw-r--r--src/execline/deps-exe/shift2
-rw-r--r--src/execline/deps-exe/tryexec2
-rw-r--r--src/execline/deps-exe/umask1
-rw-r--r--src/execline/deps-exe/unexport1
-rw-r--r--src/execline/deps-exe/wait2
-rw-r--r--src/execline/dollarat.c63
-rw-r--r--src/execline/elgetopt.c69
-rw-r--r--src/execline/elgetpositionals.c12
-rw-r--r--src/execline/elglob.c12
-rw-r--r--src/execline/emptyenv.c92
-rw-r--r--src/execline/exec.c48
-rw-r--r--src/execline/execlineb.c327
-rw-r--r--src/execline/exit.c15
-rw-r--r--src/execline/export.c27
-rw-r--r--src/execline/fdblock.c34
-rw-r--r--src/execline/fdclose.c17
-rw-r--r--src/execline/fdmove.c35
-rw-r--r--src/execline/fdreserve.c64
-rw-r--r--src/execline/forbacktickx.c145
-rw-r--r--src/execline/foreground.c14
-rw-r--r--src/execline/forx.c91
-rw-r--r--src/execline/getpid.c29
-rw-r--r--src/execline/heredoc.c59
-rw-r--r--src/execline/homeof.c28
-rw-r--r--src/execline/if.c54
-rw-r--r--src/execline/ifelse.c54
-rw-r--r--src/execline/ifte.c59
-rw-r--r--src/execline/ifthenelse.c77
-rw-r--r--src/execline/import.c12
-rw-r--r--src/execline/importas.c12
-rw-r--r--src/execline/loopwhilex.c62
-rw-r--r--src/execline/multidefine.c12
-rw-r--r--src/execline/multisubstitute.c64
-rw-r--r--src/execline/pipeline.c85
-rw-r--r--src/execline/piperw.c29
-rw-r--r--src/execline/redirfd.c69
-rw-r--r--src/execline/runblock.c149
-rw-r--r--src/execline/shift.c121
-rw-r--r--src/execline/tryexec.c61
-rw-r--r--src/execline/umask.c20
-rw-r--r--src/execline/unexport.c20
-rw-r--r--src/execline/wait.c62
-rw-r--r--src/include-local/exlsn.h36
-rw-r--r--src/include/execline/execline.h67
-rwxr-xr-xsrc/libexecline/deps-lib/execline21
-rw-r--r--src/libexecline/el_execsequence.c41
-rw-r--r--src/libexecline/el_getstrict.c18
-rw-r--r--src/libexecline/el_obsolescent.c10
-rw-r--r--src/libexecline/el_popenv.c44
-rw-r--r--src/libexecline/el_pushenv.c49
-rw-r--r--src/libexecline/el_semicolon.c35
-rw-r--r--src/libexecline/el_spawn0.c15
-rw-r--r--src/libexecline/el_spawn1.c15
-rw-r--r--src/libexecline/el_substandrun.c13
-rw-r--r--src/libexecline/el_substandrun_str.c25
-rw-r--r--src/libexecline/el_substitute.c179
-rw-r--r--src/libexecline/el_transform.c84
-rw-r--r--src/libexecline/el_vardupl.c12
-rw-r--r--src/libexecline/exlp.c78
-rw-r--r--src/libexecline/exlsn_define.c50
-rw-r--r--src/libexecline/exlsn_elglob.c78
-rw-r--r--src/libexecline/exlsn_exlp.c27
-rw-r--r--src/libexecline/exlsn_free.c12
-rw-r--r--src/libexecline/exlsn_import.c76
-rw-r--r--src/libexecline/exlsn_main.c19
-rw-r--r--src/libexecline/exlsn_multidefine.c79
-rwxr-xr-xtools/gen-deps.sh79
-rwxr-xr-xtools/install.sh64
181 files changed, 9417 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5c6415e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+*.o
+*.a
+*.lo
+*.so
+*.so.*
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..24c57e1
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,16 @@
+Main author:
+  Laurent Bercot <ska-skaware@skarnet.org>
+
+Contributors:
+  Jean Marot <marot@quatramaran.ens.fr>
+  Paul Jarc <prj@dogmap.org>
+
+Thanks to:
+  Dan J. Bernstein <djb@cr.yp.to>
+  David Madore <david@madore.org>
+  Nicolas George <cigaes@salle-s.org>
+  Joël Riou <joel.riou@normalesup.org>
+  Matthew R. Dempsky <mrd@alkemio.org>
+  Lasse Kliemann <lasse@plastictree.net>
+  Vallo Kallaste <kalts@estpak.ee>
+  Jorge Almeida <jalmeida@math.ist.utl.pt>
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..a8f16b5
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,266 @@
+20020610
+	packaging: corrected admin/ sticky bit
+20020615
+	bug: in execline.c, GETVAR couldn't read $1.
+          Fix: swap MARK and PUSH.
+	version: 0.11
+20020812
+	bug: in execline.c, no actual shiftval support for $#
+	  Fix: obvious.
+	bug: in wait.c, sizeof(unsigned int *) instead of sizeof(unsigned int)
+	  Fix: obvious.
+	ui: fdclose doesn't die anymore on invalid fd
+	ui: fixed error messages in import, export, for, forbacktick
+	ui: removed unexported commands (moved to skaembutils)
+	internal: updated libstddjb
+	internal: fixed <errno.h> inclusion (for __errno_location)
+	packaging: fixed package/compat, split package/compile
+	doc: fixed if.html
+	version: 0.12
+20020830
+	compat: package did not build on Solaris.
+	  Fix: suppress ln -f, add ||true sentinels
+	internal: added <unistd.h> to src/libstddjb/lock_*.c
+	internal: optimized emptyenv
+	packaging: revamped package/compile
+	version: 0.13
+20021001
+	deps: added btdep to skalibs, removed included libstddjb
+	code: added loopwhile
+20021002
+	bug: fixed getopt handling in wait.c
+	internal: misc code cleanups
+	version: 0.14
+20021108
+	internal: removed warnings
+	deps: moved to skalibs-0.14
+	internal: sgetopt unneeded - switched to subgetopt.
+	internal: pre-alloc'ed execline lex buffer to avoid reallocs
+	internal: simplified ifthenelse tests
+20021112
+	code: added pipe
+20021114
+	code: added fdget
+20021124
+	doc: fixed import/importas interface
+	doc: fixed forbacktick's -n option warning
+20030105
+	deps: moved to skalibs-0.17
+	code: added execlineb, documented it
+20030106
+	code: moved pipe to piperw, fdget to fdreserve
+	version: 0.15
+20030111
+	code: added emptyenv -c, shift, elgetopt, dollarat
+20030114
+	deps: moved to skalibs-0.19
+	code: rewrote execline(b) parser, new semantics
+20030218
+	code: wrote substalloc, el_substitute (that was the hard part)
+	internal: rewrote el_replace(s) to use el_substitute
+	ui: changed el_argv_make prototype
+	packaging: fixed package/upgrade
+20030219
+	ui: changed el_split prototype
+	code: wrote el_splitnetstring, rewrote dollarat
+	code: rewrote for, forbacktick
+	ui: discarded el_dollar
+	code: wrote elgetpositionals
+20030220
+	code: wrote multisubstitute
+	ui: rewrote execline-startup and execline-shell, moved them to .../etc
+20030310
+	doc: wrote all new doc
+20030315
+	code: added elglob, updated multisubstitute
+20030317
+	portability: FreeBSD-4.6 and glob() problem
+	  workaround: used shglob
+	deps: moved to skalibs-0.21
+20030320
+	version: 1.00
+20030322
+	internal: separated libexls1.a
+	code: removed el_replace*
+	code: rewrote multisubstitute et al.
+	version: 1.01
+20030330
+	deps: switched to skalibs-0.22 (fixed incompatible gen_alloc)
+20030407
+	internal: used gccattributes
+	doc: added quine
+	version: 1.02
+	bug: variable recognition too greedy
+	  fix: added 0-termination test
+	doc: added more quines
+	version: 1.03
+	ui: changed el_splitc behaviour (thx dam)
+	bug: substitution allowed empty keys
+	  fix: added checking code
+20030408
+	doc: added Paul's ultimate quine
+20030409
+	deps: moved to skalibs-0.23
+	version: 1.04
+20030502
+	deps: moved to skalibs-0.24
+20030514
+	code: added getpid, removed pid support in execline
+	doc: documented it
+	doc: added links to el_substitute.html and co
+	packaging: added conf-compile, moved import to it
+	code: added -x to redirfd
+20030519
+	bug: wrong optind in exls1_elglob
+	  fix: added "optind = 1" line
+20030615
+	deps: moved to skalibs-0.26
+	packaging: exported exec, exit, runblock
+20030620
+	deps: moved to skalibs-0.27 (sigh)
+	packaging: updated scripts
+	code: added pushenv, popenv, and friends
+20030725
+	deps: moved to skalibs-0.29
+	bug: possible bad pointer after realloc in substword()
+	  fix: rewrote substword() interface to use stralloc *, not char *
+	internal: merged subsualloc.c with el_substitute.c
+20030727
+	internal: removed execline's pre-alloc'ed lex buffer (useless)
+	doc: documented pushenv and friends (at last)
+	version: 1.05
+20031008
+	portability: reverted to old src/sys/print-ar.sh so that building
+	  works on OpenBSD. Re-added btdep to sysdeps. Sigh.
+	ui: removed deprecated el_argv_*
+	internal: merged exls1_import[as].c
+	internal: created libexlp.a
+	code: added -u option to import and importas
+	code: added -S option to execline(b)
+	doc: reflected changes
+20031107
+	code: added stricter numeric arg checking (scan_uint0)
+	deps: moved to skalibs-0.34
+20040102
+	deps: moved to skalibs-0.36
+20040229
+	doc: added el_transform.html, removed el_split.html
+	doc: modified el_substitute.html, propagated changes
+	deps: moved to skalibs-0.40
+	version: 1.06
+20041109
+	code: added homeof
+20070119
+        code: added support for runblock -r 0
+	ui: added -0 option to forbacktick and dollarat
+        ui: added -i option to import and importas
+	doc: reflected changes
+20070122
+	ui: added -i option to backtick
+	doc: reflected change
+	doc: suppressed hyperlinks in section headers
+20070125
+	deps: moved to skalibs-0.46
+	version: 1.07
+20070917
+	code: added tryexec
+20081007
+	code: added breakcode option to for and forbacktick
+	code: added ifte
+20081020
+	portability: export overwrites EXPORT on HFS+ (MacOS fs)
+	  workaround: rename EXPORT to EXPORT-
+20081022
+	packaging: /command wasn't relative to conf-sp_root
+	  fix: update package/upgrade
+	portability: strip -x is broken on some Solaris platforms
+	  workaround: empty default conf-compile/conf-striplibs
+20081023
+	packaging: separated compat links, moved to new prepare...
+	doc: reflected ui changes
+	version: 1.08
+20091214
+	code: added fdblock and ifelse
+	ui: added -X flag to if, ifte, ifthenelse
+20100119
+	code: added homeof, multidefine, loopwhilex, forx, forbacktickx
+	doc: reflected changes
+	bug: ifte didn't parse end of script correctly
+	  fix: rewrite the incriminated part
+	deps: moved to skalibs-1.0.0
+	packaging: moved to new system
+20110628
+	version: 1.1.0
+20110810
+	build: libexecline objects were not built as PIC when required
+	  fix: add x bits in deps-obj/
+	version: 1.1.1
+	ui: changed forx/forbacktickx behaviour when breakcode happens
+	version: 1.1.2
+20120214
+	internal: changed bools from unsigned char to int
+	code: added -x option to if
+	doc: added multidefine to el_substitute.html's list
+20120220
+	version: 1.1.3
+20120312
+	bug: typo in loopwhile.c, added in 1.1.3. Thx lory.fulgi@infinito.it.
+	   fix: obvious.
+20120313
+	version: 1.1.4
+20120606
+	code: added el_obsolescent() to mark loopwhile, for, forbacktick
+	code: changed forx, forbacktickx and backtick to not substitute
+	ui: added -E/-e options to those programs
+	doc: reflected changes
+20120612
+	version: 1.1.5
+20121111
+	bug: background didn't set LASTPID
+	  fix: correctly initialize the fmt array
+	version: 1.1.6
+20121115
+	doc: properly documented if's -x option
+20130124
+	build: added FHS support
+	internal: switched to space quoter and null terminator (tm).
+	doc: reflected changes
+20130212
+	version: 1.2.0
+20130403
+	bug: typo in fdreserve.c ('0' instead of 0)
+	  impact: envvars weren't properly exported
+	  fix: obvious
+	version: 1.2.1
+20130527
+	bug: bad #ifdef in forx.c and backtick.c
+	  impact: forx didn't work, bad USAGE in backtick
+	  fix: obvious
+	ui: changed USAGE macros to execlineb syntax
+	version: 1.2.2
+20130913
+	internal: fixed deprecated macros
+20130927
+	version: 1.2.3
+20131012
+	bug: pipeline crashed on empty remainder
+	  fix: don't try to be smart with df = 0, just die
+20131019
+	version: 1.2.4
+20131101
+	ui: changed LASTPID to ! and LASTEXITCODE to ?
+	  added EXECLINE_OLD_VARNAMES for compatibility
+20131105
+	version: 1.3.0
+20140325
+	bug: segfault with empty remainder in backtick
+	  fix: add a line to catch the case and exit
+	version: 1.3.1
+20140410
+	build: moved to 4-number versioning
+	deps: moved to skalibs-1.6.0.0
+20140506
+	ui: removed obsolete for, forbacktick, loopwhile
+	ui: removed default -DEXECLINE_DOSUBST_COMPAT
+20140514
+	version: 1.3.1.1
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..63309ba
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,13 @@
+Copyright (c) 2011-2014 Laurent Bercot <ska-skaware@skarnet.org>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..9c6ae2f
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,135 @@
+Build Instructions
+------------------
+
+* Requirements
+  ------------
+
+  - A POSIX-compliant C development environment
+  - GNU make version 3.81 or later
+  - skalibs version 2.0.0.0 or later: http://skarnet.org/software/skalibs/
+
+ This software will run on any operating system that implements
+POSIX.1-2008, available at:
+  http://pubs.opengroup.org/onlinepubs/9699919799/
+
+
+* Standard usage
+  --------------
+
+  ./configure && make && sudo make install
+
+ will work for most users.
+ It will install the binaries in /bin and the static libraries in
+/usr/lib/execline.
+
+ You can strip the binaries and libraries of their extra symbols via
+"make strip" before the "make install" phase. It will shave a few bytes
+off them.
+
+
+* Customization
+  -------------
+
+ You can customize paths via flags given to configure.
+ See ./configure --help for a list of all available configure options.
+
+
+* Environment variables
+  ---------------------
+
+ Controlling a build process via environment variables is a big and
+dangerous hammer. You should try and pass flags to configure instead;
+nevertheless, the standard environment variables are recognized.
+
+ The value of the CROSS_COMPILE environment variable will prefix the
+building tools' names. The --enable-cross option is preferred, see
+"Cross-compilation" below.
+ 
+ If the CC environment variable is set, its value will override compiler
+detection by configure.
+
+ The values of CFLAGS, CPPFLAGS and LDFLAGS will be appended to flags
+auto-detected by configure. To entirely override the flags set by
+configure, use make -e.
+
+ The value of LDLIBS will be appended by make to command lines that link
+an executable, even without the -e option.
+
+ The Makefile supports the DESTDIR convention for staging.
+
+
+* Shared libraries
+  ----------------
+
+ Software from skarnet.org is small enough that shared libraries are
+generally not worth using. Static linking is simpler and incurs less
+runtime overhead and less points of failure: so by default, shared
+libraries are not built and binaries are linked against the static
+versions of the skarnet.org libraries. Nevertheless, you can:
+  * build shared libraries: --enable-shared
+  * link binaries against shared libraries: --disable-allstatic
+
+
+* Static binaries
+  ---------------
+
+ By default, binaries are linked against static versions of all the
+libraries they depend on, except for the libc. You can enforce
+linking against the static libc with --enable-static-libc.
+
+ (If you are using a GNU/Linux system, be aware that the GNU libc
+behaves badly with static linking and produces huge executables,
+which is why it is not the default. Other libcs are better suited
+to static linking, for instance musl: http://musl-libc.org/)
+
+ execline is especially efficient when statically linked, so it
+is recommended if you have a suitable libc.
+
+
+* Cross-compilation
+  -----------------
+
+ skarnet.org packages centralize all the difficulty of
+cross-compilation in one place: skalibs. Once you have
+cross-compiled skalibs, the rest is easy.
+
+ Use the --enable-cross=PREFIX option to configure, or simply
+--enable-cross if your default toolchain is a cross-compiling
+toolchain. And make sure to use the correct version of skalibs
+for your target, and the correct sysdeps directory, making use
+of the --with-include, --with-lib, --with-dynlib and --with-sysdeps
+options as necessary.
+ 
+
+* The slashpackage convention
+  ---------------------------
+
+ The slashpackage convention (http://cr.yp.to/slashpackage.html)
+is a package installation scheme that provides a few guarantees
+over other conventions such as the FHS, for instance fixed
+absolute pathnames. skarnet.org packages support it: use the
+--enable-slashpackage option to configure, or
+--enable-slashpackage=DIR for a prefixed DIR/package tree.
+This option will activate slashpackage support during the build
+and set slashpackage-compatible installation directories.
+Other options setting individual installation directories will be
+ignored.
+
+ When using slashpackage, two additional Makefile targets are
+available after "make install":
+ - "make update" changes the default version of the software to the
+freshly installed one. (This is useful when you have several installed
+versions of the same software, which slashpackage supports.)
+ - "make -L global-links" adds links from /command and /library.so to the
+default version of the binaries and shared libraries. The "-L" option to
+make is necessary because targets are symbolic links, and the default make
+behaviour is to check the pointed file's timestamp and not the symlink's
+timestamp.
+
+
+* Out-of-tree builds
+  ------------------
+
+ skarnet.org packages do not support out-of-tree builds. They
+are small, so it does not cost much to duplicate the entire
+source tree if parallel builds are needed.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..7d8901c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,121 @@
+#
+# This Makefile requires GNU make.
+#
+# Do not make changes here.
+# Use the included .mak files.
+#
+
+it: all
+
+CC = $(error Please use ./configure first)
+
+include package/targets.mak
+include package/deps.mak
+-include config.mak
+
+version_m := $(basename $(version))
+version_M := $(basename $(version_m))
+version_l := $(basename $(version_M))
+CPPFLAGS_ALL := -iquote src/include-local -Isrc/include $(CPPFLAGS)
+CFLAGS_ALL := $(CFLAGS) -pipe -Wall
+CFLAGS_SHARED := -fPIC
+LDFLAGS_ALL := $(LDFLAGS)
+LDFLAGS_SHARED := -shared
+LDLIBS_ALL := $(LDLIBS)
+REALCC = $(CROSS_COMPILE)$(CC)
+AR := $(CROSS_COMPILE)ar
+RANLIB := $(CROSS_COMPILE)ranlib
+STRIP := $(CROSS_COMPILE)strip
+INSTALL := ./tools/install.sh
+
+ALL_BINS := $(LIBEXEC_TARGETS) $(BIN_TARGETS) $(SBIN_TARGETS)
+ALL_LIBS := $(SHARED_LIBS) $(STATIC_LIBS)
+ALL_INCLUDES := $(wildcard src/include/$(package)/*.h)
+
+all: $(ALL_LIBS) $(ALL_BINS) $(ALL_INCLUDES)
+
+clean:
+	@exec rm -f $(ALL_LIBS) $(ALL_BINS) $(wildcard src/*/*.o src/*/*.lo)
+
+distclean: clean
+	@exec rm -f config.mak src/include/${package}/config.h
+
+tgz: distclean
+	@. package/info && \
+	rm -rf /tmp/$$package-$$version && \
+	cp -a . /tmp/$$package-$$version && \
+	cd /tmp && \
+	tar -zpcv --owner=0 --group=0 --numeric-owner --exclude=.git* -f /tmp/$$package-$$version.tar.gz $$package-$$version && \
+	exec rm -rf /tmp/$$package-$$version
+
+strip: $(ALL_LIBS) $(ALL_BINS)
+ifneq ($(strip $(ALL_LIBS)),)
+	exec ${STRIP} -x -R .note -R .comment -R .note.GNU-stack $(ALL_LIBS)
+endif
+ifneq ($(strip $(ALL_BINS)),)
+	exec ${STRIP} -R .note -R .comment -R .note.GNU-stack $(ALL_BINS)
+endif
+
+install: install-dynlib install-libexec install-bin install-sbin install-lib install-include
+install-dynlib: $(SHARED_LIBS:lib%.so=$(DESTDIR)$(dynlibdir)/lib%.so)
+install-libexec: $(LIBEXEC_TARGETS:%=$(DESTDIR)$(libexecdir)/%)
+install-bin: $(BIN_TARGETS:%=$(DESTDIR)$(bindir)/%)
+install-sbin: $(SBIN_TARGETS:%=$(DESTDIR)$(sbindir)/%)
+install-lib: $(STATIC_LIBS:lib%.a=$(DESTDIR)$(libdir)/lib%.a)
+install-include: $(ALL_INCLUDES:src/include/$(package)/%.h=$(DESTDIR)$(includedir)/$(package)/%.h)
+
+ifneq ($(exthome),)
+
+update:
+	exec $(INSTALL) -l $(notdir $(home)) $(DESTDIR)$(exthome)
+
+global-links: $(DESTDIR)$(exthome) $(SHARED_LIBS:lib%.so=$(DESTDIR)$(sproot)/library.so/lib%.so) $(BIN_TARGETS:%=$(DESTDIR)$(sproot)/command/%) $(SBIN_TARGETS:%=$(DESTDIR)$(sproot)/command/%)
+
+$(DESTDIR)$(sproot)/command/%: $(DESTDIR)$(home)/command/%
+	exec $(INSTALL) -D -l ..$(exthome)/command/$(<F) $@
+
+$(DESTDIR)$(sproot)/library.so/lib%.so: $(DESTDIR)$(dynlibdir)/lib%.so
+	exec $(INSTALL) -D -l ..$(exthome)/library.so/$(<F) $@
+
+.PHONY: update global-links
+
+endif
+
+$(DESTDIR)$(dynlibdir)/lib%.so: lib%.so
+	$(INSTALL) -D -m 755 $< $@.$(version) && \
+	$(INSTALL) -l $<.$(version) $@.$(version_m) && \
+	$(INSTALL) -l $<.$(version_m) $@.$(version_M) && \
+	$(INSTALL) -l $<.$(version_M) $@.$(version_l) && \
+	exec $(INSTALL) -l $<.$(version_l) $@
+
+$(DESTDIR)$(libexecdir)/% $(DESTDIR)$(bindir)/% $(DESTDIR)$(sbindir)/%: % package/modes
+	exec $(INSTALL) -D -m 600 $< $@
+	grep -- ^$(@F) < package/modes | { read name mode owner && \
+	if [ x$$owner != x ] ; then chown -- $$owner $@ ; fi && \
+	chmod $$mode $@ ; }
+
+$(DESTDIR)$(libdir)/lib%.a: lib%.a
+	$(INSTALL) -D -m 644 $< $@
+
+$(DESTDIR)$(includedir)/$(package)/%.h: src/include/$(package)/%.h
+	exec $(INSTALL) -D -m 644 $< $@
+
+%.o: %.c
+	exec $(REALCC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) -c -o $@ $<
+
+%.lo: %.c
+	exec $(REALCC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) $(CFLAGS_SHARED) -c -o $@ $<
+
+$(ALL_BINS):
+	exec $(REALCC) -o $@ $(CFLAGS_ALL) $(LDFLAGS_ALL) $(LDFLAGS_NOSHARED) $^ $(LDLIBS_ALL)
+
+lib%.a:
+	exec $(AR) rc $@ $^
+	exec $(RANLIB) $@
+
+lib%.so:
+	exec $(REALCC) -o $@ $(CFLAGS_ALL) $(CFLAGS_SHARED) $(LDFLAGS_ALL) $(LDFLAGS_SHARED) -Wl,-soname,$@.$(version_l) $^
+
+.PHONY: it all clean distclean tgz strip install install-dynlib install-bin install-sbin install-lib install-include
+
+.DELETE_ON_ERROR:
diff --git a/README b/README
new file mode 100644
index 0000000..61227af
--- /dev/null
+++ b/README
@@ -0,0 +1,28 @@
+execline - an interpreter-less scripting language
+-------------------------------------------------
+
+ execline is a scripting language unlike any other in that
+it has no resident interpreter. It reads a script, turns it
+into a single command line, and executes into that command
+line; control is performed by executables run inside the
+command line itself.
+
+ It is especially suited to very small and simple scripts
+for which a shell is overpowered.
+
+ See http://skarnet.org/software/execline/ for details.
+
+
+* Installation
+  ------------
+
+ See the INSTALL file.
+
+
+* Contact information
+  -------------------
+
+ Laurent Bercot <ska-skaware at skarnet.org>
+
+ Please use the <skaware at list.skarnet.org> mailing-list for
+questions about execline.
diff --git a/README.macosx b/README.macosx
new file mode 100644
index 0000000..d71a096
--- /dev/null
+++ b/README.macosx
@@ -0,0 +1,4 @@
+
+ This package will compile and run on Darwin (MacOS X), but the building of
+shared libraries is not supported.
+ Make sure you use the --disable-shared option to configure.
diff --git a/README.solaris b/README.solaris
new file mode 100644
index 0000000..91a5b26
--- /dev/null
+++ b/README.solaris
@@ -0,0 +1,12 @@
+
+ This package assumes the existence of a POSIX shell in /bin/sh.
+ On Solaris, /bin/sh is not POSIX. Most versions of Solaris provide
+a POSIX shell in /usr/xpg4/bin/sh.
+
+ To compile this package on Solaris, you will need to run
+
+     ./patch-for-solaris
+
+ before you run ./configure. This script will change the #! invocation
+of the configure script and various tools so that a POSIX shell is used
+for the compilation process.
diff --git a/configure b/configure
new file mode 100755
index 0000000..b0c0e59
--- /dev/null
+++ b/configure
@@ -0,0 +1,385 @@
+#!/bin/sh
+
+usage () {
+cat <<EOF
+Usage: $0 [OPTION]... [VAR=VALUE]... [TARGET]
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE.  See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+System types:
+  --target=TARGET               configure to run on target TARGET [detected]
+  --host=TARGET                 same as --target
+
+Installation directories:
+  --prefix=PREFIX               main installation prefix [/]
+  --exec-prefix=EPREFIX         installation prefix for executable files [PREFIX]
+
+Fine tuning of the installation directories:
+  --dynlibdir=DIR               shared library files [PREFIX/lib]
+  --bindir=DIR                  user executables [EPREFIX/bin]
+  --sbindir=DIR                 admin executables [EPREFIX/sbin]
+  --libexecdir=DIR              package-scoped executables [EPREFIX/libexec]
+  --libdir=DIR                  static library files [PREFIX/lib]
+  --includedir=DIR              include files for the C compiler [PREFIX/include]
+
+Dependencies:
+  --with-sysdeps=DIR            use sysdeps in DIR [/usr/lib/skalibs/sysdeps]
+  --with-include=DIR            add DIR to the list of searched directories for headers
+  --with-lib=DIR                add DIR to the list of searched directories for static libraries
+  --with-dynlib=DIR             add DIR to the list of searched directories for shared libraries
+
+Optional features:
+  --enable-shared               build shared libraries [disabled]
+  --disable-static              do not build static libraries [enabled]
+  --disable-allstatic           do not prefer linking against static libraries [enabled]
+  --enable-static-libc          make entirely static binaries [disabled]
+  --enable-slashpackage[=ROOT]  assume /package installation at ROOT [disabled]
+  --enable-cross=PREFIX         prefix toolchain executable names with PREFIX [none]
+
+EOF
+exit 0
+}
+
+
+# Helper functions
+
+# If your system does not have printf, you can comment this, but it is
+# generally not a good idea to use echo.
+# See http://www.etalabs.net/sh_tricks.html
+echo () {
+  IFS=" "
+  printf %s\\n "$*"
+}
+
+quote () {
+  tr '\n' ' ' <<EOF | grep '^[-[:alnum:]_=,./:]* $' >/dev/null 2>&1 && { echo "$1" ; return 0 ; }
+$1
+EOF
+  echo "$1" | sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/" -e "s#^'\([-[:alnum:]_,./:]*\)=\(.*\)\$#\1='\2#" -e "s|\*/|* /|g"
+}
+
+fail () {
+  echo "$*"
+  exit 1
+}
+
+fnmatch () {
+  eval "case \"\$2\" in $1) return 0 ;; *) return 1 ;; esac"
+}
+
+cmdexists () {
+  type "$1" >/dev/null 2>&1
+}
+
+trycc () {
+  test -z "$CC_AUTO" && cmdexists "$1" && CC_AUTO=$1
+}
+
+stripdir () {
+  while eval "fnmatch '*/' \"\${$1}\"" ; do
+    eval "$1=\${$1%/}"
+  done
+}
+
+tryflag () {
+  echo "checking whether compiler accepts $2 ..."
+  echo "typedef int x;" > "$tmpc"
+  if $CC_AUTO $CPPFLAGS_AUTO $CFLAGS_AUTO "$2" -c -o /dev/null "$tmpc" >/dev/null 2>&1 ; then
+    echo "  ... yes"
+    eval "$1=\"\${$1} \$2\""
+    eval "$1=\${$1# }"
+    return 0
+  else
+    echo "  ... no"
+    return 1
+  fi
+}
+
+tryldflag () {
+  echo "checking whether linker accepts $2 ..."
+  echo "typedef int x;" > "$tmpc"
+  if $CC_AUTO $CFLAGS_AUTO $LDFLAGS_AUTO -nostdlib "$2" -o /dev/null "$tmpc" >/dev/null 2>&1 ; then
+    echo "  ... yes"
+    eval "$1=\"\${$1} \$2\""
+    eval "$1=\${$1# }"
+    return 0
+  else
+    echo "  ... no"
+    return 1
+  fi
+}
+
+
+# Actual script
+
+. package/info
+
+CC_AUTO="$CC"
+CFLAGS_AUTO="$CFLAGS"
+CPPFLAGS_AUTO="-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=600 $CPPFLAGS"
+LDFLAGS_AUTO="$LDFLAGS"
+LDFLAGS_NOSHARED=
+prefix=
+exec_prefix='$prefix'
+dynlibdir='$prefix/lib'
+libexecdir='$exec_prefix/libexec'
+bindir='$exec_prefix/bin'
+sbindir='$exec_prefix/sbin'
+libdir='$prefix/usr/lib/'$package
+includedir='$prefix/usr/include'
+sysdeps='$prefix/usr/lib/skalibs/sysdeps'
+manualsysdeps=false
+shared=false
+static=true
+slashpackage=false
+sproot=
+home=
+exthome=
+allstatic=true
+evenmorestatic=false
+addincpath=''
+addlibspath=''
+addlibdpath=''
+vpaths=''
+vpathd=''
+cross="$CROSS_COMPILE"
+
+for arg ; do
+  case "$arg" in
+    --help) usage ;;
+    --prefix=*) prefix=${arg#*=} ;;
+    --exec-prefix=*) exec_prefix=${arg#*=} ;;
+    --dynlibdir=*) dynlibdir=${arg#*=} ;;
+    --libexecdir=*) libexecdir=${arg#*=} ;;
+    --bindir=*) bindir=${arg#*=} ;;
+    --sbindir=*) sbindir=${arg#*=} ;;
+    --libdir=*) libdir=${arg#*=} ;;
+    --includedir=*) includedir=${arg#*=} ;;
+    --with-sysdeps=*) sysdeps=${arg#*=} manualsysdeps=true ;;
+    --with-include=*) var=${arg#*=} ; stripdir var ; addincpath="$addincpath -I$var" ;;
+    --with-lib=*) var=${arg#*=} ; stripdir var ; addlibspath="$addlibspath -L$var" ; vpaths="$vpaths $var" ;;
+    --with-dynlib=*) var=${arg#*=} ; stripdir var ; addlibdpath="$addlibdpath -L$var" ; vpathd="$vpathd $var" ;;
+    --enable-shared|--enable-shared=yes) shared=true ;;
+    --disable-shared|--enable-shared=no) shared=false ;;
+    --enable-static|--enable-static=yes) static=true ;;
+    --disable-static|--enable-static=no) static=false ;;
+    --enable-allstatic|--enable-allstatic=yes) allstatic=true ;;
+    --disable-allstatic|--enable-allstatic=no) allstatic=false ;;
+    --enable-static-libc|--enable-static-libc=yes) evenmorestatic=true ;;
+    --disable-static-libc|--enable-static-libc=no) evenmorestatic=false ;;
+    --enable-slashpackage=*) sproot=${arg#*=} ; slashpackage=true ; ;;
+    --enable-slashpackage) sproot= ; slashpackage=true ;;
+    --disable-slashpackage) sproot= ; slashpackage=false ;;
+    --enable-cross=*) cross=${arg#*=} ;;
+    --enable-cross) cross= ;;
+    --disable-cross) cross= ;;
+    --enable-*|--disable-*|--with-*|--without-*|--*dir=*|--build=*) ;;
+    --host=*|--target=*) target=${arg#*=} ;;
+    -* ) echo "$0: unknown option $arg" ;;
+    *=*) ;;
+    *) target=$arg ;;
+  esac
+done
+
+for i in prefix exec_prefix dynlibdir libexecdir bindir sbindir libdir includedir linkdynlibdir linkbindir linksbindir sysdeps sproot skalibs ; do
+  eval tmp=\${$i}
+  eval $i=$tmp
+  stripdir $i
+done
+
+# Get usable temp filenames
+i=0
+set -C
+while : ; do
+  i=$(($i+1))
+  tmpc="./tmp-configure-$$-$PPID-$i.c"
+  tmpe="./tmp-configure-$$-$PPID-$i.tmp"
+  2>|/dev/null > "$tmpc" && break
+  2>|/dev/null > "$tmpe" && break
+  test "$i" -gt 50 && fail "$0: cannot create temporary files"
+done
+set +C
+trap 'rm -f "$tmpc" "$tmpe"' EXIT ABRT INT QUIT TERM HUP
+
+# Set slashpackage values
+if $slashpackage ; then
+  home=${sproot}/package/${category}/${package}-${version}
+  exthome=${sproot}/package/${category}/${package}
+  if $manualsysdeps ; then
+    :
+  else
+    sysdeps=${sproot}/package/prog/skalibs/sysdeps
+  fi
+  binprefix=${home}/command
+  extbinprefix=${exthome}/command
+  dynlibdir=${home}/library.so
+  libexecdir=$binprefix
+  bindir=$binprefix
+  sbindir=$binprefix
+  libdir=${home}/library
+  includedir=${home}/include
+  while read dep ; do
+    addincpath="$addincpath -I${sproot}${dep}/include"
+    vpaths="$vpaths ${sproot}${dep}/library"
+    addlibspath="$addlibspath -L${sproot}${dep}/library"
+    if $allstatic ; then : ; else
+      vpathd="$vpathd ${sproot}${dep}/library.so"
+      addlibdpath="$addlibdpath -L${sproot}${dep}/library.so"
+    fi
+  done < package/deps-build
+fi
+
+# Find a C compiler to use
+echo "checking for C compiler..."
+trycc ${cross}gcc
+trycc ${cross}c99
+trycc ${cross}cc
+test -n "$CC_AUTO" || { echo "$0: cannot find a C compiler" ; exit 1 ; }
+echo "  ... $CC_AUTO"
+echo "checking whether C compiler works... "
+echo "typedef int x;" > "$tmpc"
+if $CC_AUTO $CPPFLAGS_AUTO $CFLAGS_AUTO -c -o /dev/null "$tmpc" 2>"$tmpe" ; then
+  echo "  ... yes"
+else
+  echo "  ... no. Compiler output follows:"
+  cat < "$tmpe"
+  exit 1
+fi
+
+echo "checking target system type..."
+test -n "$target" || target=$($CC_AUTO -dumpmachine 2>/dev/null) || target=unknown
+echo "  ... $target"
+if test ! -d $sysdeps || test ! -f $sysdeps/target ; then
+  echo "$0: error: $sysdeps is not a valid sysdeps directory"
+  exit 1
+fi
+if [ "x$target" != "x$(cat $sysdeps/target)" ] ; then
+  echo "$0: error: target $target does not match the contents of $sysdeps/target"
+  exit 1
+fi
+
+rt_lib=$(cat $sysdeps/rt.lib)
+socket_lib=$(cat $sysdeps/socket.lib)
+sysclock_lib=$(cat $sysdeps/sysclock.lib)
+tainnow_lib=$(cat $sysdeps/tainnow.lib)
+util_lib=$(cat $sysdeps/util.lib)
+
+tryflag CFLAGS_AUTO -std=c99
+tryflag CFLAGS_AUTO -fomit-frame-pointer
+tryflag CFLAGS_AUTO -fno-exceptions
+tryflag CFLAGS_AUTO -fno-unwind-tables
+tryflag CFLAGS_AUTO -fno-asynchronous-unwind-tables
+tryflag CFLAGS_AUTO -Wa,--noexecstack
+tryflag CFLAGS_AUTO -fno-stack-protector
+tryflag CPPFLAGS_AUTO -Werror=implicit-function-declaration
+tryflag CPPFLAGS_AUTO -Werror=implicit-int
+tryflag CPPFLAGS_AUTO -Werror=pointer-sign
+tryflag CPPFLAGS_AUTO -Werror=pointer-arith
+tryflag CPPFLAGS_AUTO -Wno-parentheses
+tryflag CPPFLAGS_AUTO -Wno-uninitialized
+tryflag CPPFLAGS_AUTO -Wno-missing-braces
+tryflag CPPFLAGS_AUTO -Wno-unused-value
+tryflag CPPFLAGS_AUTO -Wno-unused-but-set-variable
+tryflag CPPFLAGS_AUTO -Wno-unknown-pragmas
+tryflag CPPFLAGS_AUTO -Wno-pointer-to-int-cast
+
+if $evenmorestatic ; then
+  LDFLAGS_NOSHARED=-static
+fi
+
+if $shared ; then
+  tryldflag LDFLAGS_AUTO -Wl,--hash-style=both
+fi
+
+if test -z "$vpaths" ; then
+  while read dep ; do
+    base=$(basename $dep) ;
+    vpaths="$vpaths /usr/lib/$base"
+    addlibspath="$addlibspath -L/usr/lib/$base"
+  done < package/deps-build  
+fi
+
+CPPFLAGS_AUTO="$CPPFLAGS_AUTO $addincpath"
+LDFLAGS_AUTO="$LDFLAGS_AUTO $addlibspath"
+$allstatic || LDFLAGS_AUTO="$LDFLAGS_AUTO $addlibdpath"
+
+echo "creating config.mak..."
+cmdline=$(quote "$0")
+for i ; do cmdline="$cmdline $(quote "$i")" ; done
+exec 3>&1 1>config.mak
+cat << EOF
+# This file was generated by:
+# $cmdline
+# Any changes made here will be lost if configure is re-run.
+
+target := $target
+package := $package
+prefix := $prefix
+exec_prefix := $exec_prefix
+dynlibdir := $dynlibdir
+libexecdir := $libexecdir
+bindir := $bindir
+sbindir := $sbindir
+libdir := $libdir
+includedir := $includedir
+sysdeps := $sysdeps
+slashpackage := $slashpackage
+sp_root := $sproot
+version := $version
+home := $home
+exthome := $exthome
+RT_LIB := ${rt_lib}
+SOCKET_LIB := ${socket_lib}
+SYSCLOCK_LIB := ${sysclock_lib}
+TAINNOW_LIB := ${tainnow_lib}
+UTIL_LIB := ${util_lib}
+
+CC := $CC_AUTO
+CFLAGS := $CFLAGS_AUTO
+CPPFLAGS := $CPPFLAGS_AUTO
+LDFLAGS := $LDFLAGS_AUTO
+LDFLAGS_NOSHARED := $LDFLAGS_NOSHARED
+CROSS_COMPILE := $cross
+
+vpath lib%a$vpaths
+EOF
+if $allstatic ; then
+  echo ".LIBPATTERNS := lib%.a"
+  vpathd=
+fi
+  echo "vpath lib%.so$vpathd"
+echo
+$static || echo "STATIC_LIBS :="
+$shared || echo "SHARED_LIBS :="
+exec 1>&3 3>&-
+echo "  ... done."
+
+echo "creating src/include/${package}/config.h..."
+mkdir -p -m 0755 src/include/${package}
+exec 3>&1 1> src/include/${package}/config.h
+cat <<EOF
+/* ISC license. */
+
+/* Generated by: $cmdline */
+
+#ifndef ${package_macro_name}_CONFIG_H
+#define ${package_macro_name}_CONFIG_H
+
+#define ${package_macro_name}_VERSION "$version"
+EOF
+if $slashpackage ; then
+  echo "#define ${package_macro_name}_BINPREFIX \"$binprefix\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"$extbinprefix\""
+  echo "#define ${package_macro_name}_LIBEXECPREFIX \"$binprefix\""
+else
+  echo "#define ${package_macro_name}_BINPREFIX \"\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"\""
+  echo "#define ${package_macro_name}_LIBEXECPREFIX \"$libexecdir\""
+fi
+echo
+echo "#endif"
+exec 1>&3 3>&-
+echo "  ... done."
diff --git a/doc/background.html b/doc/background.html
new file mode 100644
index 0000000..85d2589
--- /dev/null
+++ b/doc/background.html
@@ -0,0 +1,57 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>execline: the background command</title>
+ <meta name="Description" content="execline: the background command" />
+ <meta name="Keywords" content="execline command background" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+</head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>background</tt> program </h1>
+
+<tt>background</tt> launches a command in the background, then goes on
+with the execution flow.
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="execlineb.html">execlineb</a> script:
+</p>
+
+<pre>
+     background [ -d ] { <em>prog1...</em> } <em>prog2...</em>
+</pre>
+
+<ul>
+ <li> <tt>background</tt> reads a <em>prog1...</em> command in a
+<a href="el_semicolon.html">block</a> and unquotes it. </li>
+ <li> It spawns a child executing <em>prog1...</em>. </li>
+ <li> It sets the <tt>!</tt> environment
+variable to the pid of the <em>prog1...</em> process. </li>
+ <li> It then execs into <em>prog2...</em>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-d</tt>&nbsp;: doublefork. If the <tt>-d</tt> option is set,
+<em>prog1...</em> will run as a grandchild of <tt>background</tt>. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>background <em>prog1...</em> "" <em>prog2...</em></tt> is
+equivalent to <tt>sh -c '<em>prog1...</em> & ; exec <em>prog2...</em>'</tt>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/backtick.html b/doc/backtick.html
new file mode 100644
index 0000000..7bdc037
--- /dev/null
+++ b/doc/backtick.html
@@ -0,0 +1,66 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>execline: the backtick command</title>
+ <meta name="Description" content="execline: the backtick command" />
+ <meta name="Keywords" content="execline command backtick" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+</head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>backtick</tt> program </h1>
+
+<p>
+<tt>backtick</tt> runs a program and uses its output as the argument of
+another program.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="execlineb.html">execlineb</a> script:
+</p>
+
+<pre>
+     backtick [ -i ] [ -n ] <em>variable</em> { <em>prog1...</em> } <em>prog2...</em>
+</pre>
+
+<ul>
+ <li> <tt>backtick</tt> reads <em>prog1...</em> in a
+<a href="el_semicolon.html">block</a> and unquotes it. </li>
+ <li> It runs <em>prog1...</em> as a child process and saves its
+output in memory. This output must not contain a null character. </li>
+ <li><tt>backtick</tt> execs into the modified <em>prog2...</em>, with
+<em>variable</em> added to the environment with <em>prog1...</em>'s
+output as a value. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-i</tt>&nbsp;: insist. If <em>prog1</em> exits non-zero,
+<tt>backtick</tt> exits with the same exit code (or 111 if <em>prog1</em>
+crashed for some reason). Without this option, <tt>backtick</tt> execs into
+<em>prog2...</em> no matter what <em>prog1</em> does, with the null word as
+<em>variable</em>'s value if <em>prog1</em> didn't write anything before
+dying. </li>
+ <li> <tt>-n</tt>&nbsp;: chomp an ending newline off <em>prog1...</em>'s
+output. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> You can start <em>prog2...</em> with "import <em>variable</em> unexport <em>variable</em>"
+to perform variable substitution. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/cd.html b/doc/cd.html
new file mode 100644
index 0000000..416dc42
--- /dev/null
+++ b/doc/cd.html
@@ -0,0 +1,45 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>execline: the cd command</title>
+ <meta name="Description" content="execline: the cd command" />
+ <meta name="Keywords" content="execline command cd chdir" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+</head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>cd</tt> program </h1>
+
+<p>
+<tt>cd</tt> changes the current working directory to a
+given directory, then executes a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     cd <em>dir</em> <em>prog...</em>
+</pre>
+
+<p>
+<tt>cd</tt> performs a
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/chdir.html">chdir()</a>
+system call on <em>dir</em>, then execs into <em>prog...</em>.
+</p>
+
+<h2> Notes </h2>
+
+<p>
+<tt>cd</tt> is a standard shell builtin. Be careful if you want to
+use the <tt>cd</tt> command outside of an <tt>execline</tt> script.
+</p>
+
+</body>
+</html>
diff --git a/doc/componentsb.txt b/doc/componentsb.txt
new file mode 100644
index 0000000..a191f04
--- /dev/null
+++ b/doc/componentsb.txt
@@ -0,0 +1,41 @@
+#!/command/execlineb
+
+# This execlineb script will sleep for 1 second, then print some
+# silly things on the standard output.
+
+
+foreground     # an unquoted string, evaluated to: foreground
+{              # A single opening brace, not included in the argv
+  sleep 1      # Two unquoted strings, evaluated to " sleep" and " 1"
+               # (without the quotation marks).
+}              # A single closing brace, evaluated to the empty word
+
+"echo"     # this is a quoted string. It will evaluate to the word: echo
+
+foo\ bar\ zoinx  # This is one word, since the spaces are escaped
+"foo bar zoinx"  # This is exactly the same word, written another way
+
+ " # this is not a comment, since it is inside a quoted string
+# This is not a comment either \" # nor is this " # but this is one
+
+"\0x41\66\0103D\n"   # This is the string ABCD followed by a newline.
+                     # Be careful: the newline will be part of the word.
+
+ \n   # this is not a newline, but the single word: n
+
+$23   # This will NOT be replaced by anything with execline-1.y, unless
+      # substitution is explicitly asked for in the script.
+      # The dollar is no special character for the execline binary.
+
+baz"$1"qux  # This will evaluate to the word baz$1qux
+baz\$1qux   # Same here
+baz$1qux    # Same here in execline-1.y
+
+${PATH}   # This will NOT be replaced by execline ; use the import command
+          # if you need the $PATH value.
+
+'this is not a string'  # it will be parsed as five separate words
+
+"\
+"        # This will be parsed as the empty word. A (backslash, newline)
+         # sequence inside a quoted string is entirely removed.
diff --git a/doc/define.html b/doc/define.html
new file mode 100644
index 0000000..4d5e0f2
--- /dev/null
+++ b/doc/define.html
@@ -0,0 +1,42 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>execline: the define command</title>
+ <meta name="Description" content="execline: the define command" />
+ <meta name="Keywords" content="execline command define" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+</head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>define</tt> program </h1>
+
+<p>
+<tt>define</tt> replaces a literal with a value, then executes
+another program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     define [ -s ] [ -C | -c ] [ -n ] [ -d <em>delim</em> ] <em>variable</em> <em>value</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>define</tt> performs
+<a href="el_substitute.html">variable substitution</a> on
+<em>prog...</em>, using <em>variable</em> as key and
+<em>value</em> as value.
+<tt>define</tt>'s options are used to <a href="el_transform.html">control
+the substitution mechanism</a>. </li>
+ <li> <tt>define</tt> then execs into the modified <em>prog...</em>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/dieshdiedie.html b/doc/dieshdiedie.html
new file mode 100644
index 0000000..dc3c661
--- /dev/null
+++ b/doc/dieshdiedie.html
@@ -0,0 +1,278 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: why execline and not sh</title>
+    <meta name="Description" content="execline: why execline and not sh" />
+    <meta name="Keywords" content="execline sh shell script language" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a><p />
+
+<h1> Why not just use <tt>/bin/sh</tt>&nbsp;? </h1>
+
+
+<a name="security">
+<h2> Security </h2></a>
+
+<p>
+ One of the most frequent sources of security problems in programs
+is <em>parsing</em>. Parsing is a complex operation, and it is easy to
+make mistakes while designing and implementing a parser. (See
+<a href="http://cr.yp.to/qmail/guarantee.html">what Dan Bernstein says
+on the subject</a>, section 5.)
+</p>
+
+<p>
+ But shells parse all the time. Worse, the <em>essence</em>
+of the shell is parsing: the parser and the runner are intimately
+interleaved and cannot be clearly separated, thanks to the
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html">specification</a>.
+Even worse, the
+shell sometimes has to perform <em>double parsing</em>, for instance
+after parameter expansion. This can lead to atrocities like
+<pre>
+zork="foo ; echo bar"
+touch $zork
+</pre> not doing what you would like them to do, even in that simple
+case. (<a href="http://www.zsh.org/">zsh</a> has a sane behaviour by
+default, at the expense of explicitly breaking the spec.)
+</p>
+
+<p>
+<tt>execlineb</tt> parses the script only once: when
+reading it. The parser has been designed to be simple and systematic,
+to reduce the potential for bugs - which you just cannot do
+with a shell. After <tt>execlineb</tt> has split up the script into
+words, no other parsing phase will happen, unless the user explicitly
+requires it. Positional parameters, when
+used, are never split, even if they contain spaces or newlines, unless
+the user explicitly requires it. Users control exactly what
+is split, what is done, and how.
+</p>
+
+<a name="portability">
+<h2> Portability </h2></a>
+
+<p>
+ The shell language was designed to make scripts portable across various
+versions of Unix. But it is actually really hard to write a portable shell
+script. There are dozens of distinct
+<tt>sh</tt> flavours, not even counting the openly incompatible
+<tt>csh</tt> approach and its various <tt>tcsh</tt>-like followers.
+The <tt>ash</tt>, <tt>bash</tt>, <tt>ksh</tt> and <tt>zsh</tt> shells
+all exhibit a different behaviour, <em>even when they are
+run with the so-called compatibility mode</em>. From what I have
+seen on various experiments, only <tt>zsh</tt> is able to follow the
+specification to the letter, at the expense of being big and complex to
+configure. This is a source of endless problems for shell script writers,
+who <em>should</em> be able to assume that a script will run everywhere,
+but <em>cannot</em> in practice. Even a simple utility like <tt>test</tt>
+cannot be used safely with the normalized options, because most shells
+come with a builtin <tt>test</tt> that does <em>not</em> respect the
+specification to the letter. And let's not get started about <tt>echo</tt>,
+which has its own set of problems. Rich Felker has
+<a href="http://www.etalabs.net/sh_tricks.html">a page</a> listing tricks
+to use to write portable shell scripts. Writing a portable script should
+not be that hard.
+</p>
+
+<p>
+execline scripts are portable. There is no
+complex syntax with opportunity to have an undefined or nonportable
+behaviour. The execline package is portable across platforms:
+there is no reason for vendors or distributors to fork their own
+incompatible version.
+ Scripts will
+not break from one machine to another; if they do,
+it's not a "portability problem", it's a bug. You are then encouraged
+to find the program that is responsible for the different behaviour,
+and send a bug-report to the program author - including me, if the
+relevant program is part of the execline distribution.
+</p>
+
+<p>
+ A long-standing problem with Unix scripts is the shebang line, which
+requires an absolute path to the interpreter. Scripts are only portable
+as is if the interpreter can be found at the same absolute path on every
+system. With <tt>/bin/sh</tt>, it is <em>almost</em> the case (Solaris
+manages to get it wrong by having a non-POSIX shell as <tt>/bin/sh</tt>
+and requiring something like <tt>#!/usr/xpg4/bin/sh</tt> to get a POSIX
+shell to interpret your script). Other scripting languages are not so
+lucky: perl can be <tt>/bin/perl</tt>, <tt>/usr/bin/perl</tt>,
+<tt>/usr/local/bin/perl</tt> or something else entirely. For those cases,
+some people advocate the use of <tt>env</tt>: <tt>#!/usr/bin/env perl</tt>.
+But first, <tt>env</tt> can only find interpreters that can be found via the
+user's PATH environment variable, which defeats the purpose of having an
+absolute path in the shebang line in the first place; and second, this only
+displaces the problem: the <tt>env</tt> utility does not
+have a guaranteed absolute path. <tt>/usr/bin/env</tt> is the usual
+convention, but not a strong guarantee: it is valid for systems to have
+<tt>/bin/env</tt> instead, for instance.
+</p>
+
+<p>
+<tt>execline</tt> suffers from the same issues. <tt>#!/bin/execlineb</tt>&nbsp;?
+<tt>#!/usr/bin/execlineb</tt>&nbsp;? This is the only portability problem that
+you will find with execline, and it is common to every script language. 
+</p>
+
+<p>
+ The real solution to this portability problem is a convention that
+guarantees fixed absolute paths for executables, which the FHS does not do.
+The <a href="http://cr.yp.to/slashpackage.html">slashpackage</a> convention is
+such an initiative, and is well-designed; but as with every
+convention, it only works if everyone follows it, and unfortunately,
+slashpackage has not
+found many followers. Nevertheless, like every skarnet.org package, execline
+can be configured to follow the slashpackage convention.
+</p>
+
+<a name="simplicity">
+<h2> Simplicity </h2></a>
+
+<p>
+ I originally wanted a shell that could be used on an embedded system.
+Even the <tt>ash</tt> shell seemed big, so I thought of writing my
+own. Hence I had a look at the
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html">sh
+specification</a>... and ran away screaming.
+This specification
+is <em>insane</em>. It goes against every good programming
+practice; it seems to have been designed only to give headaches
+to wannabe <tt>sh</tt> implementors.
+</p>
+
+<p>
+ POSIX cannot really be blamed for that: it only normalizes existing, historical
+behaviour. One can argue whether it is a good idea to normalize atrocious
+behaviour for historical reasons, as is the case with the infamous
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/gets.html">gets</a>
+function, but this is the way it is.
+</p>
+
+<p>
+ The fact remains that modern shells have to be compatible with that historical
+nonsense and that makes them big and complex at best, or incompatible and ridden
+with bugs at worst.
+An OpenBSD developer said to me, when asked about the OpenBSD <tt>/bin/sh</tt>:
+"It works, but it's far from not being a nightmare".
+</p>
+
+<p>
+ Nobody should have
+nightmare-like software on their system. Unix is simple. Unix
+was designed to be simple. And if, as Dennis Ritchie said, "it takes a
+genius to understand the simplicity", that's because incompetent people
+took advantage of the huge Unix flexibility to write insanely crappy or
+complex software. System administrators can only do a decent job when
+they understand how the programs they run are supposed to work. People
+are slowly starting to grasp this (or are they&nbsp;? We finally managed
+to get rid of sendmail and BIND, but GNU/Linux users seem happy to
+welcome the era of D-Bus and systemd. Will we ever learn&nbsp;?) - but even
+<tt>sh</tt>, a seemingly simple and basic Unix program, is hard to
+understand when you lift the cover.
+</p>
+
+<p>
+ So I decided to forego sh entirely and take a new approach. So far it
+has been working.
+ The <a href="grammar.html">execline specification</a> is simple, and,
+as I hope to have shown, easy to implement without too many bugs or
+glitches.
+</p>
+
+<a name="performance">
+<h2> Performance </h2></a>
+
+<p>
+ Since it was made to run on an embedded system, <tt>execline</tt> was
+designed to be light in memory usage. And it is.
+</p>
+
+<ul>
+ <li> No overhead due to interactive support. </li>
+ <li> No overhead due to unneeded features. Since every command performs
+its task then executes another command, all occupied resources are instantly
+freed. By contrast, a shell stays in memory during the whole execution
+time. </li>
+ <li> Very limited use of the C library. Only the C interface to the
+kernel's system calls, and some very basic functions like <tt>malloc()</tt>,
+are used in the C library. In addition to avoiding the horrible interfaces
+like <tt>stdio</tt> and the legacy libc bugs, this approach makes it easy
+to statically compile execline - you will want to do that on an embedded
+system, or just to gain performance. </li>
+</ul>
+
+<p>
+ You can have hundreds of execline scripts running simultaneously on an
+embedded box. Not exactly possible with a shell.
+</p>
+
+<p>
+ For scripts than do not require many computations that a shell can do
+without calling external programs,
+ <tt>execline</tt> is <em>faster</em> than the shell.
+Unlike <tt>sh</tt>'s
+one, the <tt>execline</tt> parser is simple and
+straightforward; actually, it's more of a lexer than a parser, because
+the execline language has been designed to be LL(1) - keep it simple,
+stupid.
+execline scripts get analysed and launched practically without a delay.
+</p>
+
+<a name="comparison" />
+<ul>
+ <li>
+ The best use case of execline is a linear, straightforward script, a
+simple command line that does not require the shell's processing power.
+In that case, execline will skip the shell's overhead and win big time
+on resource usage and execution speed. </li>
+ <li> For longer scripts that fork a few commands, with a bit of
+control flow, on average, an execline script will run at roughly the
+same speed as the equivalent shell script, while using less resources. </li>
+ <li> The worst use case of execline is when the shell is used as a
+programming language, and the script loops over complex internal constructs
+that execline is unable to replicate without forking. In that case,
+execline will waste a lot of time in fork/exec system calls that the
+shell does not have to perform, and be noticeably slower. execline has
+been designed as a <em>scripting</em> language, not as a <em>programming</em>
+language: it is efficient at being the glue that ties together programs
+doing a job, not at implementing a program's logic. </li>
+</ul>
+
+<a name="limitations">
+<h2> execline limitations </h2></a>
+
+<ul>
+ <li> <tt>execline</tt> can only handle scripts that fit in one <em>argv</em>.
+Unix systems have a limit on the <em>argv</em>+<em>envp</em> size;
+<tt>execline</tt> cannot execute scripts that are bigger than this limit.</li>
+ <li> <tt>execline</tt> commands do not perform signal handling. It is not
+possible to trap signals inside an execline script. If you want to trap
+signals, write a specific C program, or use a shell. </li>
+ <li> Due to the <tt>execline</tt> design, maintaining a state is
+difficult. Information has to transit via environment variables or
+temporary files, which makes commands like
+<a href="loopwhilex.html">loopwhilex</a> a bit painful to handle. </li>
+ <li> Despite all its problems, the main shell advantage (apart from
+being available on every Unix platform, that is) is that it
+is often <em>convenient</em>. Shell constructs can be terse and short,
+where <tt>execline</tt> constructs will be verbose and lengthy. </li>
+ <li> An execline script is generally heavier on <tt>execve()</tt> than
+the average shell script - notably in programs where the shell can
+use builtins. This can lead to a performance loss, especially when
+executed programs make numerous calls to the dynamic linker: the system
+ends up spending a lot of time resolving dynamic symbols. If it is a
+concern to you, you should try and <em>statically compile</em> the
+execline package, to eliminate the dynamic resolution costs. Unless
+you're heavily looping around <tt>execve()</tt>,
+the remaining costs will be negligible. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/dollarat.html b/doc/dollarat.html
new file mode 100644
index 0000000..2c09592
--- /dev/null
+++ b/doc/dollarat.html
@@ -0,0 +1,86 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>execline: the dollarat command</title>
+ <meta name="Description" content="execline: the dollarat command" />
+ <meta name="Keywords" content="execline command dollarat" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+</head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>dollarat</tt> program </h1>
+
+<p>
+<tt>dollarat</tt> prints the positional parameters of an execline script.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     dollarat [ -n ] [ -0 | -d <em>delimchar</em> ]
+</pre>
+
+<ul>
+ <li> <tt>dollarat</tt> reads the number <em>n</em> of "positional
+parameters" in the <tt>#</tt> environment variable. If that variable
+is not set or does not contain a valid <em>n</em>, <tt>dollarat</tt>
+exits 100. </li>
+ <li> <tt>dollarat</tt> prints the value of the <tt>1</tt> environment
+variable, then <em>delimchar</em>, then the value of the <tt>2</tt>
+environment variable... and so on until <tt><em>n</em></tt>. If one of
+these variables is not set, <tt>dollarat</tt> exits 100. </li>
+ <li> If everything runs OK, <tt>dollarat</tt> exits 0. This makes it
+one of the rare "exiting" execline commands. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-n</tt>&nbsp;: <em>chomp</em>. Do not print the last
+<em>delimchar</em>. </li>
+ <li> <tt>-d</tt>&nbsp;<em>delimchar</em>&nbsp;: use the character
+<em>delimchar</em> as separator between the arguments. Default: <tt>\n</tt>.
+If <em>delimchar</em> has more than one character, only the first one is
+used. If <em>delimchar</em> is the empty string, then <tt>dollarat</tt>
+will output the positional parameters as a
+<a href="el_transform.html#netstrings">sequence of netstrings</a> (and the
+<tt>-n</tt> option will be ignored). </li>
+ <li> <tt>-0</tt>&nbsp;: use the null character as separator. Any <tt>-d</tt>
+argument will be ignored. Warning: this option should only be used to feed
+data to programs that know how to handle null-separated lists. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> You can use <tt>dollarat&nbsp;-d&nbsp;""</tt> along with the
+<a href="forbacktickx.html">forbacktickx</a> command to reliably loop
+over the positional parameters:
+<pre>
+ #!/command/execlineb
+ forbacktickx -d "" ARG { dollarat -d "" }
+ dosomething $ARG
+</pre>
+
+ will call <tt>dosomething</tt> in turn on each argument to the script.
+That will work even if those arguments contain spaces, newlines,
+or other fancy characters. </li>
+
+ <li> Alternatively, instead of encoding data into a netstring, you can
+use a null-separated list, which will work the same way:
+<pre>
+ #!/command/execlineb
+ forbacktickx -0 ARG { dollarat -0 }
+ dosomething $ARG
+</pre> </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/el_pushenv.html b/doc/el_pushenv.html
new file mode 100644
index 0000000..2ae16dd
--- /dev/null
+++ b/doc/el_pushenv.html
@@ -0,0 +1,173 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: pushing and popping the environment</title>
+    <meta name="Description" content="execline: pushing and popping the environment" />
+    <meta name="Keywords" content="execline environment push pop el_pushenv el_popenv" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> Pushing and popping the environment </h1>
+
+<p>
+ The <a href="execlineb.html">execlineb</a> launcher
+can store <em>positional
+parameters</em>, i.e. arguments given to your script, into the
+environment. The <tt>#</tt> variable contains the number of arguments;
+the <tt>0</tt> variable contains the name of your execline script;
+the <tt>1</tt> variable contains the first argument; and so on.
+</p>
+
+<p>
+ Up to execline-1.04, this could create problems with nested scripts:
+the inner script would overwrite the outer script's parameters, and
+there was no way to get them back. In particular, writing execline
+commands in the execline language via the
+<a href="runblock.html">runblock</a> command was impossible.
+</p>
+
+<a name="push" />
+
+<p>
+ To solve that issue, execline now implements a kind of <em>environment
+stack</em>. When execlineb reads the arguments, it does
+not overwrite the positional parameters, but <em>pushes</em> them on a
+stack:
+</p>
+
+<ul>
+ <li> <tt>#</tt> will be set to the current number of arguments </li>
+ <li> but if a variable named <tt>#</tt> existed before, it is renamed <tt>#:1</tt> </li>
+ <li> and if a variable named <tt>#:1</tt> also existed, it is renamed <tt>#:2</tt> </li>
+ <li> ... and so on until <tt>#:<em>n+1</em></tt> doesn't exist. </li>
+</ul>
+
+<p>
+ Same goes for the other <em>positional parameters</em>.
+</p>
+
+<p>
+ The script then runs; and commands such as
+<a href="elgetpositionals.html">elgetpositionals</a> use the current
+frame of positional parameters, without paying attention to the deeper
+levels.
+</p>
+
+<a name="pop" />
+
+<p>
+ When you are done with the arguments, it is advisable to <em>drop</em>
+the current frame, and <em>pop</em> the environment stack to get it back
+to its previous state:
+</p>
+
+<ul>
+ <li> <tt>#</tt> will be unset </li>
+ <li> but if <tt>#:1</tt> exists, it will be renamed <tt>#</tt> </li>
+ <li> and if <tt>#:2</tt> exists, it will be renamed <tt>#:1</tt> </li>
+ <li> ... and so on until <tt>#:<em>n+1</em></tt> doesn't exist. </li>
+</ul>
+
+<p>
+ Again, same goes for the other <em>positional parameters</em>. <br />
+The <a href="runblock.html">runblock</a> command will perform that
+<em>pop</em> operation automatically; the standard "manual" way to
+perform it is to use the <a href="emptyenv.html">emptyenv -P</a> command.
+</p>
+
+<h2> A pop example </h2>
+
+<p>
+ Suppose you want to run the long-lived program <em>prog</em> after
+printing the list of its arguments.
+</p>
+
+<pre>
+ #!/command/execlineb
+ elgetpositionals
+ foreground { echo $0 $@ }
+ prog $@
+</pre>
+
+<p>
+will work, but will pollute <em>prog</em>'s environment with a set of
+positional parameters that have no meaning to it. A better script is:
+</p>
+
+<pre>
+ #!/command/execlineb
+ elgetpositionals
+ foreground { echo $0 $@ }
+ emptyenv -P
+ prog $@
+</pre>
+
+<p>
+which will run <em>prog</em> with the same environment as the script's
+caller.
+</p>
+
+<a name="integrated" />
+
+<h2> Substituting positional parameters without touching the environment </h2>
+
+<p>
+ Most of the time, you just need to substitute the positional parameters
+in your execline script, and don't need to go through the whole
+<a href="elgetpositionals.html">elgetpositionals</a> and
+<a href="emptyenv.html">emptyenv</a> chain. execline comes with an
+integrated substitution mechanism, that does not touch the environment
+at all: the <tt>-S&nbsp;<em>n</em></tt> option.
+</p>
+
+<p>
+ Scripts beginning with:
+</p>
+
+<pre>
+#!/command/execlineb -S<em>n</em>
+<em>foobar...</em>
+</pre>
+
+<p>
+ are equivalent to:
+</p>
+
+<pre>
+#!/command/execlineb
+elgetpositionals -P<em>n</em>
+emptyenv -P
+<em>foobar...</em>
+</pre>
+
+<p>
+ So, to summarize, from most efficient (but less flexible) to least efficient
+(but more flexible):
+</p>
+
+<ul>
+ <li> Use <tt>execlineb -P</tt> if you don't need positional parameters
+at all; for instance, in
+<a href="http://skarnet.org/software/s6/">s6</a> or
+<a href="http://smarden.org/runit/">runit</a> <em>run scripts</em>. </li>
+ <li> Use <tt>execlineb -S<em>n</em></tt> if you need only simple
+positional parameter substitution in your script - no
+<a href="shift.html">shift</a> or <a href="elgetopt.html">elgetopt</a>,
+no <tt>import 1</tt>. </li>
+ <li> Use <tt>execlineb -p</tt>, then <tt>elgetpositionals</tt> if
+you don't mind overwriting the current stack of positional parameters. </li>
+ <li> Use <tt>execlineb</tt>, then <tt>elgetpositionals</tt>, then
+<tt>emptyenv -P</tt> if you need the full power of positional parameter
+handling. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/el_semicolon.html b/doc/el_semicolon.html
new file mode 100644
index 0000000..615b411
--- /dev/null
+++ b/doc/el_semicolon.html
@@ -0,0 +1,124 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta http-equiv="Content-Language" content="en" />
+<title>execline: block management</title>
+<meta name="Description" content="execline: block management" />
+<meta name="Keywords" content="execline block blocks null argument tilda semicolon el_semicolon" />
+<!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+</head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> Blocks </h1>
+
+<p>
+A command line (and thus an execline script) is one-dimensional. But a
+Unix execution flow can be <em>two</em>-dimensional: when two
+instructions are sequenced, for instance. In that case, we need a
+way to extract <em>two</em> command lines from <em>one</em> argv.
+That is precisely what <em>blocks</em> are made for.
+</p>
+
+<p>
+ execline commands that need more than one linear set of arguments
+use blocks. For instance, the
+<a href="foreground.html">foreground</a> command needs to spawn a
+first process, then execute into a second one. It reads the command
+line for the first process from a block, and the command line for the
+second process from the rest of the argv. In the following script:
+</p>
+<pre>
+ #!/command/execlineb
+ foreground { echo 1 } echo 2
+</pre>
+<p>
+ <tt>echo&nbsp;1</tt> is read from a block and spawned; then
+<tt>echo&nbsp;2</tt> is executed.
+</p>
+
+<h2> execlineb syntax </h2>
+
+<p>
+ In <a href="execlineb.html">execlineb</a> scripts, blocks are
+delimited by braces. They can be nested.
+</p>
+
+<h2> argv syntax </h2>
+
+<p>
+ execlineb reads and parses the script, and converts it into an <em>argv</em>
+(a simple Unix command line) with a different syntax for blocks. 
+In an argv, blocks are not delimited by braces;
+they are made of <em>quoted arguments</em> and terminated by an
+empty word (""). A quoted argument begins with a space.
+ Nested blocks are represented by arguments being
+quoted several times, i.e. having several spaces in front of them;
+an empty word inside a block
+gets quoted too, i.e. it will be represented as a series of
+spaces.
+</p>
+
+<p>
+ Actually, the block-reading commands know nothing about braces;
+they only understand the "quoted arguments + empty word" syntax.
+So if you want to use <a href="foreground.html">foreground</a>
+from your shell to sequence <tt>echo&nbsp;1</tt> and
+<tt>echo&nbsp;2</tt>, you will have to write
+</p>
+
+<pre>
+ $ foreground ' echo' ' 1' '' echo 2
+</pre>
+
+<p>
+ You do not really need to quote every argument inside a block in
+that simple case. The following command works as well:
+</p>
+
+<pre>
+ $ foreground echo 1 '' echo 2
+</pre>
+
+<p>
+ However, this is bad practice, because it leads to a security hole:
+commands that perform
+<a href="el_substitute.html">substitution</a> inside a block may
+produce empty words, which may modify your script's execution flow.
+</p>
+
+<pre>
+ $ define FOO '' foreground ' echo' ' ${FOO}' ' rm' ' -rf' ' /' '' echo blah
+</pre>
+
+<p>
+ is safe, whereas
+</p>
+
+<pre>
+ $ define FOO '' foreground echo '${FOO}' rm -rf / '' echo blah
+</pre>
+
+<p>
+ has very much unwanted results. (Don't try this at home.)
+</p>
+
+<p>
+ You can use the <tt>EXECLINE_STRICT</tt> environment variable to
+check proper block quoting. If that variable contains <tt>1</tt>,
+commands that read blocks will print a warning message everytime
+they find an unquoted argument inside a block. If that variable
+contains <tt>2</tt> or a bigger integer, commands will print an
+error message and die on unquoted arguments.
+<br /> You can use <a href="execlineb.html">execlineb</a>'s
+<tt>-w</tt> or <tt>-W</tt>
+switch to set <tt>EXECLINE_STRICT</tt> to <tt>1</tt> or <tt>2</tt>.
+</p>
+
+</body>
+</html>
diff --git a/doc/el_substitute.html b/doc/el_substitute.html
new file mode 100644
index 0000000..4da03f5
--- /dev/null
+++ b/doc/el_substitute.html
@@ -0,0 +1,309 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: variable substitution</title>
+    <meta name="Description" content="execline: variable substitution" />
+    <meta name="Keywords" content="execline variable substitution el_substitute" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> Variable substitution </h1>
+
+<p>
+ In a shell, when you write
+</p>
+<pre>
+ $ A='foobar' ; echo $A
+</pre>
+<p>
+ the <tt>echo</tt> command is given the argument <tt>foobar</tt>.
+The <tt>foobar</tt> <em>value</em> has been substituted for the
+<tt>A</tt> <em>variable</em>.
+</p>
+<p>
+ Although <tt>execline</tt> maintains no state, and thus has no
+real variables, it provides such a <em>substitution</em> facility
+via <em>substitution commands</em>, namely:
+</p>
+<ul>
+ <li> <a href="define.html">define</a> </li>
+ <li> <a href="import.html">import</a> </li>
+ <li> <a href="importas.html">importas</a> </li>
+ <li> <a href="elglob.html">elglob</a> </li>
+ <li> <a href="elgetpositionals.html">elgetpositionals</a> </li>
+ <li> <a href="multidefine.html">multidefine</a> </li>
+ <li> <a href="multisubstitute.html">multisubstitute</a> </li>
+</ul>
+
+<p>
+ A substitution command takes a <em>key</em>, i.e. a string
+(which can contain any character but <tt>$</tt>, <tt>{</tt> and
+<tt>}</tt>, although it is recommended to use only alphanumerical
+characters), and a way to compute a <em>value</em>.
+</p>
+
+<h2> Basics </h2>
+
+<ul>
+ <li> If the substitution key is <em>foo</em>, then the substitution
+command will look for every occurrence of <tt>${<em>foo</em>}</tt> or
+<tt>$<em>foo</em></tt> in the rest of its argv. Note that
+<tt>${<em>foo</em>}bar</tt> matches, but <tt>$<em>foo</em>bar</tt>
+<strong>does not</strong>. To be safe, always use the syntax with
+braces, unless <tt>$<em>foo</em></tt> is a word on its own. </li>
+ <li> Every match is then replaced with the <em>value</em>. </li>
+</ul>
+
+<p>
+The simplest example is the following:
+</p>
+
+<pre>
+#!/command/execlineb
+define FOO blah
+echo $FOO
+</pre>
+
+<p>
+ which will replace the <tt>FOO</tt> key with the <tt>blah</tt> value,
+then execute the <tt>echo</tt> command. So that script will print
+<tt>blah</tt> on stdout.
+</p>
+
+<a name="quoting" />
+<h2> Quoting </h2>
+
+<p>
+ execline allows you to write literal <tt>${<em>foo</em>}</tt> constructs
+even when the <em>foo</em> variable is being substituted, thanks to a
+quoting mechanism.
+ Brace (pun intended) yourself: the following is the most complex part
+of the whole language.
+</p>
+
+<h3> Rationale </h3>
+
+<p>
+ If we want to be able to have a literal <tt>${<em>foo</em>}</tt>, then:
+</p>
+<ul>
+ <li> The <tt>${<em>foo</em>}</tt> sequence will mean one of two things:
+be substituted, or <em>don't</em> be substituted. </li>
+ <li> The default (unquoted) action should be: substitute. </li>
+ <li> A sequence that means "do not substitute" should be able
+to appear literally. The quote character should also be able to
+appear literally before a sequence that means "substitute". (Tricky, eh&nbsp;?) </li>
+ <li> There should be as few quote characters as possible, to avoid
+shell-like quoting nightmares. </li>
+</ul>
+
+<h3> Syntax </h3>
+
+<p>
+ Rule:
+</p>
+
+<ul>
+ <li> The backslash (<tt>\</tt>) is a quote character for substitution commands. </li>
+ <li> The following rule applies only if the <em>foo</em> key is
+explicitly used in a substitution command. If no command tries to
+substitute anything for <em>foo</em>, sequences like
+<tt>${<em>foo</em>}</tt> and preceding backslashes are left untouched. </li>
+ <li> (Substitute.) If <tt>${<em>foo</em>}</tt> is preceded by <tt>2*n</tt> backslashes
+(an <strong>even</strong> number), the whole sequence will be
+replaced with <tt>n</tt> backslashes, followed by the substituted value. </li>
+ <li> (Do not substitute.) If <tt>${<em>foo</em>}</tt> is preceded by <tt>2*n+1</tt> backslashes
+(an <strong>odd</strong> number), the whole sequence will be replaced
+with  <tt>n</tt> backslashes, followed by the literal <tt>${<em>foo</em>}</tt>. </li>
+</ul>
+
+<p>
+ And now, the catch: the <a href="execlineb.html">execlineb</a> launcher,
+as well as the shell,
+interprets backslashes as escape characters. To make a word that contains
+a backlash, you need to write <em>two</em> backslashes in your execline
+script or shell command line. That means that the whole number of backslashes
+you must write before your <tt>${<em>foo</em>}</tt> sequence must be doubled
+for the substitution command to read the proper number of backslashes and
+perform its work correctly. <br />
+ Once you keep that in mind, the quoting rule is logical.
+</p>
+
+<h3> Example </h3>
+
+<p>
+ The quoting rule is best illustrated with the following example, where
+the <tt>A</tt> key is substituted, and the <tt>$B</tt> sequences mean
+nothing special.
+</p>
+
+<pre>
+#!/command/execlineb
+define A val
+foreground { echo $A \\$A \\\\$A \\\\\\$A \\\\\\\\$A \\\\\\\\\\$A }
+             echo $B \\$B \\\\$B \\\\\\$B \\\\\\\\$B \\\\\\\\\\$B
+</pre>
+<p>
+ prints
+</p>
+<pre>
+val $A \val \$A \\val \\$A
+$B \$B \\$B \\\$B \\\\$B \\\\\$B
+</pre>
+
+<p>
+ Phew.
+</p>
+
+<a name="el_transform">
+<h2> Value transformations </h2>
+</a>
+
+<p>
+ A value can go through
+<a href="el_transform.html">several transformations</a> before it is
+substituted. It can be <a href="el_transform.html#crunch">crunched</a>,
+<a href="el_transform.html#chomp">chomped</a>, and/or
+<a href="el_transform.html#split">split</a>.
+</p>
+
+<a name="split">
+<h2> Substitution of split values </h2>
+</a>
+
+<p>
+ A <a href="el_transform.html">split</a> value for <tt>FOO</tt> means that
+a word containing <tt>${FOO}</tt> will be replaced by zero, one, or
+(usually) more than one word. The value actually means a
+<em>list</em> of values.
+</p>
+
+<p>
+ The rule is: substituting a list of values
+(<em>v1</em>, <em>v2</em>, <em>...</em>) for a key <em>A</em> is the
+same as listing the substitutions of every value <em>v<tt>i</tt></em>
+for <em>A</em>. <br />
+ For instance,
+</p>
+
+<pre>
+#!/command/execlineb
+define -s FOO "v1 v2 v3" echo prefix-${FOO}-postfix
+</pre>
+
+<p>
+ will substitute three values for <tt>$FOO</tt>: <tt>v1</tt>, <tt>v2</tt>
+and <tt>v3</tt>. So the <tt>echo</tt> command will be called with three
+arguments: <tt>prefix-v1-postfix</tt>, <tt>prefix-v2-postfix</tt>, and
+<tt>prefix-v3-postfix</tt>.
+</p>
+
+<p>
+(Implementation note: the fact that word prefixes are kept is
+what makes execline's subtitutions secure.
+<a href="el_semicolon.html">Blocks</a> are implemented via prefix
+space characters; a substitution occurring inside a block will always produce
+words beginning with the right amount of spaces, thus substituted
+values cannot prematurely terminate a block.)
+</p>
+
+<a name="recursive" />
+<h3> Recursive substitutions </h3>
+
+<p>
+ A direct consequence of that rule is that substitutions will be performed
+recursively if more than one key appears in one word and the values for
+those keys are split. Parallel substitutions are performed from left to
+right. For instance, in
+</p>
+
+<pre>
+#!/command/execlineb
+define -s B "1 2 3" echo ${B}x${B}
+</pre>
+<p>
+ the <tt>${B}x${B}</tt> word will be replaced with <em>nine</em> words:
+<tt>1x1</tt>, <tt>1x2</tt>, <tt>1x3</tt>, <tt>2x1</tt>, <tt>2x2</tt>,
+<tt>2x3</tt>, <tt>3x1</tt>, <tt>3x2</tt>, and <tt>3x3</tt>, in that order.
+<br /> Here is an example with two distinct substitutions in parallel:
+</p>
+
+<pre>
+#!/command/execlineb
+multisubstitute
+{
+  define -s A "a b c d"
+  define -s B "1 2 3"
+}
+echo ${A}x${B}
+</pre>
+
+<p>
+ The <tt>${A}x${B}</tt> word will be replaced with <em>twelve</em> words:
+<tt>ax1</tt>, <tt>ax2</tt>, <tt>ax3</tt>, <tt>bx1</tt>, <tt>bx2</tt>,
+<tt>bx3</tt>, <tt>cx1</tt>, <tt>cx2</tt>, <tt>cx3</tt>, <tt>dx1</tt>,
+<tt>dx2</tt>, and <tt>dx3</tt>, in that order. You can check that the
+order of the <tt>define</tt> directives in
+<a href="multisubstitute.html">multisubstitute</a> does not matter.
+</p>
+
+<p>
+If the left-to-right order does not suit you, then you should perform
+<em>serial</em> substitutions. For instance, the previous script can
+be replaced with
+</p>
+
+<pre>
+#!/command/execlineb
+define -s B "1 2 3"
+define -s A "a b c d"
+echo ${A}x${B}
+</pre>
+<p>
+ and will substitute <tt>${B}</tt> first, then <tt>${A}</tt>. So it
+will print
+</p>
+
+<pre>
+ax1 bx1 cx1 dx1 ax2 bx2 cx2 dx2 ax3 bx3 cx3 dx3
+</pre>
+
+<p>
+ in that order.
+</p>
+
+<a name="brainfsck"></a>
+<h2> Not for the faint of heart </h2>
+
+<p>
+ If you think you have mastered the art of execline substitution, then
+you can try to do better than these people:
+</p>
+
+<ul>
+ <li><a href="http://jriou.org/">Jo&euml;l Riou</a>
+wrote the <a href="quine-jriou.txt">first execlineb quine</a>, using
+only <tt>echo</tt> as non-execline external command. </li>
+ <li> Shortly after, <a href="http://code.dogmap.org/">Paul Jarc</a>
+wrote a <a href="quine-prj.txt">much shorter quine</a>, using
+<tt>echo</tt> and <tt>env</tt> as non-execline external commands. He
+also wrote a <a href="quine-prj-2.txt">revised version</a>, using only
+<tt>echo</tt>, and a shorter <a href="quine-prj-3.txt">definitive
+version</a>. The last one is probably very close to the shortest
+possible execline quine. </li>
+ <li> <a href="http://www.madore.org/~david/">David Madore</a>
+wrote <a href="quine-dam.txt">another quine</a>, using <tt>printf</tt>.
+His quine is longer than the other ones, but is well-commented and can
+be used as a tutorial on how to write quines. :) </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/el_transform.html b/doc/el_transform.html
new file mode 100644
index 0000000..094533a
--- /dev/null
+++ b/doc/el_transform.html
@@ -0,0 +1,204 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: value transformation</title>
+    <meta name="Description" content="execline: value transformation" />
+    <meta name="Keywords" content="execline value transformation el_transform crunch chomp split" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> Value transformation </h1>
+
+<p>
+ You can apply 3 kinds of transformations to a value which is to be
+<a href="el_substitute.html">substituted</a> for a variable:
+crunching, chomping and splitting. They
+always occur in that order.
+</p>
+
+
+<a name="delim">
+<h2> Delimiters </h2>
+</a>
+
+<p>
+ The transformations work around <em>delimiters</em>. Delimiters are
+the semantic bounds of the "words" in your value.
+ You can use any character (except the null character, which you cannot
+use in execline scripts) as a delimiter, by giving a string consisting
+of all the delimiters you want as the argument to the <tt>-d</tt> option
+used by substitution commands. By default, the string "<tt>&nbsp;\n\r\t</tt>"
+is used, which means that the default delimiters are spaces, newlines,
+carriage returns and tabs.
+</p>
+
+
+<a name="crunch">
+<h2> Crunching </h2>
+</a>
+
+<p>
+ You can tell the substitution command to merge sets of consecutive
+delimiters into a single delimiter. For instance, to replace
+three consecutive spaces, or a space and 4 tab characters, with a
+single space. This is called <em>crunching</em>, and it is done
+by giving the <tt>-C</tt> switch to the substitution command. The
+remaining delimiter will always be the first in the sequence.
+</p>
+
+<p>
+ Crunching is mainly useful when also <a href="#split">splitting</a>.
+</p>
+
+<a name="chomp">
+<h2> Chomping </h2>
+</a>
+
+<p>
+ Sometimes you don't want the last delimiter in a value.
+ <em>Chomping</em> deletes the last character of a value if it is a
+delimiter. It can be requested by giving the <tt>-n</tt> switch to the
+substitution command. Note that chomping always happens <em>after</em>
+crunching, which means you can use crunching+chomping to ignore, for
+instance, a set of trailing spaces.
+</p>
+
+
+<a name="split">
+<h2> Splitting </h2>
+</a>
+
+<p>
+ In a shell, when you write
+</p>
+
+<pre>
+ $ A='foo bar' ; echo $A
+</pre>
+
+<p>
+ the <tt>echo</tt> command is given two arguments, <tt>foo</tt>
+and <tt>bar</tt>. The <tt>$A</tt> value has been <em>split</em>,
+and the space between <tt>foo</tt> and <tt>bar</tt> acted as a
+<em>delimiter</em>.
+</p>
+
+<p>
+If you want to avoid splitting, you must write something like
+</p>
+
+<pre>
+ $ A='foo bar' ; echo "$A"
+</pre>
+
+<p>
+ The doublequotes "protect" the spaces. Unfortunately, it's easy
+to forget them and perform unwanted splits during script execution
+- countless bugs happen because of the shell's splitting behaviour.
+</p>
+
+<p>
+ <tt>execline</tt> provides a <em>splitting</em> facility, with
+several advantages over the shell's:
+</p>
+
+<ul>
+ <li> Splitting has to be explicitly requested, by specifying the
+<tt>-s</tt> option to commands that perform
+<a href="el_substitute.html">substitution</a>. By default,
+substitutions are performed as is, without interpreting the
+characters in the value. </li>
+ <li> Positional parameters are never split, so that execline
+scripts can handle arguments the way the user intended to. To
+split <tt>$1</tt>, for instance, you have to ask for it
+specifically:
+<pre>
+#!/command/<a href="execlineb.html">execlineb</a> -S1
+<a href="define.html">define</a> -sd" " ARG1S $1
+blah $ARG1S
+</pre>
+ and $ARG1S will be split using the space character as only delimiter.
+ </li>
+ <li> Any character can be a delimiter. </li>
+</ul>
+
+<h3> How it works </h3>
+
+<ul>
+ <li> A substitution command can request that the substitution value
+be split, via the <tt>-s</tt> switch. </li>
+ <li> The splitting function parses the value, looking for delimiters.
+It fills up a structure, marking the split points, and the number
+<em>n</em> of words the value is to be split into.
+ <ul>
+  <li> A word is a sequence of characters in the value <em>terminated
+by a delimiter</em>. The delimiter is not included in the word. </li>
+  <li> If the value begins with <em>x</em> delimiters, the word list
+will begin with <em>x</em> empty words. </li>
+  <li> The last sequence of characters in the value will be recognized
+as a word even if it is not terminated by a delimiter, unless you have
+requested <a href="#chomp">chomping</a> and there was no delimiter at
+the end of the value <em>before</em> the chomp operation - in which case
+that last sequence will not appear at all. </li>
+ </ul> </li>
+ <li> The substitution rewrites the argv. A non-split value will
+be written as one word in the argv; a split value will be written
+as <em>n</em> separate words. </li>
+ <li> Substitution of split values is
+<a href="el_substitute.html#recursive">performed recursively</a>. </li>
+</ul>
+
+
+<a name="netstrings">
+<h3> Decoding netstrings </h3>
+</a>
+
+<p>
+ <a href="http://cr.yp.to/proto/netstrings.txt">Netstrings</a> are
+a way to reliably encode strings containing arbitrary characters.
+<tt>execline</tt> takes advantage of this to offer a completely safe
+splitting mechanism. If a substitution command is given an empty
+delimiter string (by use of the <tt>-d&nbsp;""</tt> option), the
+splitting function will try to interpret the value as a sequence
+of netstrings, every netstring representing a word. For instance,
+in the following command line:
+</p>
+
+<pre>
+ $ define -s -d "" A '1:a,2:bb,0:,7:xyz 123,1: ,' echo '$A'
+</pre>
+
+<p>
+ the <tt>echo</tt> command will be given five arguments:
+</p>
+
+<ul>
+ <li> the "<tt>a</tt>" string </li>
+ <li> the "<tt>bb</tt>" string </li>
+ <li> the empty string </li>
+ <li> the "<tt>xyz 123</tt>" string </li>
+ <li> the "<tt> </tt>" string (a single space) </li>
+</ul>
+
+<p>
+ However, if the value is not a valid sequence of netstrings, the
+substitution command will die with an error message.
+</p>
+
+<p>
+ The <a href="dollarat.html">dollarat</a> command, for instance,
+can produce a sequence of netstrings (encoding all the arguments
+given to an execline script), meant to be decoded by a substitution
+command with the <tt>-d&nbsp;""</tt> option.
+</p>
+
+</body>
+</html>
diff --git a/doc/elgetopt.html b/doc/elgetopt.html
new file mode 100644
index 0000000..a5650f2
--- /dev/null
+++ b/doc/elgetopt.html
@@ -0,0 +1,60 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>execline: the elgetopt command</title>
+ <meta name="Description" content="execline: the elgetopt command" />
+ <meta name="Keywords" content="execline command elgetopt options arguments" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+</head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+<p />
+
+<h1> The <tt>elgetopt</tt> program </h1>
+
+<p>
+<tt>elgetopt</tt> performs <tt>getopt</tt>-style parsing on the
+arguments to an execline script.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     elgetopt <em>optstring</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>elgetopt</tt> expects to find a valid number <em>n</em> of
+arguments in the <tt>#</tt> environment variable, and <em>n</em>+1
+environment variables <tt>0</tt>, <tt>1</tt>, ..., <tt><em>n</em></tt>.
+It exits 100 if it is not the case. </li>
+ <li> <tt>elgetopt</tt> <a href="el_pushenv.html">pushes</a>
+environment variables starting with <tt>ELGETOPT_</tt>. To get the
+previous values back, use
+<a href="emptyenv.html"><tt>emptyenv&nbsp;-o</tt></a>. </li>
+ <li> <tt>elgetopt</tt> looks into <tt>1</tt>, <tt>2</tt>... for options,
+as specified by <em>optstring</em>, which is a standard <tt>getopt</tt>
+string. </li>
+ <li> If the <tt>-<em>c</em></tt> switch is recognized, <tt>elgetopt</tt>
+sets the <tt>ELGETOPT_<em>c</em></tt> environment variable. The value
+of that variable is the argument to the <tt>-<em>c</em></tt> switch if
+it has one, and 1 otherwise. </li>
+ <li> After setting all recognized options, <tt>elgetopt</tt> makes
+new <tt>#</tt>, <tt>1</tt>, <tt>2</tt>... "positional parameters" with
+what remains. </li>
+ <li> <tt>elgetopt</tt> then execs into <em>prog...</em>. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> GNU-style options are not supported. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/elgetpositionals.html b/doc/elgetpositionals.html
new file mode 100644
index 0000000..d193413
--- /dev/null
+++ b/doc/elgetpositionals.html
@@ -0,0 +1,94 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>execline: the elgetpositionals command</title>
+ <meta name="Description" content="execline: the elgetpositionals command" />
+ <meta name="Keywords" content="execline command elgetpositionals arguments positional parameters" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+</head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+<p />
+
+<h1> The <tt>elgetpositionals</tt> program </h1>
+
+<p>
+<tt>elgetpositionals</tt> substitutes the positional parameters of an execline script.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     elgetpositionals [ -P <em>sharp</em> ] <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>elgetpositionals</tt> reads the number <em>n</em> of "positional
+parameters" in the <tt>#</tt> environment variable. If that variable
+is not set or does not contain a valid <em>n</em>, <tt>elgetpositionals</tt>
+exits 100. </li>
+ <li> <tt>elgetpositionals</tt> performs some
+<a href="el_substitute.html">substitutions</a> in parallel on
+<em>prog...</em>:
+<ul>
+ <li> key: <tt>#</tt>, value: <em>n</em> </li>
+ <li> key: <tt>0</tt>, value: the value of the <tt>0</tt> environment
+variable </li>
+ <li> key: <tt>1</tt>, value: the value of the <tt>1</tt> environment
+variable </li>
+ <li> ... and so on until <em>n</em> (or <em>sharp</em> if it is
+greater than <em>n</em>). Those values are never transformed. </li>
+ <li> key: <tt>@</tt>, value: all values of the variables from <tt>1</tt> to
+<tt><em>n</em></tt>. This value is <a href="el_transform.html#split">split</a>
+into <em>n</em> words. </li>
+</ul>
+ If a variable between <tt>0</tt> and <tt><em>n</em></tt> does not
+exist, <tt>elgetpositionals</tt> exits 100.
+ </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-P</tt>&nbsp;<em>sharp</em>&nbsp;: substitute at least
+<em>sharp</em>+1 positional parameters, from 0 to
+max(<em>n</em>, <em>sharp</em>). If <em>n</em>&lt;<em>sharp</em>,
+positional parameters between <em>n</em>+1 and <em>sharp</em> are
+replaced with the empty string. Not having the <tt>-P</tt> switch is
+equivalent to having <tt>-P&nbsp;0</tt>. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> A typical argument-taking execline script will
+often begin that way:
+<pre>
+ #!/command/execlineb
+ elgetopt <em>optstring</em>
+ elgetpositionals
+ <em>prog...</em>
+</pre>
+</li>
+ <li> If you are performing other substitutions that do not depend
+on the positional parameters, think about replacing the
+<tt>elgetpositionals</tt> call with a
+<a href="multisubstitute.html">multisubstitute</a> call containing
+the <tt>elgetpositionals</tt> directive. </li>
+ <li> If you are going to use the <a href="shift.html">shift</a>
+command, it is best to use <a href="importas.html">importas</a> to
+substitute the first positional parameters, then use <tt>shift</tt>,
+then <tt>elgetpositionals</tt>. That way, <tt>$@</tt> will correctly
+be replaced by the remaining arguments. More generally, you should
+try to use <tt>elgetpositionals</tt> as late as possible. </li>
+ <li> Use <tt>execlineb</tt>'s <tt>-S</tt> switch instead of
+<tt>elgetpositionals</tt> whenever you can. It is more efficient. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/elglob.html b/doc/elglob.html
new file mode 100644
index 0000000..5b5d6aa
--- /dev/null
+++ b/doc/elglob.html
@@ -0,0 +1,68 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>execline: the elglob command</title>
+ <meta name="Description" content="execline: the elglob command" />
+ <meta name="Keywords" content="execline command elglob pattern shell globbing" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+</head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+<p />
+
+<h1> The <tt>elglob</tt> program </h1>
+
+<p>
+<tt>elglob</tt> performs globbing on a pattern, then executes
+another program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     elglob [ -v ] [ -w ] [ -s ] [ -m ] [ -e ] [ -0 ] <em>variable</em> <em>pattern</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>elglob</tt> performs
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/glob.html">globbing</a>
+on <em>pattern</em>. </li>
+ <li> It then performs
+<a href="el_substitute.html">variable substitution</a> on
+<em>prog...</em>, using <em>variable</em> as key and the result of the
+globbing as value. The value is always split: it contains as many words
+as they are matches for the globbing pattern. </li>
+ <li> <tt>elglob</tt> then execs into the modified <em>prog...</em>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-v</tt>&nbsp;: verbose. If there is a problem while globbing, print
+a warning message on stderr. </li>
+ <li> <tt>-w</tt>&nbsp;: strict. If there is a problem while globbing, die
+immediately. This is harsh - you probably don't need that option. </li>
+ <li> <tt>-s</tt>&nbsp;: sort the matches. By default, the results are
+left unsorted. </li>
+ <li> <tt>-m</tt>&nbsp;: mark. Append a slash to each word that corresponds
+to a directory. </li>
+ <li> <tt>-e</tt>&nbsp;: no escape. Treat backslashes in <em>pattern</em>
+literally; do not allow quoting of metacharacters in <em>pattern</em> via
+backslashes. <strong>Warning</strong>: the
+<a href="execlineb.html">execlineb</a> launcher
+uses the backslash as their own escape character - if you want a
+backslash to be passed to <tt>elglob</tt>, do not forget to <em>double</em>
+it. </li>
+ <li> <tt>-0</tt>&nbsp;: null globbing. By default, if <em>pattern</em>
+matches nothing, it will be substituted as is (verbatim in one word). With
+this option, if <em>pattern</em> matches nothing, it will be properly
+substituted as zero word. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/emptyenv.html b/doc/emptyenv.html
new file mode 100644
index 0000000..81cf45a
--- /dev/null
+++ b/doc/emptyenv.html
@@ -0,0 +1,57 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the emptyenv program</title>
+    <meta name="Description" content="execline: the emptyenv program" />
+    <meta name="Keywords" content="execline command emptyenv" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>emptyenv</tt> program </h1>
+
+<p>
+<tt>emptyenv</tt> empties the current environment, or cleans it up; then
+executes a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     emptyenv [ -p ] <em>prog...</em>
+     emptyenv -c <em>prog...</em>
+     emptyenv [ -o ] [ -P ] <em>prog...</em>
+</pre>
+
+<p>
+By default, <tt>emptyenv</tt> unsets all environment variables, then
+execs into <em>prog</em> with its arguments. Options control which
+environment variables are unset.
+</p>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-p</tt>&nbsp;: keep the <tt>PATH</tt> environment variable. </li>
+ <li> <tt>-c</tt>&nbsp;: clean up. Do not empty the environment. Instead,
+remove every variable used internally by the execline programs, to avoid
+any interference with or information leakage to external programs. </li>
+ <li> <tt>-o</tt>&nbsp;: <a href="el_pushenv.html#pop">pop</a> environment
+variables starting with <tt>ELGETOPT_</tt>. You might want to do this
+before executing a final program from a script that uses
+<a href="elgetpositionals.html">elgetpositionals</a>. </li>
+ <li> <tt>-P</tt>&nbsp;: <a href="el_pushenv.html#pop">pop</a> environment
+variables starting with <tt>#</tt>, <tt>0</tt> to <tt>9</tt>, and
+<tt>EXECLINE_</tt>. You might want to do this before executing a final program
+from a script launched by <a href="execlineb.html">execlineb</a>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/exec.html b/doc/exec.html
new file mode 100644
index 0000000..8655541
--- /dev/null
+++ b/doc/exec.html
@@ -0,0 +1,50 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the exec program</title>
+    <meta name="Description" content="execline: the exec program" />
+    <meta name="Keywords" content="execline command exec" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>exec</tt> program </h1>
+
+<p>
+<tt>exec</tt> executes the command line it is given.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     exec [ -c ] [ -l ] [ -a <em>argv0</em> ]<em>prog...</em>
+</pre>
+
+<p>
+<tt>exec</tt> execs into <em>prog...</em>. It does nothing else.
+<br /> Without options, <tt>exec</tt> can be seen as the execline NOP.
+</p>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-c</tt>&nbsp;: empty the environment. <em>prog</em> is executed with no environment variables. <strong>Warning:&nbsp;</strong><em>prog</em> will run with an empty PATH, so make sure it does not rely on it. </li>
+ <li> <tt>-l</tt>&nbsp;: login. Prepends <em>prog</em>'s argv[0] with a dash. </li>
+ <li> <tt>-a <em>argv0</em></tt>&nbsp;: argv0. Replace <em>prog</em>'s argv[0] with <em>argv0</em>. This is done <em>before</em> adding a dash, if the <tt>-l</tt> option is also present. </li>
+</ul>
+
+<p>
+The <tt>exec</tt> command, along with its options, is designed to emulate
+the standard <tt>exec</tt> shell builtin, which replaces the shell with the
+command it is given.
+</p>
+
+</body>
+</html>
diff --git a/doc/execline-shell.html b/doc/execline-shell.html
new file mode 100644
index 0000000..b3526f0
--- /dev/null
+++ b/doc/execline-shell.html
@@ -0,0 +1,53 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>execline: the execline-shell script</title>
+ <meta name="Description" content="execline: the execline-shell script" />
+ <meta name="Keywords" content="execline script execline-shell" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+</head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>execline-shell</tt> script </h1>
+
+<p>
+<tt>execline-shell</tt> executes <tt>$HOME/.execline-shell</tt>
+with the arguments it is given.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     /etc/execline-shell
+</pre>
+
+<ul>
+ <li> <tt>execline-shell</tt> transforms itself into
+<tt>${HOME}/.execline-shell $@</tt>. </li>
+ <li><tt>${HOME}/.execline-shell</tt> must be readable and
+executable by the user. It must exec into an interactive
+shell with <tt>$@</tt> as its argument.</li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>execline-shell</tt> is meant to be used as the <tt>SHELL</tt>
+environment variable value. It allows one to specify his favourite shell and
+shell configuration in any language, since the <tt>${HOME}/.execline-shell</tt>
+file can be any executable program. <tt>${HOME}/.execline-shell</tt> can be seen
+as a portable <tt>.<em>whatever</em>rc</tt> file. </li>
+ <li> As an administrator-modifiable configuration file, <tt>execline-shell</tt>
+provided in execline's <tt>etc/</tt> subdirectory, and should be copied by
+the administrator to <tt>/etc</tt>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/execline-startup.html b/doc/execline-startup.html
new file mode 100644
index 0000000..5df401d
--- /dev/null
+++ b/doc/execline-startup.html
@@ -0,0 +1,59 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>execline: the execline-startup script</title>
+ <meta name="Description" content="execline: the execline-startup script" />
+ <meta name="Keywords" content="execline execline-startup startup login script .profile" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+</head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>execline-startup</tt> script </h1>
+
+<p>
+<tt>execline-startup</tt> performs some system-specific
+login initialization, then executes <tt>${HOME}/.execline-loginshell</tt>.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     /etc/execline-startup
+</pre>
+
+<ul>
+ <li> <tt>execline-startup</tt> sets the <tt>SHELL</tt>
+environment variable to <tt>/etc/execline-shell</tt>.
+It then performs some system-specific initialization, and
+transforms itself into <tt>${HOME}/.execline-loginshell $@</tt>. </li>
+ <li><tt>${HOME}/.execline-loginshell</tt> must be readable and
+executable by the user. It must exec into <tt>$SHELL $@</tt>. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>execline-startup</tt> is an
+<a href="execlineb.html">execlineb</a> script; hence, it is readable
+and modifiable. It is meant to be modified by the system administrator
+to perform system-specific login-time initialization. </li>
+ <li> As a modifiable configuration file, execline-startup is provided in execline's
+<tt>etc/</tt> subdirectory, and should be copied by the administrator
+to <tt>/etc</tt>. </li>
+ <li> <tt>execline-startup</tt> is meant to be used as a login shell.
+System administrators should manually add <tt>/etc/execline-startup</tt>
+to the <tt>/etc/shells</tt> file. The <tt>/etc/execline-startup</tt>
+file itself plays the role of the <tt>/etc/profile</tt> file, and
+<tt>${HOME}/.execline-loginshell</tt> plays the role of the
+<tt>${HOME}/.profile</tt> file.</li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/execlineb.html b/doc/execlineb.html
new file mode 100644
index 0000000..64f29bd
--- /dev/null
+++ b/doc/execlineb.html
@@ -0,0 +1,246 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-6" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the execlineb command</title>
+    <meta name="Description" content="execline: the execlineb command" />
+    <meta name="Keywords" content="execline command execlineb launcher" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>execlineb</tt> program </h1>
+
+<p>
+<tt>execlineb</tt> reads and executes a script.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     execlineb [ -q | -w | -W ] [ -p | -P | -S <em>nmin</em> ] -c <em>script</em> [ <em>args...</em> ]
+</pre>
+
+<p>
+or
+</p>
+
+<pre>
+     execlineb [ -q | -w | -W ] [ -p | -P | -S <em>nmin</em> ] <em>scriptfile</em> [ <em>args...</em> ]
+</pre>
+
+<p>
+or in an executable file:
+</p>
+
+<pre>
+#!/command/execlineb [ -qwWpPS<em>nmin</em> ]
+<em>script</em>
+</pre>
+
+<p>
+ <em>Parsing phase</em>.
+</p>
+
+<ul>
+ <li> <tt>execlineb</tt> reads and parses the script it is given.
+It exits 100 on a syntax error and 111 on a temporary error.
+It makes an <em>argv</em>, i.e. a system command line, with the
+parsed script. If the <em>argv</em> is empty, <tt>execlineb</tt>
+exits 0. </li>
+</ul>
+
+<p>
+ <em>Environment management phase</em>.
+</p>
+
+<ul>
+ <li> <em>Pushing the current stack frame.</em> If neither the
+<tt>-p</tt> nor the <tt>-P</tt> nor the <tt>-S</tt> option is set:
+<tt>execlineb</tt> <a href="el_pushenv.html">pushes</a>
+the current positional parameters, i.e. environment variables that
+start with <tt>#</tt>, <tt>0</tt>, <tt>1</tt>, ..., <tt>9</tt>.
+To get the previous values back, use
+<a href="emptyenv.html"><tt>emptyenv&nbsp;-P</tt></a>. </li>
+ <li> <em>Setting the new stack frame.</em> If neither the <tt>-P</tt>
+nor the <tt>-S</tt> option is set:
+ <ul>
+  <li> <tt>execlineb</tt> sets the <tt>#</tt> environment variable to
+the number <em>n</em> of <em>args</em> it is given. </li>
+  <li> It sets the <tt>0</tt> environment variable to the name
+of the script - or to the <tt>execlineb</tt> invocation name
+if the <tt>-c</tt> option is used. </li>
+  <li> It sets the <tt>1</tt>, <tt>2</tt>, ... <tt><em>n</em></tt>
+environment variables to the different <em>args</em>. </li>
+ </ul> </li>
+</ul>
+
+<p>
+ <em>Execution phase</em>.
+</p>
+
+<ul>
+ <li> <tt>execlineb</tt> executes into the <em>argv</em> it
+has built from the script.
+There is only one command line for the
+whole script: the <tt>execlineb</tt> binary is a <em>launcher</em>,
+whose sole purpose is to execute into that command line. It does
+not stay in memory like a traditional <em>interpreter</em> would. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-c&nbsp;<em>script</em></tt>&nbsp;: execute <em>script</em>, do not
+look for a file. </li>
+</ul>
+
+<p>
+ See below for the other options.
+</p>
+
+<h2> Syntax of scripts </h2>
+
+<p>
+ An execlineb script is a string that must not contain the null character.
+<tt>execlineb</tt> parses it and divides it into <em>words</em>.
+
+ The parser recognizes the following components:
+</p>
+
+<ul>
+ <li> <em>whitespace</em> is defined as spaces, tabs, newlines and
+carriage returns. Words are always separated by whitespace.</li>
+ <li> A <em>quoted string</em> begins with a doublequote (<tt>"</tt>)
+and ends with another doublequote. Quoted doublequotes must be prefixed
+by a backslash (<tt>\</tt>). Quoted strings always evaluate to exactly
+one word. For instance, <tt>""</tt> evaluates to the empty word. </li>
+ <li> The <tt>\a</tt>, <tt>\b</tt>, <tt>\t</tt>, <tt>\n</tt>, <tt>\v</tt>,
+<tt>\f</tt>, and <tt>\r</tt> sequences are recognized in quoted
+strings, and are converted to the ASCII numbers 7, 8, 9, 10, 11, 12 and
+13 respectively. </li>
+ <li> Inside a quoted string, backslashed
+newlines disappear completely. </li>
+ <li> <tt>\0x<em>ab</em></tt> sequences are recognized in quoted strings
+and evaluate to ASCII hexadecimal number <em>ab</em>. </li>
+ <li> <tt>\0<em>abc</em></tt> sequences are recognized in quoted strings
+and evaluate to ASCII octal number <em>abc</em>. </li>
+ <li> <tt>\<em>abc</em></tt> sequences are recognized in quoted strings
+and evaluate to ASCII decimal number <em>abc</em>. <em>a</em> must not
+be zero. </li>
+ <li> A comment starts with a <tt>#</tt> and ends with the line. Comments
+are not recognized inside quoted strings. </li>
+ <li> Anything else is an unquoted string, that can evaluate to
+zero or more words. </li>
+ <li> Any character can be escaped in unquoted strings by prepending
+it with a backslash. It works the same way in quoted strings, except
+for the special sequences described above. </li>
+</ul>
+
+<p>
+ You can see an example of distinct <tt>execlineb</tt> components
+<a href="componentsb.txt">here</a>.
+</p>
+
+<p>
+ In addition to that simple lexing,
+<tt>execlineb</tt> performs the following higher-level parsing:
+</p>
+
+<ul>
+   <li> A word consisting of a single <em>opening brace</em> (<tt>{</tt>)
+increments an internal level counter, <em>blevel</em>, and disappears from the
+<em>argv</em>. Quoted open braces do not have that behaviour. </li>
+ <li> A word consisting of a single <em>closing brace</em> (<tt>}</tt>)
+decrements <em>blevel</em>, and is replaced with the empty word.
+Quoted closing braces do not have that behaviour. </li>
+ <li> If <tt>execlineb</tt> finds that braces are unmatched (i.e.
+<em>blevel</em> goes below 0 during the parsing, or is not 0 at the end
+of the script), it exits 100 with an error message. </li>
+ <li> <tt>execlineb</tt> automatically quotes
+<a href="el_semicolon.html">blocks</a>. Which means that everytime it
+finds a word, it prepends it with <em>blevel</em> spaces. </li>
+</ul>
+
+<p>
+For proper execution, the sequence of words must follow
+the <a href="grammar.html">execline grammar</a>.
+</p>
+
+<h2> Options for block syntax checking </h2>
+
+<p>
+ External execline commands that read blocks, like
+<a href="foreground.html">foreground</a>, use the <tt>EXECLINE_STRICT</tt>
+environment variable: if it is set to 1, they will print a warning message
+on stderr if they find their blocks not to be properly quoted. If it is set
+to 2, they will also die. If it is set to 0, or unset, they won't complain
+at all.
+</p>
+
+<p>
+ Normally the <tt>EXECLINE_STRICT</tt> environment variable is
+inherited from the caller. You can
+force it unset, set to 1, or set to 2 by giving respectively the
+<tt>-q</tt>, <tt>-w</tt> or <tt>-W</tt> option to <tt>execlineb</tt>.
+</p>
+
+<h2> Options for environment management </h2>
+
+<p>
+ Normally, execline scripts are <em>reentrant</em>: environment variables
+potentially overwritten by <tt>execlineb</tt>, such as <tt>#</tt> or
+<tt>0</tt>, are
+<a href="el_pushenv.html">pushed</a>. This is the standard, safe
+behaviour. Nevertheless, it is rather costly, and may be unneeded for
+small scripts: for those cases, execline comes with two options
+that bypass the environment management. Be warned that the purpose
+of these options is <strong>optimization</strong>, and you should not
+use them if you're not familiar with the way execlineb uses the
+environment to store positional parameters. Alternatively, there's also
+an integrated substitution mechanism that doesn't make use
+of the environment at all.
+</p>
+
+<ul>
+ <li> The <tt>-p</tt> option will bypass the
+<a href="el_pushenv.html">push</a> phase: the current frame of positional
+parameters will be <em>overwritten</em>. The script will <em>not</em> be
+reentrant. </li>
+ <li> The <tt>-P</tt> option will bypass positional parameter handling
+<em>completely</em>: the environment will not be pushed, and positional
+parameters will be ignored. <tt>execlineb -P -c "<em>script</em>"</tt> is
+equivalent to, but more efficient than, <tt>execlineb -c
+"emptyenv -P <em>script</em>"</tt>. You should use the <tt>-P</tt> option
+only in standalone scripts that take no arguments, such as
+<a href="http://skarnet.org/software/s6/">s6</a>'s or 
+<a href="http://smarden.org/runit/">runit</a>'s <em>run scripts</em>. </li>
+ <li> The <tt>-S&nbsp;<em>nmin</em></tt> option <em>will</em> substitute the
+positional parameters - up to at least <em>nmin</em> - but <em>will not</em>
+push nor set environment
+variables. <tt>execlineb -S3 -c "<em>script</em>"</tt> is equivalent to,
+but more efficient than, <tt>execlineb -c "elgetpositionals -P3 emptyenv
+-P <em>script</em>"</tt>. See
+<a href="el_pushenv.html#integrated">the details</a>. </li>
+</ul>
+
+<h2> Current limitations </h2>
+
+<p>
+ <tt>execlineb</tt> builds and executes a unique
+<em>argv</em> with the script: hence scripts are subject to OS-dependent
+limitations such as the kernel buffer size for <em>argv</em> and <em>envp</em>
+ - at least 64 kB on most systems. This means that <tt>execlineb</tt> cannot
+execute arbitrarily large scripts. Be careful with deeply nested scripts too:
+without the <tt>-p</tt>/<tt>-P</tt>/<tt>-S</tt> option, each execlineb
+invocation uses up some space in the environment.
+</p>
+
+</body>
+</html>
diff --git a/doc/exit.html b/doc/exit.html
new file mode 100644
index 0000000..1e94ebd
--- /dev/null
+++ b/doc/exit.html
@@ -0,0 +1,44 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the exit program</title>
+    <meta name="Description" content="execline: the exit program" />
+    <meta name="Keywords" content="execline command exit" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>exit</tt> program </h1>
+
+<p>
+<tt>exit</tt> exits with a given exit code.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     exit [ <em>n</em> ]
+</pre>
+
+<p>
+<tt>exit</tt> exits with the exit code <em>n</em>, or 0 if <em>n</em> is not
+given (in which case it's the same as <tt>true</tt>). If <em>n</em> is not
+a number, <tt>exit</tt> exits 100.
+</p>
+
+
+<h2> Notes </h2>
+
+<p>
+<tt>exit</tt> is a standard shell builtin, with the same function.
+</p>
+
+</body>
+</html>
diff --git a/doc/export.html b/doc/export.html
new file mode 100644
index 0000000..35a7f68
--- /dev/null
+++ b/doc/export.html
@@ -0,0 +1,43 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the export program</title>
+    <meta name="Description" content="execline: the export program" />
+    <meta name="Keywords" content="execline command export environment variable" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>export</tt> program </h1>
+
+<p>
+<tt>export</tt> sets an environment variable to a given value, then
+executes a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     export <em>var</em> <em>value</em> <em>prog...</em>
+</pre>
+
+<p>
+<tt>export</tt> sets the <em>var</em> environment variable to
+the string <em>value</em>, then execs into <em>prog</em> with
+its arguments.
+</p>
+
+<ul>
+ <li> <em>var</em> must be given without a dollar&nbsp;! </li>
+ <li> <em>var</em> must not contain the character <tt>=</tt> . </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/fdblock.html b/doc/fdblock.html
new file mode 100644
index 0000000..5b2bfa8
--- /dev/null
+++ b/doc/fdblock.html
@@ -0,0 +1,55 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the fdblock program</title>
+    <meta name="Description" content="execline: the fdblock program" />
+    <meta name="Keywords" content="execline command fdblock file descriptor blocking" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>fdblock</tt> program </h1>
+
+<p>
+<tt>fdblock</tt> sets or unsets the O_NONBLOCK flag on a given file descriptor
+(which makes reading or writing non-blocking or blocking), then executes
+a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     fdblock [ -n ] <em>fd</em> <em>prog...</em>
+</pre>
+
+<p>
+<tt>fdblock</tt> makes the file descriptor number <em>fd</em> blocking,
+no matter what its previous state was. It then execs into <em>prog</em>
+with its arguments.
+</p>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-n</tt>&nbsp;: non-blocking. Sets <em>fd</em> to non-blocking
+mode instead of blocking mode. If used on stdin (0) or stdout (1), this
+option will make a lot of command-line programs behave improperly, because
+most simple command-line programs only support blocking stdin and stdout.
+Make sure you know what you are doing. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>fdblock</tt> has no portable shell equivalent. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/fdclose.html b/doc/fdclose.html
new file mode 100644
index 0000000..15265ac
--- /dev/null
+++ b/doc/fdclose.html
@@ -0,0 +1,44 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the fdclose program</title>
+    <meta name="Description" content="execline: the fdclose program" />
+    <meta name="Keywords" content="execline command fdclose file descriptor close" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>fdclose</tt> program </h1>
+
+<p>
+<tt>fdclose</tt> closes a given file descriptor, then
+executes a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     fdclose <em>fd</em> <em>prog...</em>
+</pre>
+
+<p>
+<tt>fdclose</tt> closes the file descriptor number <em>fd</em>, then
+execs into <em>prog</em> with its arguments.
+</p>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>fdclose <em>n</em> prog...</tt> is roughly equivalent to
+<tt>sh -c 'exec prog... <em>n</em>&lt;&amp;-'</tt></li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/fdmove.html b/doc/fdmove.html
new file mode 100644
index 0000000..8498408
--- /dev/null
+++ b/doc/fdmove.html
@@ -0,0 +1,55 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the fdmove program</title>
+    <meta name="Description" content="execline: the fdmove program" />
+    <meta name="Keywords" content="execline command fdmove file descriptor dup dup2" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>fdmove</tt> program </h1>
+
+<p>
+<tt>fdmove</tt> moves or copies a given file descriptor, then
+executes a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     fdmove [ -c ] <em>fdto</em> <em>fdfrom</em> <em>prog...</em>
+</pre>
+
+<p>
+<tt>fdmove</tt> moves the file descriptor number <em>fdfrom</em>,
+to number <em>fdto</em>, then execs into <em>prog</em> with its arguments.
+If <em>fdto</em> is open, <tt>fdmove</tt> closes it before moving
+<em>fdfrom</em> to it.
+</p>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-c</tt>&nbsp;: duplicate <em>fdfrom</em> to <em>fdto</em>
+instead of moving it; do not close <em>fdfrom</em>. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>fdmove -c <em>a</em> <em>b</em> prog...</tt> is roughly equivalent to
+<tt>sh -c 'exec prog... <em>a</em>&gt;&amp;<em>b</em>'</tt></li>
+ <li> <tt>fdmove <em>a</em> <em>b</em> prog...</tt> is roughly equivalent to
+<tt>sh -c 'exec prog... <em>a</em>&gt;&amp;<em>b</em> <em>b</em>&lt;&amp;-'</tt></li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/fdreserve.html b/doc/fdreserve.html
new file mode 100644
index 0000000..ef047dc
--- /dev/null
+++ b/doc/fdreserve.html
@@ -0,0 +1,92 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the fdreserve program</title>
+    <meta name="Description" content="execline: the fdreserve program" />
+    <meta name="Keywords" content="execline command fdreserve file descriptor" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>fdreserve</tt> program </h1>
+
+<p>
+<tt>fdreserve</tt> updates the environment with file descriptors that
+are guaranteed safe to use, then executes a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     fdreserve <em>n</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>fdreserve</tt> tries to reserve <em>n</em> file descriptors. </li>
+ <li> <tt>fdreserve</tt> sets the <tt>FD0</tt>, <tt>FD1</tt>, ...,
+<tt>FD<em>n-1</em></tt> environment variables: each FD<em>i</em> contains a
+valid file descriptor, that can be safely opened. </li>
+ <li> <tt>fdreserve</tt> then execs into <em>prog</em> with its arguments.
+</ul>
+
+<h2> Common use </h2>
+
+<p>
+<tt>fdreserve</tt> can be used when you do not want to hardcode file
+descriptors in your scripts. For instance, to create a pipe, you could
+use:
+</p>
+
+<pre>
+ #!/command/execlineb
+ fdreserve 2
+ multisubstitute
+ {
+   importas fdr FD0
+   importas fdw FD1
+  }
+ piperw $fdr $fdw
+ <em>prog...</em>
+</pre>
+
+<p>
+ Warning: <tt>fdreserve</tt> does not allocate descriptors, it merely returns
+descriptors that are free at the time it is run. A program like
+</p>
+
+<pre>
+ #!/command/execlineb
+ fdreserve 3
+ multisubstitute
+ {
+   importas fdr FD0
+   importas fdw FD1
+ }
+ piperw $fdr $fdw
+ fdreserve 1
+ multisubstitute
+ {
+   importas oldfd FD2
+   importas newfd FD0
+ }
+ <em>prog...</em>
+</pre>
+
+<p>
+may fail, because <em>oldfd</em> and <em>newfd</em> may be the same.
+To avoid that, you should make sure that all descriptors returned by
+<tt>fdreserve</tt> are actually allocated before calling <tt>fdreserve</tt>
+again.
+(Thanks to <a href="http://code.dogmap.org/">Paul Jarc</a> for having
+spotted that case.)
+</p>
+
+</body>
+</html>
diff --git a/doc/forbacktickx.html b/doc/forbacktickx.html
new file mode 100644
index 0000000..9e09c81
--- /dev/null
+++ b/doc/forbacktickx.html
@@ -0,0 +1,77 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the forbacktickx command</title>
+  <meta name="Description" content="execline: the forbacktickx command" />
+  <meta name="Keywords" content="execline command forbacktickx" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>forbacktickx</tt> program </h1>
+
+<p>
+<tt>forbacktickx</tt> runs a program and uses its output as loop elements to
+run another program.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="execlineb.html">execlineb</a> script:
+</p>
+
+<pre>
+     forbacktickx [ -p | -x breakcode ] [ -n ] [ -C | -c ] [ -0 | -d <em>delim</em> ] <em>variable</em> { <em>gen...</em> } <em>loop...</em>
+</pre>
+
+<ul>
+ <li> <tt>forbacktickx</tt> reads a
+<a href="el_semicolon.html">block</a>,
+<em>gen...</em>, and unquotes it. </li>
+ <li> It runs <em>gen...</em> as a child process. <em>gen</em>'s
+output must not contain a null character. </li>
+ <li> It reads <em>gen</em>'s output as it needs,
+<a href="el_transform.html#split">splitting</a> it automatically. </li>
+ <li> For every argument <em>x</em> in the split output,
+<tt>forbacktickx</tt> runs <em>loop...</em> as a child process, with
+<em>variable</em>=<em>x</em> added to its environment. </li>
+ <li><tt>forbacktickx</tt> then exits 0.
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-p</tt>&nbsp;: parallel mode. Do not wait for a <em>loop...</em>
+instance to finish before spawning the next one. <em>forbacktickx</em> will
+still wait for all instances of <em>loop</em> to terminate before
+exiting, though. </li>
+ <li> <tt>-0</tt>&nbsp;: accept null characters from <em>gen</em>'s output,
+using them as delimiters. If this option and a <tt>-d</tt> option are
+used simultaneously, the rightmost one wins. </li>
+ <li> <tt>-x</tt>&nbsp;<em>breakcodes</em>&nbsp;: <em>breakcodes</em> must
+be a comma-separated list of exit codes. If at some point <em>loop...</em>
+exits with a code listed in <em>breakcodes</em>, forbacktickx will not keep
+looping, but will exit immediately with the same exit code. This doesn't apply
+if the <tt>-p</tt> option has been given. </li>
+ <li> Other options are used to <a href="el_transform.html">control
+the substitution mechanism</a> for every <em>x</em>. Of course, you can't
+split <em>x</em>. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> You can start <em>loop...</em> with "import <em>variable</em> unexport <em>variable</em>"
+to perform variable substitution.
+</ul>
+ 
+</body>
+</html>
diff --git a/doc/foreground.html b/doc/foreground.html
new file mode 100644
index 0000000..a2a465f
--- /dev/null
+++ b/doc/foreground.html
@@ -0,0 +1,57 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the foreground command</title>
+  <meta name="Description" content="execline: the foreground command" />
+  <meta name="Keywords" content="execline command foreground" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>foreground</tt> program </h1>
+
+<p>
+<tt>foreground</tt> executes a sequence of commands.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="execlineb.html">execlineb</a> script:
+</p>
+
+<pre>
+     foreground { <em>prog1...</em> } <em>prog2...</em>
+</pre>
+
+<ul>
+ <li> <tt>foreground</tt> reads <em>prog1</em> in a
+<a href="el_semicolon.html">block</a>. It forks and
+executes it, then waits for it to complete. </li>
+ <li> <tt>foreground</tt> sets the <tt>?</tt> environment
+variable to the exit code of <em>prog1</em>. If <em>prog1...</em>
+did not exit normally, the <tt>?</tt> value is 111. </li>
+ <li> <tt>foreground</tt> then execs into <em>prog2...</em>. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>foreground</tt> is the basic sequence operator: it takes two
+commands and executes them one by one. execline scripts require it to
+wrap external commands that exit instead of natively supporting the
+"perform some action, then execute some other program" model. </li>
+ <li> <tt>foreground <em>prog1...</em> "" <em>prog2...</em></tt> is
+equivalent to <tt>sh -c '<em>prog1...</em> ; exec <em>prog2...</em>'</tt>.
+ </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/forx.html b/doc/forx.html
new file mode 100644
index 0000000..86729b8
--- /dev/null
+++ b/doc/forx.html
@@ -0,0 +1,66 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the forx command</title>
+  <meta name="Description" content="execline: the forx command" />
+  <meta name="Keywords" content="execline command forx" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>forx</tt> program </h1>
+
+<p>
+<tt>forx</tt> runs a loop.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="execlineb.html">execlineb</a> script:
+</p>
+
+<pre>
+     forx [ -p | -x <em>breakcodes</em> ] <em>variable</em> { <em>args...</em> } <em>loop...</em>
+</pre>
+
+<ul>
+ <li> <tt>forx</tt> reads a
+<a href="el_semicolon.html">block</a> and unquotes it.
+That block contains a list of <em>args</em>. </li>
+ <li> For each argument <em>x</em> in <em>args...</em>,
+<tt>forx</tt> runs <em>loop</em> as a child process, with
+<em>variable</em>=<em>x</em> added to its environment. </li>
+ <li> <tt>forx</tt> then exits 0. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-p</tt>&nbsp;: run in parallel. Do not wait for an instance of
+<em>loop...</em> to exit before spawning the next one. <tt>forx</tt>
+will still wait for all instances of <em>loop</em> to terminate before
+exiting, though. </li>
+ <li> <tt>-x</tt>&nbsp;<em>breakcodes</em>&nbsp;: <em>breakcodes</em> must
+be a comma-separated list of exit codes. If the <tt>-p</tt> flag
+hasn't been given and <em>loop</em> exits with one of the codes in <em>breakcodes</em>,
+forx will not run the following instances of the loop, but exit immediately with the
+same exit code. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> You can start <em>loop</em> with "import <em>variable</em> unexport <em>variable</em>"
+if you want variable substitution. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/getpid.html b/doc/getpid.html
new file mode 100644
index 0000000..fb60c7c
--- /dev/null
+++ b/doc/getpid.html
@@ -0,0 +1,44 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the getpid program</title>
+    <meta name="Description" content="execline: the getpid program" />
+    <meta name="Keywords" content="execline command getpid process id variable" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>getpid</tt> program </h1>
+
+<p>
+<tt>getpid</tt> stores its process ID in a given environment variable,
+then executes a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     getpid <em>var</em> <em>prog...</em>
+</pre>
+
+<p>
+<tt>getpid</tt> stores its PID in the <em>var</em> variable, then
+execs into <em>prog</em> with its arguments.
+</p>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <em>var</em> must be given without a dollar&nbsp;! </li>
+ <li> <em>var</em> must not contain <tt>=</tt>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/grammar.html b/doc/grammar.html
new file mode 100644
index 0000000..6c26dbd
--- /dev/null
+++ b/doc/grammar.html
@@ -0,0 +1,160 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: language design and grammar</title>
+    <meta name="Description" content="execline: language design and grammar" />
+    <meta name="Keywords" content="execline language design grammar script shell" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>execline</tt> language design and grammar </h1>
+
+<a name="principles" />
+<h2> <tt>execline</tt> principles </h2>
+
+<p>
+ Here are some basic Unix facts:
+</p>
+
+<ul>
+ <li> Unix programs are started with the <tt>execve()</tt>
+system call, which takes 3 arguments: the command name (which
+we won't discuss here because it's redundant in most cases),
+the command line <em>argv</em>, which specifies the program name and its
+arguments, and the environment <em>envp</em>. </li>
+ <li> The <em>argv</em> structure makes it easy to read some
+arguments at the beginning of <em>argv</em>, perform some action,
+then <tt>execve()</tt> into the rest of <em>argv</em>. For
+instance, the <tt>nice</tt> command works that way:
+<pre> nice -10 echo blah </pre> will read <tt>nice</tt> and <tt>-10</tt>
+from the argv, change the process' <em>nice</em> value, then exec into
+the command <tt>echo blah</tt>. This is called
+<a href"http://en.wikipedia.org/wiki/Chain_loading">chain loading</a>
+by some people, and <a href="http://www.faqs.org/docs/artu/ch07s02.html">
+Bernstein chaining</a> by others. </li>
+ <li> The purpose of the environment is to preserve some state across
+<tt>execve()</tt> calls. This state is usually small: most programs
+keep their information in the filesystem. </li>
+ <li> A <em>script</em> is basically a text file whose meaning is a
+sequence of actions, i.e. calls to Unix programs, with some control
+over the execution flow. You need a program to interpret your script.
+Traditionally, this program is <tt>/bin/sh</tt>: scripts are written
+in the <em>shell</em> language. </li>
+ <li> The shell reads and interprets the script command after command.
+That means it must preserve a state, and stay in memory while the
+script is running. </li>
+ <li> Standard shells have lots of built-in features and commands, so
+they are big. Spawning (i.e. <tt>fork()</tt>ing then <tt>exec()</tt>ing)
+a shell script takes time, because the shell program itself must be
+initialized. For simple programs like <tt>nice -10 echo blah</tt>,
+a shell is overpowered - we only need a way to make an <em>argv</em>
+from the "<tt>nice -10 echo blah</tt>" string, and <tt>execve()</tt>
+into that <em>argv</em>. </li>
+ <li> Unix systems have a size limit for <em>argv</em>+<em>envp</em>,
+but it is high. POSIX states that this limit must not be inferior to
+4&nbsp;KB - and most simple scripts are smaller than that. Modern systems
+have a much higher limit: for instance, it is 64&nbsp;KB on FreeBSD-4.6,
+and 128&nbsp;KB on Linux. </li>
+</ul>
+
+<p>
+ Knowing that, and wanting lightweight and efficient scripts, I
+wondered: "Why should the interpreter stay in memory while the script
+is executing&nbsp;? Why not parse the script once and for all, put
+it all into one <em>argv</em>, and just execute into that <em>argv</em>,
+relying on external commands (which will be called from within the
+script) to control the execution flow&nbsp;?"
+</p>
+
+<p> <tt>execline</tt> was born. </p>
+
+<ul>
+ <li> <tt>execline</tt> is the first script language to rely
+<em>entirely</em> on chain loading. An execline script is a
+single <em>argv</em>, made of a chain of programs designed to
+perform their action then <tt>exec()</tt> into the next one. </li>
+ <li> The <a href="execlineb.html">execlineb</a> command is a
+<em>launcher</em>: it reads and parses a text file, converting it
+to an <em>argv</em>, then executes into that <em>argv</em>. It
+does nothing more. </li>
+ <li> Straightforward scripts like <tt>nice -10 echo blah</tt>
+will be run just as they are, without the shell overhead.
+Here is what the script could look like:
+<pre>
+#!/command/execlineb -P
+nice -10
+echo blah
+</pre>
+ </li>
+ <li> More complex scripts will include calls to other <tt>execline</tt>
+commands, which are meant to provide some control over the process state
+and execution flow from inside an <em>argv</em>. </li>
+</ul>
+
+<a name="grammar" />
+<h2> Grammar of an execline script </h2>
+
+<p>
+An execline script can be parsed as follows:
+</p>
+
+<pre>
+ &lt;instruction&gt; = &lt;&gt; | external options &lt;arglist&gt; &lt;instruction&gt; | builtin options &lt;arglist&gt; &lt;blocklist&gt; &lt;instruction&gt;
+ &lt;arglist&gt; = &lt;&gt; | arg &lt;arglist&gt;
+ &lt;blocklist&gt; = &lt;&gt; | &lt;block&gt; &lt;blocklist&gt;
+ &lt;block&gt; = { &lt;arglist&gt; } | { &lt;instrlist&gt; }
+ &lt;instrlist&gt; = &lt;&gt; | &lt;instruction&gt; &lt;instrlist&gt;
+</pre>
+
+<p>
+(This grammar is ambivalent, but much simpler to understand than the
+non-ambivalent ones.)
+</p>
+
+<ul>
+ <li> An execline script is valid if it reduces to an
+<em>instruction</em>. </li>
+ <li> The empty <em>instruction</em> is the same as the <tt>true</tt>
+command: when an execline component must exec into the empty
+instruction, it exits 0. </li>
+ <li> Basically, every non-empty <em>instruction</em>, be it
+"<em>builtin</em>" - an execline command - or "<em>external</em>"
+- a program such as <tt>echo</tt> or <tt>cp</tt> - takes a number of
+arguments, the <em>arglist</em>, then executes into a (possibly empty)
+<em>instruction</em>. </li>
+ <li> Some <em>builtin</em>s are special because they also take a
+non-empty <em>blocklist</em> after their <em>arglist</em>. For instance,
+the <a href="foreground.html">foreground</a> command takes an empty
+<em>arglist</em> and one <em>block</em>: <pre>
+ #!/command/execlineb -P
+ foreground { sleep 1 } echo blah
+</pre> is a valid <a href="execlineb.html">execlineb</a> script.
+The <a href="foreground.html">foreground</a> command uses the
+<tt>sleep&nbsp;1</tt> <em>block</em> then execs into the
+remaining <tt>echo&nbsp;blah</tt> <em>instruction</em>. </li>
+</ul>
+
+<a name="features"></a>
+<h2> execline features </h2>
+
+<p>
+ <tt>execline</tt> commands can perform some transformations on
+their <em>argv</em>, to emulate some aspects of a shell. Here are
+descriptions of these features:
+</p>
+
+<ul>
+ <li> <a href="el_semicolon.html">Block management</a> </li>
+ <li> <a href="el_substitute.html">Variable substitution</a> </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/heredoc.html b/doc/heredoc.html
new file mode 100644
index 0000000..f49f880
--- /dev/null
+++ b/doc/heredoc.html
@@ -0,0 +1,56 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the heredoc program</title>
+    <meta name="Description" content="execline: the heredoc program" />
+    <meta name="Keywords" content="execline command heredoc here document" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>heredoc</tt> program </h1>
+
+<p>
+<tt>heredoc</tt> runs a command with a certain string fed to a
+file descriptor.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     heredoc [ -d ] <em>fd</em> <em>string</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>heredoc</tt> execs into <em>prog...</em> with
+<em>string</em> available on the <em>fd</em> file
+descriptor. </li>
+ <li> <em>string</em> must not contain a null character. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-d</tt>&nbsp;: run the process feeding <em>string</em> to <em>fd</em>
+as a grandchild of <tt>heredoc</tt>. This is meant to prevent a zombie
+from hanging around if <em>prog...</em> has read <em>string</em> and fails
+to wait for its children.</li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>heredoc</tt> is meant to be used in place of the shell
+<tt>&lt;&lt;</tt> construct, which includes <em>here-documents</em>
+into scripts. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/homeof.html b/doc/homeof.html
new file mode 100644
index 0000000..31f2a40
--- /dev/null
+++ b/doc/homeof.html
@@ -0,0 +1,37 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the homeof program</title>
+    <meta name="Description" content="execline: the homeof program" />
+    <meta name="Keywords" content="execline command homeof" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>homeof</tt> program </h1>
+
+<p>
+<tt>homeof</tt> prints the home directory of a user.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     homeof <em>user</em>
+</pre>
+
+<p>
+<tt>homeof</tt> finds the name of <em>user</em>'s home directory, writes
+it on stdout, then exits 0. If an error occurs, it prints nothing on
+stdout but exits 111 with an explanatory message on stderr.
+</p>
+
+</body>
+</html>
diff --git a/doc/if.html b/doc/if.html
new file mode 100644
index 0000000..2157a07
--- /dev/null
+++ b/doc/if.html
@@ -0,0 +1,68 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the if command</title>
+  <meta name="Description" content="execline: the if command" />
+  <meta name="Keywords" content="execline command if" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>if</tt> program </h1>
+
+<p>
+<tt>if</tt> performs conditional execution.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="execlineb.html">execlineb</a> script:
+</p>
+
+<pre>
+     if [ -X ] [ -n ] [ -t | -x <em>exitcode</em> ] { <em>prog1...</em> } <em>prog2...</em>
+</pre>
+
+<ul>
+ <li> <tt>if</tt> reads <em>prog1...</em> in a
+<a href="el_semicolon.html">block</a>. It forks and executes it,
+then waits for it to complete. </li>
+ <li> If <em>prog1</em> crashes, <tt>if</tt> exits 1 with a special
+error message. </li>
+ <li> If <em>prog1</em> exits a non-zero status,
+<tt>if</tt> exits 1.</li>
+ <li> Else <tt>if</tt> execs into <em>prog2</em>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-X</tt>&nbsp;: treat a crash of <em>prog1</em> as a non-zero ("false") exit.
+ <li> <tt>-n</tt>&nbsp;: negate the test (exit on true, exec into <em>prog2</em> on false) </li>
+ <li> <tt>-x</tt>&nbsp;<em>exitcode</em>&nbsp;: exit <em>exitcode</em> instead of 1 if the test fails. </li>
+ <li> <tt>-t</tt>&nbsp;: exit 0 instead of 1 if the test fails.
+This is equivalent to <tt>-x 0</tt>. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>if</tt> will exit if <em>prog1...</em> exits false. To use it in
+an execline script that must run <em>prog3...</em> no matter the result of
+the test, use a <tt>foreground</tt> wrapper:
+<pre> foreground { if { <em>prog1...</em> } <em>prog2...</em> } <em>prog3...</em> </pre>
+(in <a href="execlineb.html">execlineb</a> syntax) </li>
+ <li> <tt>if <em>prog1...</em> "" <em>prog2...</em></tt> is
+equivalent to <tt>sh -c '<em>prog1...</em> && exec <em>prog2...</em>'</tt>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/ifelse.html b/doc/ifelse.html
new file mode 100644
index 0000000..2298cf8
--- /dev/null
+++ b/doc/ifelse.html
@@ -0,0 +1,59 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the ifelse command</title>
+  <meta name="Description" content="execline: the ifelse command" />
+  <meta name="Keywords" content="execline command ifelse" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://www.skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>ifelse</tt> program </h1>
+
+<p>
+ <tt>ifelse</tt> performs conditional execution, with two branches.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="execlineb.html">execlineb</a> script:
+</p>
+<pre>
+     ifelse [ -X ] [ -n ] { <em>prog1...</em> } { <em>prog2...</em> } <em>prog3...</em>
+</pre>
+
+<ul>
+ <li> <tt>ifelse</tt> reads <em>prog1...</em> in a
+<a href="el_semicolon.html">block</a>. It forks and executes it,
+then waits for it to complete. </li>
+ <li> If <em>prog1</em> crashes, <tt>ifelse</tt> exits 1 with an error message. </li>
+ <li> If <em>prog1</em> exits with a return code equal to 0,
+<tt>ifelse</tt> execs into <em>prog2</em>. </li>
+ <li> Else <tt>ifelse</tt> execs into <em>prog3</em>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-n</tt>&nbsp;: negate the test. </li>
+ <li> <tt>-X</tt>&nbsp;: do not die if <em>prog1</em> crashes; treat a crash
+as a non-zero ("false") exit. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>ifelse <em>prog1...</em> "" <em>prog2...</em> "" <em>prog3...</em></tt> is
+roughly equivalent to <tt>sh -c '<em>prog1...</em> && exec <em>prog2...</em> || exec <em>prog3...</em>'</tt>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/ifte.html b/doc/ifte.html
new file mode 100644
index 0000000..40b0ec9
--- /dev/null
+++ b/doc/ifte.html
@@ -0,0 +1,67 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the ifte command</title>
+  <meta name="Description" content="execline: the ifte command" />
+  <meta name="Keywords" content="execline command ifte" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>ifte</tt> program </h1>
+
+<p>
+<tt>ifte</tt> performs a conditional alternative.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="execlineb.html">execlineb</a> script:
+</p>
+
+<pre>
+     ifte [ -X ] [ -n ] { <em>progthen...</em> } { <em>progelse...</em> } <em>progif...</em>
+</pre>
+
+<ul>
+ <li> <tt>ifte</tt> reads <em>progthen...</em> and <em>progelse...</em> in two
+consecutive <a href="el_semicolon.html">blocks</a>. </li>
+ <li> <tt>ifte</tt> runs <em>progif...</em> as a child process
+and waits for it to complete. </li>
+ <li> If <em>progif...</em> crashes (i.e. is killed by a signal), <tt>ifte</tt>
+exits 1 with an error message. </li>
+ <li> If <em>progif...</em> exits zero, <tt>ifte</tt> execs into
+<em>progthen...</em>, else it execs into <em>progelse...</em>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-X</tt>&nbsp;: do not exit if <em>progif</em> crashes; instead,
+proceed as if the test had returned false. </li>
+ <li> <tt>-n</tt>&nbsp;: negate the test. <em>progthen...</em> will be run
+iff <em>progif...</em> exits nonzero. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<p>
+ <tt>ifte</tt> is a simpler version of <a href="ifthenelse.html">ifthenelse</a>.
+It performs <em>only</em> conditional execution, not instruction sequence.
+</p>
+
+<p>
+"<tt>ifthenelse { progif } { progthen } { progelse } remainder</tt>" is the
+equivalent of "<tt>foreground { ifte { progthen } { progelse } progif } remainder</tt>".
+</p>
+
+</body>
+</html>
diff --git a/doc/ifthenelse.html b/doc/ifthenelse.html
new file mode 100644
index 0000000..3180412
--- /dev/null
+++ b/doc/ifthenelse.html
@@ -0,0 +1,59 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the ifthenelse command</title>
+  <meta name="Description" content="execline: the ifthenelse command" />
+  <meta name="Keywords" content="execline command ifthenelse" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>ifthenelse</tt> program </h1>
+
+<p>
+<tt>ifthenelse</tt> performs a conditional alternative.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="execlineb.html">execlineb</a> script:
+</p>
+
+<pre>
+     ifthenelse [ -X ] [ -s ] { <em>progif...</em> } { <em>progthen...</em> } { <em>progelse...</em> } <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>ifthenelse</tt> reads
+<em>progif...</em>, <em>progthen...</em> and <em>progelse...</em> in 3
+consecutive <a href="el_semicolon.html">blocks</a>. </li>
+ <li> <tt>ifthenelse</tt> runs <em>progif...</em> as a child process
+and waits for it to complete. </li>
+ <li> If <em>progif...</em> crashes (i.e. is killed by a signal), <tt>ifthenelse</tt>
+exits 1 with an error message. </li>
+ <li> If <em>progif...</em> exits zero, <tt>ifthenelse</tt> runs
+<em>progthen...</em> as a child process, else it runs <em>progelse...</em>. </li>
+ <li> <tt>ifthenelse</tt> waits for its child to complete and puts the exit
+status in the <tt>?</tt> environment variable. It then
+execs into <em>prog...</em>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-X</tt>&nbsp;: if <em>progif</em> crashes, do not exit; proceed
+as if it had returned false. </li>
+ <li> <tt>-s</tt>&nbsp;: magic scoping hack. This option does powerful but
+ugly things, and is left undocumented on purpose. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/import.html b/doc/import.html
new file mode 100644
index 0000000..1c6896c
--- /dev/null
+++ b/doc/import.html
@@ -0,0 +1,37 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the import program</title>
+    <meta name="Description" content="execline: the import program" />
+    <meta name="Keywords" content="execline command import environment variable" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>import</tt> program </h1>
+
+<p>
+<tt>import</tt> replaces an environment variable name with its value,
+then executes another program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     import [ -i | -D <em>default</em> ] [ -s ] [ -C | -c ] [ -n ] [ -d <em>delim</em> ] <em>envvar</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>import</tt> behaves exactly as
+<tt><a href="importas.html">importas</a> <em>envvar</em> <em>envvar</em></t>.
+</ul>
+
+</body>
+</html>
diff --git a/doc/importas.html b/doc/importas.html
new file mode 100644
index 0000000..a9c6f15
--- /dev/null
+++ b/doc/importas.html
@@ -0,0 +1,57 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the importas program</title>
+    <meta name="Description" content="execline: the importas program" />
+    <meta name="Keywords" content="execline command importas import environment variable" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>importas</tt> program </h1>
+
+<p>
+<tt>importas</tt> replaces a literal with the value of an
+environment variable, then executes another program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     importas [ -i | -D default ] [ -s ] [ -C | -c ] [ -n ] [ -d <em>delim</em> ] <em>variable</em> <em>envvar</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>importas</tt> fetches the value of <em>envvar</em> in the
+environment. If neither the <em>-D</em> nor the <em>-i</em> option is given,
+and <em>envvar</em> is undefined, the <strong>null word</strong> is returned. </li>
+ <li> <tt>importas</tt> then performs
+<a href="el_substitute.html">variable substitution</a> on <em>prog...</em>,
+with <em>variable</em> as key and that string as value.
+ <li><tt>importas</tt> then execs into the modified <em>prog...</em>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-D</tt>&nbsp;<em>default</em>&nbsp;: If <em>envvar</em> is
+undefined, and this option is not given, substitute zero word for
+<em>variable</em> instead of the empty word; and if it is given,
+substitute <em>default</em> instead. To substitute the empty word,
+use <tt>-D&nbsp;""</tt>. </li>
+ <li> <tt>-i</tt>&nbsp;: Insist. If <em>envvar</em> is undefined,
+<tt>importas</tt> will not do anything; instead, it will exit 100 with an
+error message. This has precedence over any <tt>-D</tt> option. </li>
+ <li> Other options are used to <a href="el_transform.html">control
+the substitution mechanism</a>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/index.html b/doc/index.html
new file mode 100644
index 0000000..682e935
--- /dev/null
+++ b/doc/index.html
@@ -0,0 +1,213 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: a small scripting language</title>
+    <meta name="Description" content="execline: a small scripting language" />
+    <meta name="Keywords" content="execline script scripting language shell embedded chain loading bernstein chaining unix" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> execline </h1>
+
+<h2> What is it&nbsp;? </h2>
+
+<p>
+ execline is a (non-interactive) scripting language, like <tt>sh</tt>&nbsp;;
+but its syntax is quite different from a traditional shell syntax.
+The <tt>execlineb</tt> program is meant to be used as an interpreter for a
+text file; the other commands are essentially useful inside an
+<tt>execlineb</tt> script.
+</p>
+
+<p>
+ execline is as powerful as a shell: it features
+<a href="loopwhilex.html">conditional loops</a>,
+<a href="elgetopt.html">getopt-style option handling</a>,
+<a href="elglob.html">filename globbing</a>, and more.
+ Meanwhile, its syntax is far more logic and predictable than the
+shell's syntax, and has no security issues.
+</p>
+
+<ul>
+<li> <a href="grammar.html">The execline design and grammar</a></li>
+<li> <a href="dieshdiedie.html">Why not just use <tt>/bin/sh</tt>&nbsp;?</a></li>
+</ul>
+
+<hr />
+
+<h2> Installation </h2>
+
+<h3> Requirements </h3>
+
+<ul>
+ <li> A POSIX-compliant system with a standard C development environment </li>
+ <li> GNU make, version 3.81 or later </li>
+ <li> <a href="http://skarnet.org/software/skalibs/">skalibs</a> version
+2.0.0.0 or later </li>
+</ul>
+
+<h3> Licensing </h3>
+
+<p>
+ execline is free software. It is available under the
+<a href="http://opensource.org/licenses/ISC">ISC license</a>.
+</p>
+
+<h3> Download </h3>
+
+<ul>
+ <li> The current execline version is <a href="execline-2.0.0.0.tar.gz">2.0.0.0</a>. </li>
+</ul>
+
+<h3> Compilation </h3>
+
+<ul>
+ <li> See the enclosed INSTALL file for installation details. </li>
+</ul>
+
+<h3> Upgrade notes </h3>
+
+<ul>
+ <li> <a href="upgrade.html">This page</a> lists the differences to be aware of between
+the previous versions of execline and the current one. </li>
+</ul>
+
+<hr />
+
+<h2> Special note </h2>
+
+<p>
+ Before version 2.0.0.0, execline used the slashpackage convention by default.
+ This is not the case anymore; nevertheless, the examples in this documentation
+still use <tt>#!/command/execlineb</tt> as their shebang line, and assume that
+the execline binaries are available in <tt>/command</tt>. Adapt them according
+to your installation: the shebang lines for your system might be something like
+<tt>#!/bin/execlineb</tt>, or <tt>#!/usr/bin/execlineb</tt>, or
+<tt>#!/usr/local/bin/execlineb</tt>, or something else entirely.
+</p>
+
+<hr />
+
+<h2> Reference </h2>
+<h3> Commands </h3>
+
+<p>
+ All these commands exit 111 if they encounter a temporary error, and
+100 if they encounter a permanent error - such as a misuse.
+</p>
+<p>
+ (Script parser / launcher)
+</p>
+<ul>
+<li><a href="execlineb.html">The <tt>execlineb</tt> program</a></li>
+</ul>
+<p>
+ (Process state control)
+</p>
+<ul>
+<li><a href="cd.html">The <tt>cd</tt> program</a></li>
+<li><a href="umask.html">The <tt>umask</tt> program</a></li>
+<li><a href="emptyenv.html">The <tt>emptyenv</tt> program</a></li>
+<li><a href="export.html">The <tt>export</tt> program</a></li>
+<li><a href="unexport.html">The <tt>unexport</tt> program</a></li>
+<li><a href="fdclose.html">The <tt>fdclose</tt> program</a></li>
+<li><a href="fdblock.html">The <tt>fdblock</tt> program</a></li>
+<li><a href="fdmove.html">The <tt>fdmove</tt> program</a></li>
+<li><a href="fdreserve.html">The <tt>fdreserve</tt> program</a></li>
+<li><a href="redirfd.html">The <tt>redirfd</tt> program</a></li>
+<li><a href="piperw.html">The <tt>piperw</tt> program</a></li>
+<li><a href="heredoc.html">The <tt>heredoc</tt> program</a></li>
+<li><a href="pipeline.html">The <tt>pipeline</tt> program</a></li>
+<li><a href="wait.html">The <tt>wait</tt> program</a></li>
+<li><a href="getpid.html">The <tt>getpid</tt> program</a></li>
+<li><a href="exec.html">The <tt>exec</tt> program</a></li>
+<li><a href="tryexec.html">The <tt>tryexec</tt> program</a></li>
+<li><a href="exit.html">The <tt>exit</tt> program</a></li>
+</ul>
+<p>
+ (<a href="el_semicolon.html">Basic block management</a>)
+</p>
+<ul>
+<li><a href="foreground.html">The <tt>foreground</tt> program</a></li>
+<li><a href="background.html">The <tt>background</tt> program</a></li>
+<li><a href="if.html">The <tt>if</tt> program</a></li>
+<li><a href="ifelse.html">The <tt>ifelse</tt> program</a></li>
+<li><a href="ifte.html">The <tt>ifte</tt> program</a></li>
+<li><a href="ifthenelse.html">The <tt>ifthenelse</tt> program</a></li>
+<li><a href="backtick.html">The <tt>backtick</tt> program</a></li>
+<li><a href="runblock.html">The <tt>runblock</tt> program</a></li>
+</ul>
+<p>
+ (<a href="el_substitute.html">Variable management</a>)
+</p>
+<ul>
+<li><a href="define.html">The <tt>define</tt> program</a></li>
+<li><a href="importas.html">The <tt>importas</tt> program</a></li>
+<li><a href="import.html">The <tt>import</tt> program</a></li>
+<li><a href="elglob.html">The <tt>elglob</tt> program</a></li>
+<li><a href="elgetpositionals.html">The <tt>elgetpositionals</tt> program</a></li>
+<li><a href="multidefine.html">The <tt>multidefine</tt> program</a></li>
+<li><a href="multisubstitute.html">The <tt>multisubstitute</tt> program</a></li>
+</ul>
+<p>
+ (Loops)
+</p>
+<ul>
+<li><a href="forx.html">The <tt>forx</tt> program</a></li>
+<li><a href="forbacktickx.html">The <tt>forbacktickx</tt> program</a></li>
+<li><a href="loopwhilex.html">The <tt>loopwhilex</tt> program</a></li>
+</ul>
+<p>
+ (Positional parameters and options management)
+</p>
+<ul>
+<li><a href="elgetopt.html">The <tt>elgetopt</tt> program</a></li>
+<li><a href="shift.html">The <tt>shift</tt> program</a></li>
+<li><a href="dollarat.html">The <tt>dollarat</tt> program</a></li>
+</ul>
+<p>
+ (Miscellaneous)
+</p>
+<ul>
+<li><a href="homeof.html">The <tt>homeof</tt> program</a></li>
+</ul>
+
+<h3> Provided scripts: example <tt>.profile</tt> replacement </h3>
+
+<ul>
+<li><a href="execline-shell.html">The <tt>execline-shell</tt> script</a></li>
+<li><a href="execline-startup.html">The <tt>execline-startup</tt> script</a></li>
+</ul>
+
+<h3> Fun stuff </h3>
+
+<ul>
+<li>An execline <a href="quine-jriou.txt">quine</a>. This was quinely provided by
+<a href="http://jriou.org/">Jo&euml;l Riou</a>. The only
+external command used is <tt>echo</tt>. </li>
+<li> Another <a href="quine-prj.txt">quine</a>, provided by
+<a href="http://code.dogmap.org/">Paul Jarc</a>. It is much shorter, but
+uses the external commands <tt>echo</tt> and <tt>env</tt>. Later, Paul rewrote
+it <a href="quine-prj-2.txt">using only <tt>echo</tt></a>, then
+<a href="quine-prj-3.txt">using only <tt>echo</tt> and the environment</a>. </li>
+<li> Another <a href="quine-dam.txt">quine</a>, provided by
+<a href="http://www.madore.org/~david/">David Madore</a>. It uses the
+external command <tt>printf</tt>. It is longer, but quite stylish. </li>
+</ul>
+
+<h2> Related resources </h2>
+
+<ul>
+ <li> <tt>execline</tt> is discussed on the
+<a href="http://skarnet.org/lists.html#skaware">skaware</a> mailing-list. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/loopwhilex.html b/doc/loopwhilex.html
new file mode 100644
index 0000000..9def60f
--- /dev/null
+++ b/doc/loopwhilex.html
@@ -0,0 +1,60 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the loopwhilex command</title>
+  <meta name="Description" content="execline: the loopwhilex command" />
+  <meta name="Keywords" content="execline command loopwhilex" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>loopwhilex</tt> program </h1>
+
+<p>
+<tt>loopwhilex</tt> performs a conditional loop.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     loopwhilex [ -n ] [ -x <em>exitcodes</em> ] <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>loopwhilex</tt> runs <em>prog...</em> as a child process and
+waits for it to complete. </li>
+ <li> As long as <em>prog</em> exits zero, <tt>loopwhile</tt> runs it again. </li>
+ <li> <tt>loopwhilex</tt> then exits 0. If <em>prog</em> was killed by a signal,
+<tt>loopwhilex</tt> exits that signal's number instead. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-x</tt>&nbsp;<em>exitcodes</em>&nbsp;: <em>exitcodes</em> must be a comma-separated
+list of valid exit codes. If this option is given, <tt>loopwhilex</tt> will exit if <em>prog...</em>'s
+exit code is listed in <em>breakcodes</em>. </li>
+ <li> <tt>-n</tt>&nbsp;: negate the test: run <em>prog...</em> as long as it exits non-zero
+(or exits a code that is <em>not</em> listed in <em>breakcodes</em>). </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>loopwhilex <em>prog</em>...</tt> is equivalent to <tt>loopwhilex -n -x 0 <em>prog...</em></tt>. </li>
+ <li> Be careful: execline <strong>maintains no state</strong>, in particular it
+uses <strong>no real variables</strong>, and environment will
+be of no use here since every instance of <em>prog...</em> runs as a separate
+child process. To avoid being stuck in an infinite loop, <em>prog...</em>
+should modify some external state - for instance, the filesystem. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/multidefine.html b/doc/multidefine.html
new file mode 100644
index 0000000..03f6b22
--- /dev/null
+++ b/doc/multidefine.html
@@ -0,0 +1,69 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the multidefine command</title>
+  <meta name="Description" content="execline: the multidefine command" />
+  <meta name="Keywords" content="execline command multidefine" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>multidefine</tt> program </h1>
+
+<p>
+<tt>multidefine</tt> splits a value and defines several variables at once,
+then executes another program.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="execlineb.html">execlineb</a> script:
+</p>
+
+<pre>
+     multidefine [ -0 ] [ -r ] [ -C | -c ] [ -n ] [ -d <em>delim</em> ] <em>value</em> { <em>variables...</em> } <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>multidefine</tt> reads a <a href="el_semicolon.html">block</a>
+containing a list of variables. </li>
+ <li> <tt>multidefine</tt> <a href="el_transform.html">splits</a>
+<em>value</em>, and performs other operations depending on the given
+options. </li>
+ <li> <tt>multidefine</tt> performs
+<a href="el_substitute.html">parallel substitution</a> on
+<em>prog...</em>, using all of the <em>variables</em> in the block as keys.
+The first word in the split <em>value</em> is assigned to the first
+<em>variable</em>, the second word is assigned to the second <em>variable</em>,
+and so on. Every <em>variable</em> is substituted with exactly one word. </li>
+ <li> If a <em>variable</em> is the empty word, then the word in the split
+<em>value</em> corresponding to its position is not substituted. So you can
+use empty words to pad the list of variables and only perform substition
+on the relevant fields. </li>
+ <li> <tt>multidefine</tt> then execs into the modified <em>prog...</em>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-0</tt>&nbsp;: if there are more <em>variables</em> in the block than
+there are words in the split <em>value</em>, the excess variables
+will be replaced with zero word. Without this option, the excess variables are
+replaced with the empty word. </li>
+ <li> <tt>-r</tt>&nbsp;: behave similarly to the "read" shell command.
+If there are more words in the split <em>value</em> than there are
+<em>variables</em> in the block, the last variable will be replaced with all
+the remaining words (and will be split). Without this option, the last variable
+is replaced with a single word, and the excess words are lost. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/multisubstitute.html b/doc/multisubstitute.html
new file mode 100644
index 0000000..6400187
--- /dev/null
+++ b/doc/multisubstitute.html
@@ -0,0 +1,121 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the multisubstitute command</title>
+  <meta name="Description" content="execline: the multisubstitute command" />
+  <meta name="Keywords" content="execline command multisubstitute" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>multisubstitute</tt> program </h1>
+
+<p>
+<tt>multisubstitute</tt> performs several substitutions at once in
+its <em>argv</em>, then executes another program.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="execlineb.html">execlineb</a> script:
+</p>
+
+<pre>
+     multisubstitute
+     {
+       [ <a href="define.html">define</a> [ -n ] [ -s ] [ -C | -c ] [ -d <em>delim</em> ] <em>variable</em> <em>value</em> ]
+       [ <a href="importas.html">importas</a> [ -i | -D <em>default</em> ] [ -n ] [ -s ] [ -C | -c ] [ -d <em>delim</em> ] <em>variable</em> <em>envvar</em> ]
+       [ <a href="import.html">import</a> [ -i | -D <em>default</em> ] [ -n ] [ -s ] [ -C | -c ] [ -d <em>delim</em> ] <em>envvar</em> ]
+       [ <a href="elglob.html">elglob</a> [ -v ] [ -w ] [ -s ] [ -m ] [ -e ] [ -0 ] <em>variable</em> <em>pattern</em> ]
+       [ <a href="elgetpositionals.html">elgetpositionals</a> [ -P <em>sharp</em> ] ]
+       [ <a href="multidefine.html">multidefine</a> <em>value</em> { <em>variable...</em> } ]
+       <em>...</em>
+     }
+     <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>multisubstitute</tt> reads a <a href="el_semicolon.html">block</a>
+containing a series of substitution commands. It performs all
+those <a href="el_substitute.html">substitutions</a> on
+<em>prog...</em> in parallel. Check the relevant documentation page
+to learn about the syntax of each substitution command. </li>
+ <li> <tt>multisubstitute</tt> then execs into the modified <em>prog...</em>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> If a <tt>backtick</tt> directive was given with the <tt>-i</tt> option,
+and <em>command</em> crashes or exits nonzero, <tt>multisubstitute</tt> will
+also exit with the same exit code. </li>
+ <li> If an <tt>import</tt> or <tt>importas</tt> directive was given with the
+<tt>-i</tt> option, and the looked up variable is undefined,
+<tt>multisubstitute</tt> will exit 100. </li>
+</ul>
+
+<h2> Rationale </h2>
+
+<h3> Security </h3>
+
+<p>
+ <tt>multisubstitute</tt> can be used to avoid unwanted
+<em>serial substitutions</em>. Consider the following script:
+</p>
+
+<pre>
+ #!/command/execlineb
+ export A wrong
+ define B ${A}
+ import A
+ echo ${B}
+</pre>
+
+<p>
+ Running it will print <tt>wrong</tt>, because <tt>A</tt> is substituted
+<em>after</em> B. On the contrary, the following script:
+</p>
+
+<pre>
+ #!/command/execlineb
+ export A wrong
+ multisubstitute
+ {
+   define B ${A}
+   import A
+ }
+ echo ${B}
+</pre>
+
+<p>
+ will print <tt>${A}</tt>, because A and B are substituted at the same
+time. Serial substitution may be what you want - but when in doubt,
+always perform parallel substitution.
+</p>
+
+<h3> Efficiency </h3>
+
+<p>
+<a href="el_substitute.html">Substitution</a> is a costly mechanism:
+the whole <em>argv</em> is read three times and rewritten twice.
+Serial substitution multiplies the cost by the number of
+substitutions, whereas parallel substitution pays the price only once.
+</p>
+
+<h2> Credits </h2>
+
+<p>
+<a href="http://code.dogmap.org/">Paul Jarc</a> first originated the
+idea of the <tt>multisubstitute</tt> command and a possible syntax.
+</p>
+
+</body>
+</html>
diff --git a/doc/pipeline.html b/doc/pipeline.html
new file mode 100644
index 0000000..4f6677f
--- /dev/null
+++ b/doc/pipeline.html
@@ -0,0 +1,67 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the pipeline command</title>
+  <meta name="Description" content="execline: the pipeline command" />
+  <meta name="Keywords" content="execline command pipeline" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>pipeline</tt> program </h1>
+
+<p>
+<tt>pipeline</tt> runs two commands with a pipe between them.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="execlineb.html">execlineb</a> script:
+</p>
+
+<pre>
+     pipeline [ -d ] [ -r | -w ] { <em>prog1...</em> } <em>prog2...</em>
+</pre>
+
+<ul>
+ <li> <tt>pipeline</tt> reads <em>prog1...</em> in a
+<a href="el_semicolon.html">block</a> and unquotes it. </li>
+ <li> It runs <em>prog1...</em> as a child process and execs into
+<em>prog2...</em>, with a pipe between <em>prog1</em>'s stdout and
+<em>prog2</em>'s stdin. </li>
+ <li> <em>prog1</em>'s pid is available in <em>prog2</em> as the !
+environment variable. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-d</tt>&nbsp;: run <em>prog1...</em>
+as a grandchild of <tt>pipeline</tt>. This is meant to prevent a zombie
+from hanging around if <em>prog2...</em> fails to wait for its children.</li>
+ <li> <tt>-r</tt>&nbsp;: make <em>prog1...</em> the writer and
+<em>prog2...</em> the reader. This is the default. </li>
+ <li> <tt>-w</tt>&nbsp;: make <em>prog1...</em> the reader and
+<em>prog2...</em> the writer. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> You can easily create a chain of pipes: <tt>pipeline a "" pipeline b "" c</tt>
+is roughly equivalent to
+<tt>sh -c 'exec a | b | c'</tt>, except that shells usually run <tt>c</tt>
+as a child process like <tt>a</tt> and <tt>b</tt>, and <tt>exec</tt> has no
+effect. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/piperw.html b/doc/piperw.html
new file mode 100644
index 0000000..64b9477
--- /dev/null
+++ b/doc/piperw.html
@@ -0,0 +1,38 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the piperw command</title>
+  <meta name="Description" content="execline: the piperw command" />
+  <meta name="Keywords" content="execline command piperw" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>piperw</tt> program </h1>
+
+<p>
+<tt>piperw</tt> creates a pipe (an anonymous one), then
+executes a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     piperw <em>fdr</em> <em>fdw</em> <em>prog...</em>
+</pre>
+
+<p>
+<tt>piperw</tt> creates a pipe with descriptor <em>fdw</em> as the
+writing end and descriptor <em>fdr</em> as the reading end.
+It then execs into <em>prog</em> with its arguments.
+</p>
+
+</body>
+</html>
diff --git a/doc/quine-dam.txt b/doc/quine-dam.txt
new file mode 100644
index 0000000..c69a52c
--- /dev/null
+++ b/doc/quine-dam.txt
@@ -0,0 +1,110 @@
+#! /command/execlineb -P
+# Public Domain.
+# See comments below.
+# (Search for "HERE".)
+#
+define -sCd "\n" lns "
+${p} ${bubble} is the end of the quine's data.
+${p} They represent the following code, with various quotations:
+${p} ${b} (backslash) is represented as ${d}${ob}b${cb}
+${p} ${q} (double quote) is represented as ${d}${ob}q${cb}
+${p} ${p} (sharp/pound/shibboleth/whatever) is represented as ${d}${ob}p${cb}
+${p} ${ob} (open brace) is represented as ${d}${ob}ob${cb}
+${p} ${cb} (closed brace) is represented as ${d}${ob}cb${cb}
+${p} ${d} (dollar) is represented as ${d}${ob}d${cb}
+${p} ${bubble} (the magic word) is represented as ${d}${ob}bubble${cb}
+${p} (The point of the magic word is to allow the reader
+${p} to conveniently skip over the large data section.)
+${p}
+${p} Now we have the quine's code!
+${p}
+${p} First, print the lines that come before the data.
+foreground ${ob} printf %s ${b}${p}${b}!${q} ${q} ${cb}
+foreground ${ob} printf %s${b}${b}n ${q}/command/execlineb -P${q} ${cb}
+foreground ${ob} printf %s${b}${b}n ${b}${p}${q} Public Domain.${q} ${cb}
+foreground ${ob} printf %s${b}${b}n ${b}${p}${q} See comments below.${q} ${cb}
+foreground ${ob} printf %s ${b}${p}${q} (Search for ${q} ${cb}
+foreground ${ob} printf %s${b}${b}n ${b}${q}${bubble}${b}${q}.) ${cb}
+foreground ${ob} printf %s${b}${b}n ${b}${p} ${cb}
+foreground ${ob} printf %s ${q}define -sCd ${b}${q}${b}${b}n${b}${q} lns ${b}${q}${q} ${cb}
+${p} Next, print the data themselves, as data.
+for lin ${ob} ${d}${ob}lns${cb} ${cb} ${ob}
+multisubstitute ${ob}
+define b ${d}${ob}b${cb}
+define q ${d}${ob}q${cb}
+define p ${d}${ob}p${cb}
+define ob ${d}${ob}ob${cb}
+define cb ${d}${ob}cb${cb}
+define d ${d}${ob}d${cb}
+define bubble ${d}${ob}bubble${cb}
+define intron ${d}${ob}intron${cb}
+${cb} printf ${b}${b}n%s ${d}${ob}lin${cb} ${cb}
+foreground ${ob} printf %s${b}${b}n ${b}${q} ${cb}
+${p} Finally, use the data to print the code!
+for lin ${ob} ${d}${ob}lns${cb} ${cb} ${ob}
+multisubstitute ${ob}
+define b ${b}${b}
+define q ${b}${q}
+define p ${b}${p}
+define ob ${b}${ob}
+define cb ${b}${cb}
+define d ${d}
+define bubble ${bubble}
+define intron ${q}${intron}${q}
+${cb} printf %s${b}${b}n ${d}${ob}lin${cb} ${cb}
+${p} That's all, folks! - Well, that wasn't so hard, was it?
+${p} (This quine was written by <david.madore@ens.fr> - see
+${p} <URL: http://www.eleves.ens.fr:8080/home/madore/computers/quine.html >
+${p} for more information on quines and how to write them.)"
+# HERE is the end of the quine's data.
+# They represent the following code, with various quotations:
+# \ (backslash) is represented as ${b}
+# " (double quote) is represented as ${q}
+# # (sharp/pound/shibboleth/whatever) is represented as ${p}
+# { (open brace) is represented as ${ob}
+# } (closed brace) is represented as ${cb}
+# $ (dollar) is represented as ${d}
+# HERE (the magic word) is represented as ${bubble}
+# (The point of the magic word is to allow the reader
+# to conveniently skip over the large data section.)
+#
+# Now we have the quine's code!
+#
+# First, print the lines that come before the data.
+foreground { printf %s \#\!" " }
+foreground { printf %s\\n "/command/execlineb -P" }
+foreground { printf %s\\n \#" Public Domain." }
+foreground { printf %s\\n \#" See comments below." }
+foreground { printf %s \#" (Search for " }
+foreground { printf %s\\n \"HERE\".) }
+foreground { printf %s\\n \# }
+foreground { printf %s "define -sCd \"\\n\" lns \"" }
+# Next, print the data themselves, as data.
+for lin { ${lns} } {
+multisubstitute {
+define b ${b}
+define q ${q}
+define p ${p}
+define ob ${ob}
+define cb ${cb}
+define d ${d}
+define bubble ${bubble}
+define intron ${intron}
+} printf \\n%s ${lin} }
+foreground { printf %s\\n \" }
+# Finally, use the data to print the code!
+for lin { ${lns} } {
+multisubstitute {
+define b \\
+define q \"
+define p \#
+define ob \{
+define cb \}
+define d $
+define bubble HERE
+define intron "NOTICE HOW THIS SENTENCE APPEARS ONLY ONCE IN THIS QUINE?"
+} printf %s\\n ${lin} }
+# That's all, folks! - Well, that wasn't so hard, was it?
+# (This quine was written by <david.madore@ens.fr> - see
+# <URL: http://www.eleves.ens.fr:8080/home/madore/computers/quine.html >
+# for more information on quines and how to write them.)
diff --git a/doc/quine-jriou.txt b/doc/quine-jriou.txt
new file mode 100644
index 0000000..f8e5455
--- /dev/null
+++ b/doc/quine-jriou.txt
@@ -0,0 +1,28 @@
+#!/command/execlineb
+define A "#!/command/execlineb"
+define B "fine G $ foreground { echo ${C} }
+echo -n foreground ${D} define C ${E}${C}${R}foreground
+${D} echo ${G}${D}A${H} ${H}${R}foreground
+${D} echo define A ${G}${D}C${H}${G}${D}A${H}${G}${D}C${H} ${H}${R}echo
+-n define B ${G}${D}C${H} ${H}${R}foreground
+${D} echo -n ${G}${D}B${H} ${H}${R}foreground
+${D} multisubstitute ${D} define C ${E}${C} define D ${E}${D}${R}define
+E ${E}${E}${R}define
+H ${E}${H} define R ${C}${R}${C} ${H} de } echo ${B}"
+foreground { define C \"
+foreground { echo ${A} }
+foreground { echo define A ${C}${A}${C} }
+echo -n define B ${C} }
+foreground { echo -n ${B} }
+foreground { multisubstitute { define C \" define D \{
+define E \\
+define H \} define R "
+" } define G $ foreground { echo ${C} }
+echo -n foreground ${D} define C ${E}${C}${R}foreground
+${D} echo ${G}${D}A${H} ${H}${R}foreground
+${D} echo define A ${G}${D}C${H}${G}${D}A${H}${G}${D}C${H} ${H}${R}echo
+-n define B ${G}${D}C${H} ${H}${R}foreground
+${D} echo -n ${G}${D}B${H} ${H}${R}foreground
+${D} multisubstitute ${D} define C ${E}${C} define D ${E}${D}${R}define
+E ${E}${E}${R}define
+H ${E}${H} define R ${C}${R}${C} ${H} de } echo ${B}
diff --git a/doc/quine-prj-2.txt b/doc/quine-prj-2.txt
new file mode 100644
index 0000000..9c60b92
--- /dev/null
+++ b/doc/quine-prj-2.txt
@@ -0,0 +1,15 @@
+#!/command/execlineb
+define e "#!/command/execlineb
+define e ${q}${E}${q}
+multisubstitute {
+define q ${b}${q}
+define b ${b}${b}
+define E $e
+}
+echo $e"
+multisubstitute {
+define q \"
+define b \\
+define E $e
+}
+echo $e
diff --git a/doc/quine-prj-3.txt b/doc/quine-prj-3.txt
new file mode 100644
index 0000000..e5b5708
--- /dev/null
+++ b/doc/quine-prj-3.txt
@@ -0,0 +1,13 @@
+#!/command/execlineb -P
+define e "#!/command/execlineb -P
+define e ${q}${E}${q}
+export E $e
+define q ${b}${q}
+define b ${b}${b}
+import E
+echo $e"
+export E $e
+define q \"
+define b \\
+import E
+echo $e
diff --git a/doc/quine-prj.txt b/doc/quine-prj.txt
new file mode 100644
index 0000000..8d2643f
--- /dev/null
+++ b/doc/quine-prj.txt
@@ -0,0 +1,13 @@
+#!/command/execlineb
+define e "#!/command/execlineb
+define e $q${E}${q}
+env e=$e
+define q ${b}${q}
+define b ${b}${b}
+importas E e
+echo $e"
+env e=$e
+define q \"
+define b \\
+importas E e
+echo $e
diff --git a/doc/redirfd.html b/doc/redirfd.html
new file mode 100644
index 0000000..fd29ef4
--- /dev/null
+++ b/doc/redirfd.html
@@ -0,0 +1,100 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the redirfd command</title>
+  <meta name="Description" content="execline: the redirfd command" />
+  <meta name="Keywords" content="execline command redirfd" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>redirfd</tt> program </h1>
+
+<p>
+<tt>redirfd</tt> redirects a given file descriptor to a file, then
+executes a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     redirfd [ -r | -w | -u | -a | -c | -x ] [ -n | -b ] <em>fd</em> <em>file</em> <em>prog...</em>
+</pre>
+
+<p>
+<tt>redirfd</tt> redirects the file descriptor number <em>fd</em>
+to <em>file</em>, then execs into <em>prog...</em>.
+</p>
+
+<h2> Options </h2>
+
+<p>
+ One and only one of the -r, -w, -u, -a, -c, or -x options must be given;
+the -n and -b options may be added in any case.
+</p>
+
+<ul>
+ <li> <tt>-r</tt>&nbsp;: open <em>file</em> for reading. </li>
+ <li> <tt>-w</tt>&nbsp;: open <em>file</em> for writing, truncating it if it already exists. </li>
+ <li> <tt>-u</tt>&nbsp;: open <em>file</em> for reading and writing. </li>
+ <li> <tt>-a</tt>&nbsp;: open <em>file</em> for appending, creating it if it doesn't exist. </li>
+ <li> <tt>-c</tt>&nbsp;: open <em>file</em> for appending. Do not create it if it doesn't exist. </li>
+ <li> <tt>-x</tt>&nbsp;: open <em>file</em> for writing, creating it, failing if it already exists. </li>
+ <li> <tt>-n</tt>&nbsp;: open <em>file</em> in non-blocking mode. </li>
+ <li> <tt>-b</tt>&nbsp;: change mode of <em>file</em> after opening it:
+to non-blocking mode if the <tt>-n</tt> option was not given,
+to blocking mode if it was. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>redirfd -r <em>n</em> <em>file</em> prog...</tt> is roughly equivalent to
+<tt>sh -c 'exec prog... <em>n</em>&lt;<em>file</em>'</tt></li>
+ <li> <tt>redirfd -w <em>n</em> <em>file</em> prog...</tt> is roughly equivalent to
+<tt>sh -c 'exec prog... <em>n</em>&gt;<em>file</em>'</tt></li>
+ <li> <tt>redirfd -u <em>n</em> <em>file</em> prog...</tt> is roughly equivalent to
+<tt>sh -c 'exec prog... <em>n</em>&lt;&gt;<em>file</em>'</tt></li>
+ <li> <tt>redirfd -a <em>n</em> <em>file</em> prog...</tt> is roughly equivalent to
+<tt>sh -c 'exec prog... <em>n</em>&gt;&gt;<em>file</em>'</tt></li>
+ <li> <tt>redirfd -c <em>n</em> <em>file</em> prog...</tt> has no portable
+shell equivalent. Some shells provide the <em>noclobber</em> option for
+a similar feature. </li>
+ <li> <tt>redirfd -x <em>n</em> <em>file</em> prog...</tt> has no portable
+shell equivalent.</tt> </li>
+</ul>
+
+<h2> Special fifo handling </h2>
+
+<p>
+ The <tt>-n</tt> and <tt>-b</tt> options are especially useful with
+named pipes. 
+</p>
+
+<ul>
+ <li> Opening a fifo for reading, blocking if there is no writer:
+<tt>redirfd -r <em>n</em> <em>fifo</em> prog...</tt></li>
+ <li> Opening a fifo for reading, with instant success even if
+there is no writer, and blocking at the first attempt to read from it:
+<tt>redirfd -r -nb <em>n</em> <em>fifo</em> prog...</tt></li>
+ <li> Opening a fifo for writing, blocking if there is no reader:
+<tt>redirfd -w <em>n</em> <em>fifo</em> prog...</tt></li>
+ <li> Opening a fifo for writing, with instant success even if
+there is no reader:
+<tt>redirfd -w -nb <em>n</em> <em>fifo</em> prog...</tt>. Warning:
+the first attempt to write to the fifo will raise a SIGPIPE if there is
+still no reader at that time. The named pipe semantics normally do not
+allow a fifo to be open for writing without a reading end, and you
+should know what you are doing if you're using <tt>redirfd</tt>
+this way. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/runblock.html b/doc/runblock.html
new file mode 100644
index 0000000..6f5386e
--- /dev/null
+++ b/doc/runblock.html
@@ -0,0 +1,75 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the runblock program</title>
+    <meta name="Description" content="execline: the runblock program" />
+    <meta name="Keywords" content="execline command runblock" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">www.skarnet.org</a>
+</p>
+
+<h1> The <tt>runblock</tt> program </h1>
+
+<p>
+<tt>runblock</tt>'s purpose is to help you write execline commands
+in the execline language. It can only be used inside an execline
+script. If the script has been given blocks as arguments, <tt>runblock</tt>
+allows you to execute one of the blocks individually.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     runblock [ -P ] [ -n <em>argshift</em> ] [ -r ] <em>n</em>
+</pre>
+
+<ul>
+ <li> <tt>runblock</tt> skips the first <em>argshift</em> positional
+parameters. It does that to allow you to design commands that take simple
+arguments <em>and</em> blocks. </li>
+ <li> Then <tt>runblock</tt> looks for and parses
+<a href="el_semicolon.html">blocks</a> in the positional parameters. </li>
+ <li> If the <tt>-r</tt> option is present: <tt>runblock</tt> skips
+<em>n</em> blocks and execs into the remaining arguments. </li>
+ <li> Else it skips <em>n</em>-1 blocks and execs into the <em>n</em>th
+one. </li>
+ <li> Normally <tt>runblock</tt> <a href="el_pushenv.html#pop">pops</a>
+its environment frame before executing. If the <tt>-P</tt> option has
+been given, it <em>does not</em> pop. </li>
+ <li> Of course, if the block structure doesn't match, <tt>runblock</tt>
+exits 100 with an error message. </li>
+</ul>
+
+<h2> Example: implementing the <a href="ifelse.html">ifelse</a> command </h2>
+
+<p>
+ Suppose that we want to implement the <a href="ifelse.html">ifelse</a> command as
+an execline script, using the <a href="ifte.html">ifte</a> command.
+<tt>runblock</tt> allows us to do it in a simple way:
+</p>
+
+<pre>
+ #!/command/execlineb
+ ifte { runblock 2 } { runblock -r 2 } runblock 1
+</pre>
+
+<p>
+ That's it.
+</p>
+
+<h2> Credits </h2>
+
+<p>
+ The <tt>runblock</tt> idea, as well as the <tt>ifelse</tt> idea, comes
+from <a href="http://code.dogmap.org/">Paul Jarc</a>.
+</p>
+
+</body>
+</html>
diff --git a/doc/shift.html b/doc/shift.html
new file mode 100644
index 0000000..775e472
--- /dev/null
+++ b/doc/shift.html
@@ -0,0 +1,68 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: the shift program</title>
+    <meta name="Description" content="execline: the shift program" />
+    <meta name="Keywords" content="execline command shift" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">www.skarnet.org</a>
+</p>
+
+<h1> The <tt>shift</tt> program </h1>
+
+<p>
+<tt>shift</tt> shifts the positional parameters of an execline script.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     shift [ -n <em>argn</em> ] [ -b <em>blockn</em> ] <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>shift</tt> shifts <em>argn</em> positional parameters,
+then <em>blockn</em> blocks. It then execs <em>prog...</em>. </li>
+ <li> By default, <em>argn</em> and <em>blockn</em> are both zero;
+but if neither the <tt>-n</tt> nor the <tt>-b</tt> option is given,
+then <em>argn</em> is 1 and <em>blockn</em> is 0. </li>
+</ul>
+
+<h2> Details </h2>
+
+<ul>
+ <li> <tt>shift</tt> reads the number of "positional parameters" in the
+<tt>#</tt> environment variable. Let <em>n</em> be that number. </li>
+ <li> If the <tt>#</tt> environment variable is not set or does not
+contain a valid number, or one of the <tt>0</tt>, <tt>1</tt>, ...,
+<tt><em>n</em></tt> environment variables is not set, <tt>shift</tt>
+exits 100 with an error message. </li>
+ <li> <tt>shift</tt> calculates a shift value <em>m</em>, corresponding
+to <em>argn</em> arguments followed by enough arguments to make
+<em>blockn</em> blocks. </li>
+ <li> It shifts the positional parameters <em>m</em> times: the
+value of the <tt><em>m</em>+1</tt> variable becomes the value of the
+<tt>1</tt> variable, <tt><em>m</em>+2</tt> becomes <tt>2</tt> and so on,
+and <tt>#</tt> is set to <em>n</em>-<em>m</em> (floored at zero). </li>
+ <li> <tt>shift</tt> then execs into <em>prog...</em>. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>shift</tt> is a standard shell builtin. Be careful if you
+want to use it outside of an execline script. </li>
+ <li> The <tt>-b</tt> option is only useful to implement execline
+commands in the execline language. You shouldn't normally have to
+use it. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/tryexec.html b/doc/tryexec.html
new file mode 100644
index 0000000..2fcc7b6
--- /dev/null
+++ b/doc/tryexec.html
@@ -0,0 +1,67 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the tryexec command</title>
+  <meta name="Description" content="execline: the tryexec command" />
+  <meta name="Keywords" content="execline command tryexec" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>tryexec</tt> program </h1>
+
+<p>
+<tt>tryexec</tt> executes into a command line, with a fallback.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="execlineb.html">execlineb</a> script:
+</p>
+
+<pre>
+     tryexec [ -n ] [ -c ] [ -l ] [ -a argv0 ] { <em>prog1...</em> } <em>prog2...</em>
+</pre>
+
+<ul>
+ <li> <tt>tryexec</tt> reads <em>prog1...</em> in a
+<a href="el_semicolon.html">block</a>. It then executes into it,
+completely forgetting <em>prog2...</em> </li>
+ <li> If for some reason the <tt>execve()</tt> fails - for instance,
+a non-executable <em>prog1</em> - then <tt>tryexec</tt> executes
+into <em>prog2...</em> instead. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-n</tt>&nbsp;: reverse <em>prog1...</em> and <em>prog2...</em>'s
+role. The latter becomes the main execution path and the former becomes
+the fallback. </li>
+</ul>
+
+<p>
+  The <tt>-c</tt>, <tt>-l</tt> and <tt>-a</tt> options have the same
+semantics as with the <a href="exec.html">exec</a> program.
+</p>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>tryexec <em>prog1...</em> "" <em>prog2...</em></tt> would be
+equivalent to
+<tt>sh -c 'exec <em>prog1...</em> || exec <em>prog2...</em>'</tt>, if
+such a shell construct existed. Unfortunately, the shell language does
+not offer that functionality. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/umask.html b/doc/umask.html
new file mode 100644
index 0000000..5edf511
--- /dev/null
+++ b/doc/umask.html
@@ -0,0 +1,44 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the umask command</title>
+  <meta name="Description" content="execline: the umask command" />
+  <meta name="Keywords" content="execline command umask" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>umask</tt> program </h1>
+
+<p>
+<tt>umask</tt> sets the umask (file creation mask),
+then executes a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     umask <em>mask</em> <em>prog...</em>
+</pre>
+
+<p>
+<tt>umask</tt> sets the current umask to <em>mask</em>,
+then execs into <em>prog...</em>.
+</p>
+
+<h2> Notes </h2>
+
+<p>
+<tt>umask</tt> is a standard shell builtin. Be careful if you want to
+use the <tt>umask</tt> command outside of an <tt>execline</tt> script.
+</p>
+
+</body>
+</html>
diff --git a/doc/unexport.html b/doc/unexport.html
new file mode 100644
index 0000000..331189c
--- /dev/null
+++ b/doc/unexport.html
@@ -0,0 +1,46 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the unexport command</title>
+  <meta name="Description" content="execline: the unexport command" />
+  <meta name="Keywords" content="execline command unexport environment" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>unexport</tt> program </h1>
+
+<p>
+<tt>unexport</tt> removes a variable from the environment, then
+executes a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     unexport <em>var</em> <em>prog...</em>
+</pre>
+
+<p>
+<tt>unexport</tt> removes the <em>var</em> variable from the
+environment, then execs into <em>prog</em> with
+its arguments.
+</p>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> Unsetting <em>var</em> is quite different from setting it to an
+empty value. Shell scripts usually won't make the distinction;
+execline does. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/upgrade.html b/doc/upgrade.html
new file mode 100644
index 0000000..9cd795b
--- /dev/null
+++ b/doc/upgrade.html
@@ -0,0 +1,35 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: how to upgrade</title>
+    <meta name="Description" content="execline: how to upgrade" />
+    <meta name="Keywords" content="execline installation upgrade" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+
+<h2> to 2.0.0.0 </h2>
+
+<ul>
+ <li> The build system has completely changed. It is now a standard
+<tt>./configure &amp;&amp; make &amp; &amp; sudo make install</tt>
+build system. See the enclosed INSTALL file for details. </li>
+ <li> slashpackage is not activated by default. </li>
+ <li> shared libraries are neither built nor used by default. </li>
+ <li> skalibs dependency bumped to 2.0.0.0 </li>
+ <li> The obsolete -E option to backtick, forx and forbacktickx is not
+supported anymore. </li>
+ <li> multisubstitute does not support the "backtick" directive
+anymore. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/wait.html b/doc/wait.html
new file mode 100644
index 0000000..680cd78
--- /dev/null
+++ b/doc/wait.html
@@ -0,0 +1,53 @@
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="Content-Language" content="en" />
+  <title>execline: the wait command</title>
+  <meta name="Description" content="execline: the wait command" />
+  <meta name="Keywords" content="execline command wait" />
+  <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>wait</tt> program </h1>
+
+<p>
+<tt>wait</tt> waits for a set of children, then executes a program.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="execlineb.html">execlineb</a> script:
+</p>
+
+<pre>
+     wait [ -r ] { [ <em>pids...</em> ] } <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>wait</tt> reads a list of <em>pids</em> in a
+(possibly empty) <a href="el_semicolon.html">block</a>,
+and unquotes it. </li>
+ <li> <tt>wait</tt> waits for every child whose pid is
+listed in <em>pids...</em>. If <em>pids...</em> is an
+empty list, it waits for every child process it has. </li>
+ <li><tt>wait</tt> then execs into <em>prog...</em>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-r</tt>&nbsp;: reap mode. Do not pause until a child has
+exited; only reap all pending zombies. The read block must be empty
+for that option to be effective. </li>
+</ul>
+
+</body>
+</html>
diff --git a/package/deps-build b/package/deps-build
new file mode 100644
index 0000000..05d5af4
--- /dev/null
+++ b/package/deps-build
@@ -0,0 +1 @@
+/package/prog/skalibs
diff --git a/package/deps.mak b/package/deps.mak
new file mode 100644
index 0000000..96211e9
--- /dev/null
+++ b/package/deps.mak
@@ -0,0 +1,110 @@
+#
+# This file has been generated by tools/gen-deps.sh
+#
+
+src/execline/background.o src/execline/background.lo: src/execline/background.c src/include/execline/execline.h
+src/execline/backtick.o src/execline/backtick.lo: src/execline/backtick.c src/include/execline/execline.h
+src/execline/cd.o src/execline/cd.lo: src/execline/cd.c
+src/execline/define.o src/execline/define.lo: src/execline/define.c src/include-local/exlsn.h
+src/execline/dollarat.o src/execline/dollarat.lo: src/execline/dollarat.c
+src/execline/elgetopt.o src/execline/elgetopt.lo: src/execline/elgetopt.c src/include/execline/execline.h
+src/execline/elgetpositionals.o src/execline/elgetpositionals.lo: src/execline/elgetpositionals.c src/include-local/exlsn.h
+src/execline/elglob.o src/execline/elglob.lo: src/execline/elglob.c src/include-local/exlsn.h
+src/execline/emptyenv.o src/execline/emptyenv.lo: src/execline/emptyenv.c src/include/execline/execline.h
+src/execline/exec.o src/execline/exec.lo: src/execline/exec.c
+src/execline/execlineb.o src/execline/execlineb.lo: src/execline/execlineb.c src/include/execline/execline.h src/include-local/exlsn.h
+src/execline/exit.o src/execline/exit.lo: src/execline/exit.c
+src/execline/export.o src/execline/export.lo: src/execline/export.c
+src/execline/fdblock.o src/execline/fdblock.lo: src/execline/fdblock.c
+src/execline/fdclose.o src/execline/fdclose.lo: src/execline/fdclose.c
+src/execline/fdmove.o src/execline/fdmove.lo: src/execline/fdmove.c
+src/execline/fdreserve.o src/execline/fdreserve.lo: src/execline/fdreserve.c
+src/execline/forbacktickx.o src/execline/forbacktickx.lo: src/execline/forbacktickx.c src/include/execline/config.h src/include/execline/execline.h
+src/execline/foreground.o src/execline/foreground.lo: src/execline/foreground.c src/include/execline/execline.h
+src/execline/forx.o src/execline/forx.lo: src/execline/forx.c src/include/execline/config.h src/include/execline/execline.h
+src/execline/getpid.o src/execline/getpid.lo: src/execline/getpid.c
+src/execline/heredoc.o src/execline/heredoc.lo: src/execline/heredoc.c
+src/execline/homeof.o src/execline/homeof.lo: src/execline/homeof.c
+src/execline/if.o src/execline/if.lo: src/execline/if.c src/include/execline/execline.h
+src/execline/ifelse.o src/execline/ifelse.lo: src/execline/ifelse.c src/include/execline/execline.h
+src/execline/ifte.o src/execline/ifte.lo: src/execline/ifte.c src/include/execline/execline.h
+src/execline/ifthenelse.o src/execline/ifthenelse.lo: src/execline/ifthenelse.c src/include/execline/execline.h
+src/execline/import.o src/execline/import.lo: src/execline/import.c src/include-local/exlsn.h
+src/execline/importas.o src/execline/importas.lo: src/execline/importas.c src/include-local/exlsn.h
+src/execline/loopwhilex.o src/execline/loopwhilex.lo: src/execline/loopwhilex.c src/include/execline/execline.h
+src/execline/multidefine.o src/execline/multidefine.lo: src/execline/multidefine.c src/include-local/exlsn.h
+src/execline/multisubstitute.o src/execline/multisubstitute.lo: src/execline/multisubstitute.c src/include/execline/execline.h src/include-local/exlsn.h
+src/execline/pipeline.o src/execline/pipeline.lo: src/execline/pipeline.c src/include/execline/execline.h
+src/execline/piperw.o src/execline/piperw.lo: src/execline/piperw.c
+src/execline/redirfd.o src/execline/redirfd.lo: src/execline/redirfd.c
+src/execline/runblock.o src/execline/runblock.lo: src/execline/runblock.c src/include/execline/execline.h
+src/execline/shift.o src/execline/shift.lo: src/execline/shift.c src/include/execline/execline.h
+src/execline/tryexec.o src/execline/tryexec.lo: src/execline/tryexec.c src/include/execline/execline.h
+src/execline/umask.o src/execline/umask.lo: src/execline/umask.c
+src/execline/unexport.o src/execline/unexport.lo: src/execline/unexport.c
+src/execline/wait.o src/execline/wait.lo: src/execline/wait.c src/include/execline/execline.h
+src/libexecline/el_execsequence.o src/libexecline/el_execsequence.lo: src/libexecline/el_execsequence.c src/include/execline/execline.h
+src/libexecline/el_getstrict.o src/libexecline/el_getstrict.lo: src/libexecline/el_getstrict.c src/include/execline/execline.h
+src/libexecline/el_obsolescent.o src/libexecline/el_obsolescent.lo: src/libexecline/el_obsolescent.c src/include/execline/execline.h
+src/libexecline/el_popenv.o src/libexecline/el_popenv.lo: src/libexecline/el_popenv.c src/include/execline/execline.h
+src/libexecline/el_pushenv.o src/libexecline/el_pushenv.lo: src/libexecline/el_pushenv.c src/include/execline/execline.h
+src/libexecline/el_semicolon.o src/libexecline/el_semicolon.lo: src/libexecline/el_semicolon.c src/include/execline/execline.h
+src/libexecline/el_spawn0.o src/libexecline/el_spawn0.lo: src/libexecline/el_spawn0.c src/include/execline/execline.h
+src/libexecline/el_spawn1.o src/libexecline/el_spawn1.lo: src/libexecline/el_spawn1.c src/include/execline/execline.h
+src/libexecline/el_substandrun.o src/libexecline/el_substandrun.lo: src/libexecline/el_substandrun.c src/include-local/exlsn.h
+src/libexecline/el_substandrun_str.o src/libexecline/el_substandrun_str.lo: src/libexecline/el_substandrun_str.c src/include/execline/execline.h src/include-local/exlsn.h
+src/libexecline/el_substitute.o src/libexecline/el_substitute.lo: src/libexecline/el_substitute.c src/include/execline/execline.h
+src/libexecline/el_transform.o src/libexecline/el_transform.lo: src/libexecline/el_transform.c src/include/execline/execline.h
+src/libexecline/el_vardupl.o src/libexecline/el_vardupl.lo: src/libexecline/el_vardupl.c src/include/execline/execline.h
+src/libexecline/exlp.o src/libexecline/exlp.lo: src/libexecline/exlp.c src/include/execline/execline.h src/include-local/exlsn.h
+src/libexecline/exlsn_define.o src/libexecline/exlsn_define.lo: src/libexecline/exlsn_define.c src/include/execline/execline.h src/include-local/exlsn.h
+src/libexecline/exlsn_elglob.o src/libexecline/exlsn_elglob.lo: src/libexecline/exlsn_elglob.c src/include/execline/execline.h src/include-local/exlsn.h
+src/libexecline/exlsn_exlp.o src/libexecline/exlsn_exlp.lo: src/libexecline/exlsn_exlp.c src/include-local/exlsn.h
+src/libexecline/exlsn_free.o src/libexecline/exlsn_free.lo: src/libexecline/exlsn_free.c src/include-local/exlsn.h
+src/libexecline/exlsn_import.o src/libexecline/exlsn_import.lo: src/libexecline/exlsn_import.c src/include/execline/execline.h src/include-local/exlsn.h
+src/libexecline/exlsn_main.o src/libexecline/exlsn_main.lo: src/libexecline/exlsn_main.c src/include/execline/execline.h src/include-local/exlsn.h
+src/libexecline/exlsn_multidefine.o src/libexecline/exlsn_multidefine.lo: src/libexecline/exlsn_multidefine.c src/include/execline/execline.h src/include-local/exlsn.h
+
+background:src/execline/background.o -lexecline -lskarnet
+backtick:src/execline/backtick.o -lexecline -lskarnet
+cd:src/execline/cd.o -lskarnet
+define:src/execline/define.o -lexecline -lskarnet
+dollarat:src/execline/dollarat.o -lskarnet
+elgetopt:src/execline/elgetopt.o -lexecline -lskarnet
+elgetpositionals:src/execline/elgetpositionals.o -lexecline -lskarnet
+elglob:src/execline/elglob.o -lexecline -lskarnet
+emptyenv:src/execline/emptyenv.o -lexecline -lskarnet
+exec:src/execline/exec.o -lskarnet
+execlineb:src/execline/execlineb.o -lexecline -lskarnet
+exit:src/execline/exit.o -lskarnet
+export:src/execline/export.o -lskarnet
+fdblock:src/execline/fdblock.o -lskarnet
+fdclose:src/execline/fdclose.o -lskarnet
+fdmove:src/execline/fdmove.o -lskarnet
+fdreserve:src/execline/fdreserve.o -lskarnet
+forbacktickx:src/execline/forbacktickx.o -lexecline -lskarnet
+foreground:src/execline/foreground.o -lexecline -lskarnet
+forx:src/execline/forx.o -lexecline -lskarnet
+getpid:src/execline/getpid.o -lskarnet
+heredoc:src/execline/heredoc.o -lskarnet
+homeof:src/execline/homeof.o -lskarnet
+if:src/execline/if.o -lexecline -lskarnet
+ifelse:src/execline/ifelse.o -lexecline -lskarnet
+ifte:src/execline/ifte.o -lexecline -lskarnet
+ifthenelse:src/execline/ifthenelse.o -lexecline -lskarnet
+import:src/execline/import.o -lexecline -lskarnet
+importas:src/execline/importas.o -lexecline -lskarnet
+loopwhilex:src/execline/loopwhilex.o -lexecline -lskarnet
+multidefine:src/execline/multidefine.o -lexecline -lskarnet
+multisubstitute:src/execline/multisubstitute.o -lexecline -lskarnet
+pipeline:src/execline/pipeline.o -lexecline -lskarnet
+piperw:src/execline/piperw.o -lskarnet
+redirfd:src/execline/redirfd.o -lskarnet
+runblock:src/execline/runblock.o -lexecline -lskarnet
+shift:src/execline/shift.o -lexecline -lskarnet
+tryexec:src/execline/tryexec.o -lexecline -lskarnet
+umask:src/execline/umask.o -lskarnet
+unexport:src/execline/unexport.o -lskarnet
+wait:src/execline/wait.o -lexecline -lskarnet
+libexecline.a: src/libexecline/el_execsequence.o src/libexecline/el_getstrict.o src/libexecline/el_obsolescent.o src/libexecline/el_popenv.o src/libexecline/el_pushenv.o src/libexecline/el_semicolon.o src/libexecline/el_spawn0.o src/libexecline/el_spawn1.o src/libexecline/el_substandrun.o src/libexecline/el_substandrun_str.o src/libexecline/el_substitute.o src/libexecline/el_transform.o src/libexecline/el_vardupl.o src/libexecline/exlsn_define.o src/libexecline/exlsn_elglob.o src/libexecline/exlsn_import.o src/libexecline/exlsn_multidefine.o src/libexecline/exlsn_exlp.o src/libexecline/exlsn_main.o src/libexecline/exlsn_free.o src/libexecline/exlp.o
+libexecline.so: src/libexecline/el_execsequence.lo src/libexecline/el_getstrict.lo src/libexecline/el_obsolescent.lo src/libexecline/el_popenv.lo src/libexecline/el_pushenv.lo src/libexecline/el_semicolon.lo src/libexecline/el_spawn0.lo src/libexecline/el_spawn1.lo src/libexecline/el_substandrun.lo src/libexecline/el_substandrun_str.lo src/libexecline/el_substitute.lo src/libexecline/el_transform.lo src/libexecline/el_vardupl.lo src/libexecline/exlsn_define.lo src/libexecline/exlsn_elglob.lo src/libexecline/exlsn_import.lo src/libexecline/exlsn_multidefine.lo src/libexecline/exlsn_exlp.lo src/libexecline/exlsn_main.lo src/libexecline/exlsn_free.lo src/libexecline/exlp.lo
diff --git a/package/info b/package/info
new file mode 100644
index 0000000..03775df
--- /dev/null
+++ b/package/info
@@ -0,0 +1,4 @@
+package=execline
+version=2.0.0.0
+category=admin
+package_macro_name=EXECLINE
diff --git a/package/modes b/package/modes
new file mode 100644
index 0000000..29e242a
--- /dev/null
+++ b/package/modes
@@ -0,0 +1,41 @@
+background		0755
+backtick		0755
+cd			0755
+define			0755
+dollarat		0755
+elgetopt		0755
+elgetpositionals	0755
+elglob			0755
+multidefine		0755
+multisubstitute		0755
+emptyenv		0755
+exec			0755
+exit			0755
+execlineb		0755
+export			0755
+fdblock			0755
+fdclose			0755
+fdreserve		0755
+fdmove			0755
+forx			0755
+forbacktickx		0755
+foreground		0755
+getpid			0755
+heredoc			0755
+homeof			0755
+if			0755
+ifelse			0755
+ifte			0755
+ifthenelse		0755
+import			0755
+importas		0755
+loopwhilex		0755
+piperw			0755
+pipeline		0755
+redirfd			0755
+runblock		0755
+shift			0755
+tryexec			0755
+umask			0755
+unexport		0755
+wait			0755
diff --git a/package/targets.mak b/package/targets.mak
new file mode 100644
index 0000000..9583a50
--- /dev/null
+++ b/package/targets.mak
@@ -0,0 +1,51 @@
+BIN_TARGETS = \
+background \
+backtick \
+cd \
+define \
+dollarat \
+elgetopt \
+elgetpositionals \
+elglob \
+emptyenv \
+exec \
+execlineb \
+exit \
+export \
+fdblock \
+fdclose \
+fdmove \
+fdreserve \
+forbacktickx \
+foreground \
+forx \
+getpid \
+heredoc \
+homeof \
+if \
+ifelse \
+ifte \
+ifthenelse \
+import \
+importas \
+loopwhilex \
+multidefine \
+multisubstitute \
+pipeline \
+piperw \
+redirfd \
+runblock \
+shift \
+tryexec \
+umask \
+unexport \
+wait
+
+SBIN_TARGETS =
+LIBEXEC_TARGETS =
+
+SHARED_LIBS = \
+libexecline.so
+
+STATIC_LIBS = \
+libexecline.a
diff --git a/patch-for-solaris b/patch-for-solaris
new file mode 100755
index 0000000..02f2e3c
--- /dev/null
+++ b/patch-for-solaris
@@ -0,0 +1,17 @@
+#!/usr/xpg4/bin/sh
+
+patchit () {
+  echo '#!/usr/xpg4/bin/sh' > $1.tmp
+  tail -n +2 $1 >> $1.tmp
+  mv -f $1.tmp $1
+  chmod 755 $1
+}
+
+patchit ./configure
+patchit ./tools/install.sh
+patchit ./tools/gen-deps.sh
+
+echo 'SHELL := /usr/xpg4/bin/sh' > Makefile.tmp
+echo >> Makefile.tmp
+cat Makefile >> Makefile.tmp
+mv -f Makefile.tmp Makefile
diff --git a/src/execline/background.c b/src/execline/background.c
new file mode 100644
index 0000000..d3e96ff
--- /dev/null
+++ b/src/execline/background.c
@@ -0,0 +1,76 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <unistd.h>
+#ifdef EXECLINE_OLD_VARNAMES
+#include <skalibs/bytestr.h>
+#endif
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/uint64.h>
+#include <execline/execline.h>
+
+#define USAGE "background [ -d ] { command... }"
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  pid_t pid ;
+  int argc1 ;
+  int df = 0 ;
+  PROG = "background" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "d", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'd' : df = 1 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  argc1 = el_semicolon(argv) ;
+  if (!argc1) strerr_dief1x(100, "empty block") ;
+  if (argc1 >= argc) strerr_dief1x(100, "unterminated block") ;
+  if (argc1 + 1 == argc) df = 0 ;
+  argv[argc1] = 0 ;
+
+  if (df)
+  {
+    pid = doublefork() ;
+    switch (pid)
+    {
+      case -1: strerr_diefu1sys(111, "doublefork") ;
+      case 0:
+        PROG = "background (grandchild)" ;
+        pathexec0_run(argv, envp) ;
+        strerr_dieexec(127, argv[0]) ;
+    }
+  }
+  else
+  {
+    pid = el_spawn0(argv[0], argv, envp) ;
+    if (!pid) strerr_diefu2sys(111, "spawn ", argv[0]) ;
+  }
+  if (argc1 + 1 == argc) return 0 ;
+  {
+#ifdef EXECLINE_OLD_VARNAMES
+    char fmt[UINT64_FMT * 2 + 10] = "!=" ;
+#else
+    char fmt[UINT64_FMT + 2] = "!=" ;
+#endif
+    register unsigned int i = 2 ;
+    i += uint64_fmt(fmt+i, (uint64)pid) ; fmt[i++] = 0 ;
+#ifdef EXECLINE_OLD_VARNAMES
+    byte_copy(fmt+i, 8, "LASTPID=") ; i += 8 ;
+    i += uint64_fmt(fmt+i, (uint64)pid) ; fmt[i++] = 0 ;
+#endif
+    pathexec_r(argv + argc1 + 1, envp, env_len(envp), fmt, i) ;
+  }
+  strerr_dieexec(111, argv[argc1+1]) ;
+}
diff --git a/src/execline/backtick.c b/src/execline/backtick.c
new file mode 100644
index 0000000..d0f74e4
--- /dev/null
+++ b/src/execline/backtick.c
@@ -0,0 +1,84 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+#include <execline/execline.h>
+
+#define USAGE "backtick [ -i ] [ -n ] var { prog... } remainder..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  subgetopt_t localopt = SUBGETOPT_ZERO ;
+  int argc1 ;
+  stralloc modif = STRALLOC_ZERO ;
+  int insist = 0, chomp = 0 ;
+  PROG = "backtick" ;
+  for (;;)
+  {
+    register int opt = subgetopt_r(argc, argv, "ein", &localopt) ;
+    if (opt < 0) break ;
+    switch (opt)
+    {
+      case 'i' : insist = 1 ; break ;
+      case 'n' : chomp = 1 ; break ;
+      case 'e' : break ; /* compat */
+      default : dieusage() ;
+    }
+  }
+  argc -= localopt.ind ; argv += localopt.ind ;
+
+  if (argc < 2) dieusage() ;
+  if (!*argv[0]) strerr_dief1x(100, "empty variable not accepted") ;
+  if (!stralloc_cats(&modif, argv[0]) || !stralloc_catb(&modif, "=", 1))
+    strerr_diefu1sys(111, "stralloc_catb") ;
+  argc-- ; argv++ ;
+  argc1 = el_semicolon(argv) ;
+  if (!argc1) strerr_dief1x(100, "empty block") ;
+  if (argc1 >= argc) strerr_dief1x(100, "unterminated block") ;
+
+  {
+    int p[2] ;
+    pid_t pid ;
+    if (pipe(p) < 0) strerr_diefu1sys(111, "pipe") ;
+    pid = fork() ;
+    switch (pid)
+    {
+      case -1: strerr_diefu1sys(111, "fork") ;
+      case 0:
+        argv[argc1] = 0 ;
+        fd_close(p[0]) ;
+        PROG = "backtick (child)" ;
+        if (fd_move(1, p[1]) < 0) strerr_diefu1sys(111, "fd_move") ;
+        pathexec_run(argv[0], argv, envp) ;
+        strerr_dieexec(111, argv[0]) ;
+    }
+    fd_close(p[1]) ;
+    if (!slurp(&modif, p[0])) strerr_diefu1sys(111, "slurp") ;
+    fd_close(p[0]) ;
+    if (wait_pid(pid, &p[0]) < 0) strerr_diefu1sys(111, "wait_pid") ;
+    if (insist && wait_status(p[0]))
+      strerr_dief1x(wait_status(p[0]), "child process exited non-zero") ;
+  }
+  if (argc == argc1 - 1) return 0 ;
+  if (!stralloc_0(&modif)) strerr_diefu1sys(111, "stralloc_catb") ;
+  {
+    unsigned int reallen = str_len(modif.s) ;
+    if (reallen < modif.len - 1)
+    {
+      if (insist)
+        strerr_dief1x(1, "child process output contained a null character") ;
+      else
+        modif.len = reallen + 1 ;
+    }
+    if (chomp && (modif.s[modif.len - 2] == '\n'))
+      modif.s[--modif.len - 1] = 0 ;
+  }
+  pathexec_r(argv + argc1 + 1, envp, env_len(envp), modif.s, modif.len) ;
+  strerr_dieexec(111, argv[argc1 + 1]) ;
+}
diff --git a/src/execline/cd.c b/src/execline/cd.c
new file mode 100644
index 0000000..c774ce5
--- /dev/null
+++ b/src/execline/cd.c
@@ -0,0 +1,17 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "cd path prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  PROG = "cd" ;
+  if (argc < 3) strerr_dieusage(100, USAGE) ;
+  if (chdir(argv[1]) == -1)
+    strerr_diefu2sys(111, "chdir to ", argv[1]) ;
+  pathexec_run(argv[2], argv+2, envp) ;
+  strerr_dieexec(111, argv[2]) ;
+}
diff --git a/src/execline/define.c b/src/execline/define.c
new file mode 100644
index 0000000..f87c531
--- /dev/null
+++ b/src/execline/define.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include "exlsn.h"
+
+#define USAGE "define [ -n ] [ -s ] [ -C | -c ] [ -d delim ] key value prog..."
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  PROG = "define" ;
+  exlsn_main(argc, argv, envp, &exlsn_define, USAGE) ;
+}
diff --git a/src/execline/deps-exe/background b/src/execline/deps-exe/background
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/background
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/backtick b/src/execline/deps-exe/backtick
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/backtick
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/cd b/src/execline/deps-exe/cd
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/cd
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/define b/src/execline/deps-exe/define
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/define
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/dollarat b/src/execline/deps-exe/dollarat
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/dollarat
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/elgetopt b/src/execline/deps-exe/elgetopt
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/elgetopt
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/elgetpositionals b/src/execline/deps-exe/elgetpositionals
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/elgetpositionals
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/elglob b/src/execline/deps-exe/elglob
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/elglob
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/emptyenv b/src/execline/deps-exe/emptyenv
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/emptyenv
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/exec b/src/execline/deps-exe/exec
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/exec
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/execlineb b/src/execline/deps-exe/execlineb
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/execlineb
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/exit b/src/execline/deps-exe/exit
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/exit
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/export b/src/execline/deps-exe/export
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/export
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/fdblock b/src/execline/deps-exe/fdblock
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/fdblock
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/fdclose b/src/execline/deps-exe/fdclose
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/fdclose
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/fdmove b/src/execline/deps-exe/fdmove
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/fdmove
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/fdreserve b/src/execline/deps-exe/fdreserve
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/fdreserve
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/forbacktickx b/src/execline/deps-exe/forbacktickx
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/forbacktickx
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/foreground b/src/execline/deps-exe/foreground
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/foreground
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/forx b/src/execline/deps-exe/forx
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/forx
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/getpid b/src/execline/deps-exe/getpid
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/getpid
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/heredoc b/src/execline/deps-exe/heredoc
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/heredoc
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/homeof b/src/execline/deps-exe/homeof
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/homeof
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/if b/src/execline/deps-exe/if
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/if
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/ifelse b/src/execline/deps-exe/ifelse
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/ifelse
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/ifte b/src/execline/deps-exe/ifte
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/ifte
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/ifthenelse b/src/execline/deps-exe/ifthenelse
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/ifthenelse
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/import b/src/execline/deps-exe/import
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/import
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/importas b/src/execline/deps-exe/importas
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/importas
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/loopwhilex b/src/execline/deps-exe/loopwhilex
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/loopwhilex
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/multidefine b/src/execline/deps-exe/multidefine
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/multidefine
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/multisubstitute b/src/execline/deps-exe/multisubstitute
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/multisubstitute
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/pipeline b/src/execline/deps-exe/pipeline
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/pipeline
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/piperw b/src/execline/deps-exe/piperw
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/piperw
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/redirfd b/src/execline/deps-exe/redirfd
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/redirfd
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/runblock b/src/execline/deps-exe/runblock
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/runblock
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/shift b/src/execline/deps-exe/shift
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/shift
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/tryexec b/src/execline/deps-exe/tryexec
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/tryexec
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/deps-exe/umask b/src/execline/deps-exe/umask
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/umask
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/unexport b/src/execline/deps-exe/unexport
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/execline/deps-exe/unexport
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/execline/deps-exe/wait b/src/execline/deps-exe/wait
new file mode 100644
index 0000000..8c3ba3b
--- /dev/null
+++ b/src/execline/deps-exe/wait
@@ -0,0 +1,2 @@
+-lexecline
+-lskarnet
diff --git a/src/execline/dollarat.c b/src/execline/dollarat.c
new file mode 100644
index 0000000..66b5c61
--- /dev/null
+++ b/src/execline/dollarat.c
@@ -0,0 +1,63 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/uint.h>
+#include <skalibs/netstring.h>
+
+#define USAGE "dollarat [ -n ] [ -0 | -d delimchar ]"
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int n, i = 0 ;
+  char const *x ;
+  char delim = '\n' ;
+  int zero = 0 ;
+  int nl = 1 ;
+  PROG = "dollarat" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "nd:0", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n' : nl = 0 ; break ;
+        case 'd' : delim = *l.arg ; break ;
+        case '0' : zero = 1 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (zero) delim = 0 ;
+  x = env_get2(envp, "#") ;
+  if (!x) strerr_dienotset(100, "#") ;
+  if (!uint0_scan(x, &n)) strerr_dieinvalid(100, "#") ;
+  for (; i < n ; i++)
+  {
+    char fmt[UINT_FMT] ;
+    fmt[uint_fmt(fmt, i+1)] = 0 ;
+    x = env_get2(envp, fmt) ;
+    if (!x) strerr_dienotset(100, fmt) ;
+    if (delim || zero)
+    {
+      if ((buffer_puts(buffer_1, x) < 0)
+       || (((i < n-1) || nl) && (buffer_put(buffer_1, &delim, 1) < 0)))
+        strerr_diefu1sys(111, "write to stdout") ;
+    }
+    else
+    {
+      unsigned int written = 0 ;
+      if (!netstring_put(buffer_1, x, str_len(x), &written))
+        strerr_diefu1sys(111, "write a netstring to stdout") ;
+    }
+  }
+  if (!buffer_flush(buffer_1))
+    strerr_diefu1sys(111, "write to stdout") ;
+  return 0 ;
+}
diff --git a/src/execline/elgetopt.c b/src/execline/elgetopt.c
new file mode 100644
index 0000000..4dfd4d8
--- /dev/null
+++ b/src/execline/elgetopt.c
@@ -0,0 +1,69 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/env.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/uint.h>
+#include <execline/execline.h>
+
+#define USAGE "elgetopt optstring prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int n, nbak ;
+  unsigned int envlen = env_len(envp) ;
+  stralloc modif = STRALLOC_ZERO ;
+  char const *x = env_get2(envp, "#") ;
+  PROG = "elgetopt" ;
+  if (argc < 3) strerr_dieusage(100, USAGE) ;
+  if (!x) strerr_dienotset(100, "#") ;
+  if (!uint0_scan(x, &n)) strerr_dieinvalid(100, "#") ;
+  nbak = n++ ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    char const *args[n+1] ;
+    register unsigned int i = 0 ;
+    for ( ; i < n ; i++)
+    {
+      char fmt[UINT_FMT] ;
+      fmt[uint_fmt(fmt, i)] = 0 ;
+      args[i] = env_get2(envp, fmt) ;
+      if (!args[i]) strerr_dienotset(100, fmt) ;
+    }
+    args[n] = 0 ;
+    for (;;)
+    {
+      char hmpf[11] = "ELGETOPT_?" ;
+      register int opt = sgetopt_r(n, args, argv[1], &l) ;
+      if (opt == -1) break ;
+      if (opt == '?') return 1 ;
+      hmpf[9] = opt ;
+      if (!env_addmodif(&modif, hmpf, l.arg ? l.arg : "1")) goto err ;
+    }
+    n -= l.ind ;
+    for (i = 0 ; i < nbak ; i++)
+    {
+      char fmt[UINT_FMT]  ;
+      fmt[uint_fmt(fmt, i+1)] = 0 ;
+      if (!env_addmodif(&modif, fmt, (i < n) ? args[l.ind + i] : 0)) goto err ;
+    }
+  }
+  {
+    char fmt[UINT_FMT] ;
+    fmt[uint_fmt(fmt, n)] = 0 ;
+    if (!env_addmodif(&modif, "#", fmt)) goto err ;
+  }
+  {
+    char const *const list[1] = { "ELGETOPT_" } ;
+    char const *v[envlen] ;
+    if (el_pushenv(&satmp, envp, envlen, list, 1) < 0) goto err ;
+    if (!env_make(v, envlen, satmp.s, satmp.len)) goto err ;
+    pathexec_r(argv+2, v, envlen, modif.s, modif.len) ;
+    strerr_dieexec(111, argv[2]) ;
+  }
+err:
+  strerr_diefu1sys(111, "update environment") ;
+}
diff --git a/src/execline/elgetpositionals.c b/src/execline/elgetpositionals.c
new file mode 100644
index 0000000..3ce4b69
--- /dev/null
+++ b/src/execline/elgetpositionals.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include "exlsn.h"
+
+#define USAGE "elgetpositionals [ -P num ] prog..."
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  PROG = "elgetpositionals" ;
+  exlsn_main(argc, argv, envp, &exlsn_exlp, USAGE) ;
+}
diff --git a/src/execline/elglob.c b/src/execline/elglob.c
new file mode 100644
index 0000000..ffff74a
--- /dev/null
+++ b/src/execline/elglob.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include "exlsn.h"
+
+#define USAGE "elglob [ -v ] [ -w ] [ -s ] [ -m ] [ -e ] [ -0 ] key pattern prog..."
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  PROG = "elglob" ;
+  exlsn_main(argc, argv, envp, &exlsn_elglob, USAGE) ;
+}
diff --git a/src/execline/emptyenv.c b/src/execline/emptyenv.c
new file mode 100644
index 0000000..cae8869
--- /dev/null
+++ b/src/execline/emptyenv.c
@@ -0,0 +1,92 @@
+/* ISC license. */
+
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+#include <execline/execline.h>
+
+#define USAGE "emptyenv [ -p | -c | -o | -P ] prog..."
+
+static void cleanupenv (char const *const *argv, char const *const *envp)
+{
+  stralloc sa = STRALLOC_ZERO ;
+  if (!pathexec_env("!", 0) || !pathexec_env("?", 0)) goto err ;
+#ifdef EXECLINE_OLD_VARNAMES
+  if (!pathexec_env("LASTPID", 0) || !pathexec_env("LASTEXITCODE", 0)) goto err ;
+#endif
+  for (; *envp ; envp++)
+  {
+    char const *s = *envp ;
+    sa.len = 0 ;
+    if (!str_diffn(s, "ELGETOPT_", 9)
+     || !str_diffn(s, "EXECLINE_", 9)
+     || !str_diffn(s, "FD", 2)
+     || (s[0] == '#')
+     || ((s[0] >= '0') && (s[0] <= '9')))
+      if (!stralloc_catb(&sa, s, str_chr(s, '='))
+       || !stralloc_0(&sa)
+       || !pathexec_env(sa.s, 0))
+        goto err ;
+  }
+  stralloc_free(&sa) ;
+  pathexec(argv) ;
+  strerr_dieexec(111, argv[0]) ;
+err:
+  strerr_diefu1sys(111, "clean up environment") ;
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  int flagpath = 0, flagcleanup = 0, flagopt = 0, flagpos = 0 ;
+  PROG = "emptyenv" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "pcoP", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'p' : flagpath = 1 ; break ;
+        case 'c' : flagcleanup = 1 ; break ;
+        case 'o' : flagopt = 1 ; break ;
+        case 'P' : flagpos = 1 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) strerr_dieusage(100, USAGE) ;
+  if (flagcleanup) cleanupenv(argv, envp) ;
+  else if (!flagopt && !flagpos)
+  {
+    char const *newenv[2] = { 0, 0 } ;
+    if (flagpath)
+      for (; *envp ; envp++)
+        if (!str_diffn(*envp, "PATH=", 5))
+        {
+          newenv[0] = *envp ;
+          break ;
+        }
+    pathexec_run(argv[0], argv, newenv) ;
+  }
+  else
+  {
+    static char const *const list[12] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "#", "ELGETOPT_" } ;
+    stralloc sa = STRALLOC_ZERO ;
+    unsigned int envlen = env_len(envp) ;
+    int n = el_popenv(&sa, envp, envlen, flagpos ? list : list + 11, 11 * flagpos + flagopt) ;
+    if (n < 0) strerr_diefu1sys(111, "pop current execline environment") ;
+    {
+      char const *v[envlen - n + 1] ;
+      if (!env_make(v, envlen-n, sa.s, sa.len)) strerr_diefu1sys(111, "env_make") ;
+      v[envlen-n] = 0 ;
+      pathexec_run(argv[0], argv, v) ;
+    }
+    stralloc_free(&sa) ;
+  }
+  strerr_dieexec(111, argv[0]) ;
+}
diff --git a/src/execline/exec.c b/src/execline/exec.c
new file mode 100644
index 0000000..2213ba4
--- /dev/null
+++ b/src/execline/exec.c
@@ -0,0 +1,48 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+
+#define USAGE "exec [ -c ] [ -l ] [ -a argv0 ] prog..."
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  static char const *const zero = 0 ;
+  char const *executable = 0 ;
+  char const *argv0 = 0 ;
+  int dash = 0 ;
+  PROG = "exec" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "cla:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'c' : envp = &zero ; break ;
+        case 'l' : dash = 1 ; break ;
+        case 'a' : argv0 = l.arg ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) strerr_dieusage(100, USAGE) ;
+
+  executable = argv[0] ;
+  if (argv0) argv[0] = argv0 ;
+  if (dash)
+  {
+    register unsigned int n = str_len(argv[0]) ;
+    char dashed[n+2] ;
+    dashed[0] = '-' ;
+    byte_copy(dashed+1, n+1, argv[0]) ;
+    argv[0] = (char const *)dashed ;
+    pathexec_run(executable, argv, envp) ;
+  }
+  else pathexec_run(executable, argv, envp) ;
+  strerr_dieexec(111, executable) ;
+}
diff --git a/src/execline/execlineb.c b/src/execline/execlineb.c
new file mode 100644
index 0000000..1b9e7ad
--- /dev/null
+++ b/src/execline/execlineb.c
@@ -0,0 +1,327 @@
+/* ISC license. */
+
+#include <skalibs/uint16.h>
+#include <skalibs/uint.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+#include <execline/execline.h>
+#include "exlsn.h"
+
+#define USAGE "execlineb [ -p | -P | -S nmin ] [ -q | -w | -W ] [ -c commandline ] script args"
+
+typedef unsigned char chargen_t (void) ;
+
+/* Action (strongest 11 bits) */
+
+#define PUSH 0x8000
+#define PUSH0 0x4000
+#define PUSHSPECIAL 0x2000
+#define SETBASE 0x1000
+#define MARK 0x0800
+#define CALC 0x0400
+#define QUOTE 0x0200
+#define INCB 0x0100
+#define DECB 0x0080
+
+
+/* State (weakest 5 bits) */
+
+#define MAIN 0x00
+#define INWORD 0x01
+#define INWORDESC 0x02
+#define INSTR 0x03
+#define INSTRESC 0x04
+#define INREM 0x05
+#define OCT0 0x06
+#define OCT1 0x07
+#define OCT2 0x08
+#define DEC1 0x09
+#define DEC2 0x0a
+#define HEX0 0x0b
+#define HEX1 0x0c
+#define ENDCALC 0x0d
+#define OPENB 0x0e
+#define CLOSEB 0x0f
+#define ERROR 0x10
+#define ACCEPT 0x11
+
+static buffer b ;
+
+static void initbuffer (char const *s)
+{
+  static char buf[BUFFER_INSIZE] ;
+  int fd = open_readb(s) ;
+  if (fd < 0) strerr_diefu3sys(111, "open ", s, " for reading") ;
+  if (coe(fd) < 0) strerr_diefu2sys(111, "coe ", s) ;
+  buffer_init(&b, &buffer_read, fd, buf, BUFFER_INSIZE) ;
+}
+
+static unsigned char nextinbuffer ()
+{
+  char c ;
+  switch (buffer_get(&b, &c, 1))
+  {
+    case -1: strerr_diefu1sys(111, "read script") ;
+    case 0 : return 0 ;
+  }
+  return (unsigned char)c ;
+}
+
+static unsigned char const *string = 0 ;
+
+static unsigned char nextinstring ()
+{
+  static unsigned int pos = 0 ;
+  return string[pos++] ;
+}
+
+static int lex (stralloc *sa, chargen_t *next)
+{
+  static unsigned char const class[256] = "`aaaaaaaaadaaaaaaaaaaaaaaaaaaaaaafcbffffffffffffjhhhhhhhiifffffffmmmmmmfffffffffffffffffffffeffffggmmmgfffffffkfffkfkfkflffnfoffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ;
+  static uint16 const table[16][16] =
+  {
+    { 0x0011, 0x4011, 0x0010, 0x0010, 0x0010, 0x0011, 0x0010, 0x0010, 0x0010, 0x0010, 0x0010, 0x0010, 0x0010, 0x0010, 0x0010, 0x4091 },
+    { 0x0000, 0x4000, 0x8001, 0x8003, 0x8003, 0x0005, 0x0010, 0x8403, 0x8403, 0x8403, 0x8403, 0x0010, 0x8403, 0x8403, 0x0100, 0x4080 },
+    { 0x0005, 0x8001, 0x8001, 0x8003, 0x8003, 0x0005, 0x0010, 0x8403, 0x8403, 0x8403, 0x8403, 0x0010, 0x8403, 0x8403, 0x8001, 0x8001 },
+    { 0x0203, 0x0003, 0x8001, 0x0001, 0x8003, 0x0005, 0x0010, 0x0401, 0x0401, 0x0401, 0x0401, 0x0010, 0x0401, 0x0401, 0x0003, 0x0003 },
+    { 0x0000, 0x4000, 0x8001, 0x8003, 0x0003, 0x0000, 0x0010, 0x8403, 0x8403, 0x8403, 0x8403, 0x0010, 0x8403, 0x8403, 0x0100, 0x4080 },
+    { 0x0202, 0x0002, 0x8001, 0x0004, 0x8003, 0x0005, 0x0010, 0x0404, 0x0404, 0x0404, 0x0404, 0x0010, 0x0404, 0x0404, 0x0002, 0x0002 },
+    { 0x8201, 0x8001, 0x8001, 0x8003, 0x8003, 0x0005, 0x0010, 0x8403, 0x8403, 0x8403, 0x8403, 0x0010, 0x8403, 0x8403, 0x8001, 0x8001 },
+    { 0x8201, 0x8001, 0x8001, 0x8003, 0x2003, 0x0005, 0x0010, 0x8403, 0x8403, 0x8403, 0x8403, 0x880c, 0x800d, 0x8403, 0x8001, 0x8001 },
+    { 0x8201, 0x8001, 0x8001, 0x8003, 0x9809, 0x0005, 0x8807, 0x8008, 0x800d, 0x800a, 0x800d, 0x880c, 0x800d, 0x8403, 0x8001, 0x8001 },
+    { 0x8201, 0x8001, 0x8001, 0x8003, 0x9809, 0x0005, 0x0010, 0x8403, 0x8403, 0x800a, 0x800d, 0x880c, 0x800d, 0x8403, 0x8001, 0x8001 },
+    { 0x8201, 0x8001, 0x8001, 0x8003, 0x1006, 0x0005, 0x8807, 0x8008, 0x800d, 0x800a, 0x800d, 0x880c, 0x800d, 0x8403, 0x8001, 0x8001 },
+    { 0x8201, 0x8001, 0x8001, 0x8003, 0x2003, 0x0005, 0x0010, 0x8403, 0x8403, 0x8403, 0x8403, 0x0010, 0x8403, 0x8403, 0x8001, 0x8001 },
+    { 0x8201, 0x8001, 0x8001, 0x8003, 0x8003, 0x0005, 0x100b, 0x8403, 0x8403, 0x8403, 0x8403, 0x0010, 0x8403, 0x8403, 0x8001, 0x8001 },
+    { 0x8201, 0x8001, 0x8001, 0x8003, 0x8003, 0x0005, 0x0010, 0x8403, 0x8403, 0x8403, 0x8403, 0x880c, 0x800d, 0x8403, 0x8001, 0x8001 },
+    { 0x820e, 0x8001, 0x8001, 0x8003, 0x8003, 0x0005, 0x0010, 0x8403, 0x8403, 0x8403, 0x8403, 0x0010, 0x8403, 0x8403, 0x8001, 0x8001 },
+    { 0x820f, 0x8001, 0x8001, 0x8003, 0x8003, 0x0005, 0x0010, 0x8403, 0x8403, 0x8403, 0x8403, 0x0010, 0x8403, 0x8403, 0x8001, 0x8001 }
+  } ;
+
+  unsigned int mark = 0 ;
+  unsigned int n = 0 ;
+  unsigned char state = MAIN, base = 10 ;
+  unsigned int blevel = 0 ;
+
+  while (state < ERROR)
+  {
+    unsigned char cur = (*next)() ;
+    register uint16 c = table[class[cur]-'`'][state] ;
+    state = c & 0x1F ;
+
+ /* Actions. The order is important ! */
+
+    if (c & CALC)
+    {
+      unsigned int z ;
+      if (!stralloc_0(sa)) return -1 ;
+      sa->len = mark ;
+      uint_scan_base(sa->s + sa->len, &z, base) ;
+      sa->s[sa->len++] = (unsigned char)z ;
+    }
+    if (c & MARK) mark = sa->len ;
+    if (c & QUOTE)
+    {
+      char tilde = EXECLINE_BLOCK_QUOTE_CHAR ;
+      register unsigned int i = blevel ;
+      if (!stralloc_readyplus(sa, i<<1)) return -1 ;
+      while (i--) stralloc_catb(sa, &tilde, 1) ;
+    }
+    if (c & INCB) sa->len -= ++blevel ;
+    if (c & DECB)
+    {
+      if (!blevel--) return -4 ;
+      sa->s[--sa->len-1] = EXECLINE_BLOCK_END_CHAR ;
+      if (!EXECLINE_BLOCK_END_CHAR) sa->len-- ;
+    }
+    if (c & PUSH) if (!stralloc_catb(sa, (char *)&cur, 1)) return -1 ;
+    if (c & PUSHSPECIAL)
+    {
+      char x = 7 + byte_chr("abtnvfr", 7, cur) ;
+      if (!stralloc_catb(sa, &x, 1)) return -1 ;
+    }
+    if (c & PUSH0) if (n++, !stralloc_0(sa)) return -1 ;
+    if (c & SETBASE)
+      switch (cur)
+      {
+        case 'x' : base = 16 ; break ;
+        case '0' : base = 8 ; break ;
+        default : base = 10 ;
+      }
+  }
+  if (state == ERROR) return -2 ;
+  if (blevel) return -3 ;
+  return n ;
+}
+
+
+static int myexlp (stralloc *sa, char const *const *argv, unsigned int argc, unsigned int nmin, char const *dollar0)
+{
+  exlsn_t info = EXLSN_ZERO ;
+  unsigned int n = argc > nmin ? argc : nmin ;
+  unsigned int i = 0 ;
+
+  if (!genalloc_ready(elsubst_t, &info.data, 3 + n)) return -1 ;
+  if (!stralloc_ready(&info.vars, 6 + (n << 1))) goto err ;
+  stralloc_catb(&info.vars, "#\0" "0\0@", 6) ;
+  {
+    elsubst_t blah[3] ;
+    char fmt[UINT_FMT] ;
+    blah[0].var = 0 ; blah[0].value = 0 ; blah[0].n = 1 ;
+    if (!stralloc_catb(&info.values, fmt, uint_fmt(fmt, argc)) || !stralloc_0(&info.values)) goto err ;
+    blah[1].var = 2 ; blah[1].value = info.values.len ; blah[1].n = 1 ;
+    if (!stralloc_catb(&info.values, dollar0, str_len(dollar0) + 1)) goto err ;
+    blah[2].var = 4 ; blah[2].value = info.values.len ; blah[2].n = argc ;
+    genalloc_catb(elsubst_t, &info.data, blah, 3) ;
+  }
+  for (; i < n ; i++)
+  {
+    elsubst_t blah ;
+    char fmt[UINT_FMT] ;
+    blah.var = info.vars.len ; blah.value = info.values.len ; blah.n = 1 ;
+    if (!stralloc_catb(&info.vars, fmt, uint_fmt(fmt, i+1)) || !stralloc_0(&info.vars)) goto err ;
+    if (!stralloc_catb(&info.values, i < argc ? argv[i] : "", i < argc ? str_len(argv[i]) + 1 : 1)) goto err ;
+    genalloc_append(elsubst_t, &info.data, &blah) ;
+  }
+  {
+    stralloc dst = STRALLOC_ZERO ;
+    int r = el_substitute(&dst, sa->s, sa->len, info.vars.s, info.values.s, genalloc_s(elsubst_t, &info.data), genalloc_len(elsubst_t, &info.data)) ;
+    if (r < 0) goto err ;
+    exlsn_free(&info) ;
+    stralloc_free(sa) ;
+    *sa = dst ;
+    return r ;
+  }
+
+ err:
+  exlsn_free(&info) ;
+  return -1 ;
+}
+
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  chargen_t *next ;
+  stralloc sa = STRALLOC_ZERO ;
+  stralloc modif = STRALLOC_ZERO ;
+  int nc ;
+  int flagstrict = -1 ;
+  unsigned int nmin = 0 ;
+  char const *dollar0 = argv[0] ;
+  unsigned int flagpushenv = 2 ;
+  PROG = "execlineb" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "pPqwWc:S:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'p' : flagpushenv = 1 ; break ;
+        case 'P' : flagpushenv = 0 ; break ;
+        case 'q' : flagstrict = 0 ; break ;
+        case 'w' : flagstrict = 1 ; break ;
+        case 'W' : flagstrict = 2 ; break ;
+        case 'c' : string = (unsigned char *)l.arg ; break ;
+        case 'S' :
+        {
+          if (!uint0_scan(l.arg, &nmin)) strerr_dieusage(100, USAGE) ;
+          flagpushenv = 3 ;
+          break ;
+        }
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (string) next = &nextinstring ;
+  else
+  {
+    if (!argv[0]) strerr_dieusage(100, USAGE) ;
+    initbuffer(argv[0]) ;
+    dollar0 = argv[0] ;
+    argv++ ; argc-- ;
+    next = &nextinbuffer ;
+  }
+
+  nc = lex(&sa, next) ;
+  switch (nc)
+  {
+    case -4: strerr_dief2x(100, "unmatched ", "}") ;
+    case -3: strerr_dief2x(100, "unmatched ", "{") ;
+    case -2: strerr_dief1x(100, "syntax error") ;
+    case -1: strerr_diefu1sys(111, "parse script") ;
+    case 0 : return 0 ;
+  }
+
+  if (flagstrict >= 0)
+  {
+    char fmt[UINT_FMT] ;
+    fmt[uint_fmt(fmt, (unsigned int)flagstrict)] = 0 ;
+    if (!env_addmodif(&modif, "EXECLINE_STRICT", flagstrict ? fmt : 0)) goto errenv ;
+  }
+
+  if (flagpushenv == 3)
+  {
+    flagpushenv = 0 ;
+    if (flagstrict && ((unsigned int)argc < nmin))
+    {
+      char fmtn[UINT_FMT] ;
+      char fmta[UINT_FMT] ;
+      fmtn[uint_fmt(fmtn, nmin)] = 0 ;
+      fmta[uint_fmt(fmta, argc)] = 0 ;
+      if (flagstrict > 1)
+        strerr_dief4x(100, "too few arguments: expecting at least ", fmtn, " but got ", fmta) ;
+      else
+        strerr_warnw4x("too few arguments: expecting at least ", fmtn, " but got ", fmta) ;
+    }
+    nc = myexlp(&sa, argv, argc, nmin, dollar0) ;
+    if (nc < 0) strerr_diefu1sys(111, "substitute positional parameters") ;
+  }
+  else if (flagpushenv)
+  {
+    char fmt[UINT_FMT] ;
+    register unsigned int i = 0 ;
+    fmt[uint_fmt(fmt, argc)] = 0 ;
+    if (!env_addmodif(&modif, "#", fmt)) goto errenv ;
+    if (!env_addmodif(&modif, "0", dollar0)) goto errenv ;
+    for (; i < (unsigned int)argc ; i++)
+    {
+      fmt[uint_fmt(fmt, i+1)] = 0 ;
+      if (!env_addmodif(&modif, fmt, argv[i])) goto errenv ;
+    }
+  }
+
+  {
+    char const *v[nc+1] ;
+    if (!env_make(v, nc, sa.s, sa.len)) strerr_diefu1sys(111, "make argv") ;
+    v[nc] = 0 ;
+
+    if (flagpushenv > 1)
+    {
+      static char const *const list[11] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "#" } ;
+      unsigned int envlen = env_len(envp) ;
+      char const *w[envlen] ;
+      if (el_pushenv(&satmp, envp, envlen, list, 11) < 0) goto errenv ;
+      if (!env_make(w, envlen, satmp.s, satmp.len)) goto errenv ;
+      pathexec_r(v, w, envlen, modif.s, modif.len) ;
+      stralloc_free(&satmp) ;
+    }
+    else if (modif.len)
+      pathexec_r(v, envp, env_len(envp), modif.s, modif.len) ;
+    else
+      pathexec_run(v[0], v, envp) ;
+  }
+  stralloc_free(&modif) ;
+  strerr_dieexec(111, sa.s) ;
+errenv:
+  strerr_diefu1sys(111, "update environment") ;  
+}
diff --git a/src/execline/exit.c b/src/execline/exit.c
new file mode 100644
index 0000000..d9c6c20
--- /dev/null
+++ b/src/execline/exit.c
@@ -0,0 +1,15 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include <skalibs/uint.h>
+
+#define USAGE "exit [ exitcode ]"
+
+int main (int argc, char const *const *argv)
+{
+  unsigned int e ;
+  PROG = "exit" ;
+  if (argc < 2) return 0 ;
+  if (!uint0_scan(argv[1], &e)) strerr_dieusage(100, USAGE) ;
+  return (int)e ;
+}
diff --git a/src/execline/export.c b/src/execline/export.c
new file mode 100644
index 0000000..e7a7bbe
--- /dev/null
+++ b/src/execline/export.c
@@ -0,0 +1,27 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "export variable value prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int len1 ;
+  PROG = "export" ;
+  if (argc < 4) strerr_dieusage(100, USAGE) ;
+  len1 = str_len(argv[1]) ;
+  if (byte_chr(argv[1], len1, '=') < len1)
+    strerr_dief2x(100, "invalid variable name: ", argv[1]) ;
+  {
+    unsigned int len2 = str_len(argv[2]) ;
+    char fmt[len1 + len2 + 2] ;
+    byte_copy(fmt, len1, argv[1]) ;
+    fmt[len1] = '=' ;
+    byte_copy(fmt + len1 + 1, len2 + 1, argv[2]) ;
+    pathexec_r(argv+3, envp, env_len(envp), fmt, len1 + len2 + 2) ;
+  }
+  strerr_dieexec(111, argv[3]) ;
+}
diff --git a/src/execline/fdblock.c b/src/execline/fdblock.c
new file mode 100644
index 0000000..486591e
--- /dev/null
+++ b/src/execline/fdblock.c
@@ -0,0 +1,34 @@
+/* ISC license. */
+
+#include <skalibs/uint.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "fdblock [ -n ] fd prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int fd ;
+  int block = 1 ;
+  PROG = "fdblock" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "n", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n' : block = 0 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if ((argc < 2) || !uint0_scan(argv[0], &fd)) strerr_dieusage(100, USAGE) ;
+  if ((block ? ndelay_off(fd) : ndelay_on(fd)) < 0)
+    strerr_diefu1sys(111, block ? "ndelay_off" : "ndelay_on") ;
+  pathexec_run(argv[1], argv+1, envp) ;
+  strerr_dieexec(111, argv[1]) ;
+}
diff --git a/src/execline/fdclose.c b/src/execline/fdclose.c
new file mode 100644
index 0000000..33b5941
--- /dev/null
+++ b/src/execline/fdclose.c
@@ -0,0 +1,17 @@
+/* ISC license. */
+
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "fdclose fd prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int fd ;
+  PROG = "fdclose" ;
+  if ((argc < 3) || !uint0_scan(argv[1], &fd)) strerr_dieusage(100, USAGE) ;
+  fd_close((int)fd) ;
+  pathexec_run(argv[2], argv+2, envp) ;
+  strerr_dieexec(111, argv[2]) ;
+}
diff --git a/src/execline/fdmove.c b/src/execline/fdmove.c
new file mode 100644
index 0000000..8b7417e
--- /dev/null
+++ b/src/execline/fdmove.c
@@ -0,0 +1,35 @@
+/* ISC license. */
+
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "fdmove [ -c ] to from prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int to, from ;
+  int flagcopy = 0 ;
+  PROG = "fdmove" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "c", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'c' : flagcopy = 1 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if ((argc < 3) || !uint0_scan(argv[0], &to) || !uint0_scan(argv[1], &from))
+    strerr_dieusage(100, USAGE) ;
+  if ((flagcopy ? fd_copy((int)to, (int)from) : fd_move((int)to, (int)from)) == -1)
+    strerr_diefu4sys(111, "move fd ", argv[1], " to fd ", argv[0]) ;
+  pathexec_run(argv[2], argv+2, envp) ;
+  strerr_dieexec(111, argv[2]) ;
+}
diff --git a/src/execline/fdreserve.c b/src/execline/fdreserve.c
new file mode 100644
index 0000000..2df721a
--- /dev/null
+++ b/src/execline/fdreserve.c
@@ -0,0 +1,64 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <sys/resource.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "fdreserve n prog..."
+
+#define MAXFDS 1024
+
+unsigned int doit (char *modif, unsigned int i, int fd)
+{
+  register unsigned int pos = 2 ;
+  modif[0] = 'F' ; modif[1] = 'D' ;
+  pos += uint_fmt(modif + pos, i) ;
+  modif[pos++] = '=' ;
+  pos += uint_fmt(modif + pos, (unsigned int)fd) ;
+  modif[pos++] = 0 ;
+  return pos ;
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int n ;
+  PROG = "fdreserve" ;
+  if ((argc < 3) || !uint0_scan(argv[1], &n))
+    strerr_dieusage(100, USAGE) ;
+  {
+    struct rlimit lim ;
+    if (getrlimit(RLIMIT_NOFILE, &lim) < 0) strerr_diefu1sys(111, "getrlimit") ;
+    if (n > lim.rlim_cur) strerr_dief1x(100, "too many requested fds") ;
+  }
+  {
+    char modif[12 * n] ;  /* enough for n times "FDaaaa=bbbb\0" */
+    unsigned int j = 0 ;
+    {
+      int fd[n >> 1][2] ;
+      register unsigned int i = 0 ;
+      for (; i < (n>>1) ; i++)
+        if (pipe(fd[i]) < 0)
+          strerr_diefu1sys(111, "reserve fds") ;
+      if (n & 1)
+      {
+        register int lastfd = open_read("/dev/null") ;
+        if (lastfd < 0)
+          strerr_diefu1sys(111, "reserve last fd") ;
+        fd_close(lastfd) ;
+        j += doit(modif + j, n-1, lastfd) ;
+      }
+      for (i = 0 ; i < (n>>1) ; i++)
+      {
+        fd_close(fd[i][0]) ;
+        fd_close(fd[i][1]) ;
+        j += doit(modif + j, i<<1, fd[i][0]) ;
+        j += doit(modif + j, (i<<1)|1, fd[i][1]) ;
+      }
+    }
+    pathexec_r(argv+2, envp, env_len(envp), modif, j) ;
+  }
+  strerr_dieexec(111, argv[2]) ;
+}
diff --git a/src/execline/forbacktickx.c b/src/execline/forbacktickx.c
new file mode 100644
index 0000000..f7b1460
--- /dev/null
+++ b/src/execline/forbacktickx.c
@@ -0,0 +1,145 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/fmtscan.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/netstring.h>
+#include <skalibs/ushort.h>
+#include <execline/config.h>
+#include <execline/execline.h>
+
+#define USAGE "forbacktickx [ -p | -x breakcode,breakcode,... ] [ -n ] [ -C | -c ] [ -0 | -d delim ] var { backtickcmd... } command..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+static int isbreak (unsigned short *tab, unsigned int n, int code)
+{
+  register unsigned int i = 0 ;
+  for (; i < n ; i++) if ((unsigned short)code == tab[i]) break ;
+  return i < n ;
+}
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  genalloc pids = GENALLOC_ZERO ; /* pid_t */
+  char const *delim = " \n\r\t" ;
+  unsigned int delimlen = 4 ;
+  char const *x ;
+  int argc1 ;
+  unsigned short breakcodes[256] ;
+  unsigned int nbc = 0 ;
+  int crunch = 0, chomp = 0 ;
+  PROG = "forbacktickx" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "epnCc0d:x:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+	case 'e' : break ; /* compat */
+        case 'p' :
+        {
+          if (!genalloc_ready(pid_t, &pids, 2))
+            strerr_diefu1sys(111, "genalloc_ready") ;
+          break ;
+        }
+        case 'n' : chomp = 1 ; break ;
+        case 'C' : crunch = 1 ; break ;
+        case 'c' : crunch = 0 ; break ;
+        case '0' : delim = "" ; delimlen = 1 ; break ;
+        case 'd' : delim = l.arg ; delimlen = str_len(delim) ; break ;
+        case 'x' :
+          if (!ushort_scanlist(breakcodes, 256, l.arg, &nbc)) dieusage() ;
+          break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (argc < 2) dieusage() ;
+  x = argv[0] ; if (!*x) dieusage() ;
+  argv++ ; argc-- ;
+  argc1 = el_semicolon(argv) ;
+  if (!argc1) strerr_dief1x(100, "empty block") ;
+  if (argc1 >= argc) strerr_dief1x(100, "unterminated block") ;
+  argv[argc1] = 0 ;
+  {
+    int fd ;
+    pid_t pidw = el_spawn1(argv[0], argv, envp, &fd, 1) ;
+    if (!pidw) strerr_diefu2sys(111, "spawn ", argv[0]) ;
+    {
+      char buf[BUFFER_INSIZE] ;
+      buffer b = BUFFER_INIT(&buffer_read, fd, buf, BUFFER_INSIZE) ;
+      stralloc modif = STRALLOC_ZERO ;
+      unsigned int envlen = env_len(envp) ;
+      unsigned int modifstart = str_len(x)+1 ;
+      char const *newenv[envlen + 2] ;
+      if (!stralloc_ready(&modif, modifstart+1))
+        strerr_diefu1sys(111, "stralloc_ready") ;
+      byte_copy(modif.s, modifstart-1, x) ;
+      modif.s[modifstart-1] = '=' ;
+      for (;;)
+      {
+        pid_t pid ;
+        modif.len = modifstart ;
+        if (delimlen)
+        {
+          register int r = skagetlnsep(&b, &modif, delim, delimlen) ;
+          if (!r) break ;
+          else if (r < 0)
+          {
+            if (errno != EPIPE) strerr_diefu1sys(111, "skagetlnsep") ;
+            if (chomp) break ;
+          }
+          else modif.len-- ;
+          if ((modif.len == modifstart) && crunch) continue ;
+        }
+        else
+        {
+          unsigned int unread = 0 ;
+          if (netstring_get(&b, &modif, &unread) <= 0)
+          {
+            if (netstring_okeof(&b, unread)) break ;
+            else strerr_diefu1sys(111, "netstring_get") ;
+          }
+        }
+        if (!stralloc_0(&modif)) strerr_diefu1sys(111, "stralloc_0") ;
+        if (!env_merge(newenv, envlen+2, envp, envlen, modif.s, modif.len))
+          strerr_diefu1sys(111, "merge environment") ;
+        pid = el_spawn0(argv[argc1 + 1], argv + argc1 + 1, newenv) ;
+        if (!pid) strerr_diefu2sys(111, "spawn ", argv[argc1+1]) ;
+        if (pids.s)
+        {
+          if (!genalloc_append(pid_t, &pids, &pid))
+            strerr_diefu1sys(111, "genalloc_append") ;
+        }
+        else
+        {
+          int wstat ;
+          if (wait_pid(pid, &wstat) < 0)
+            strerr_diefu2sys(111, "wait for ", argv[argc1 + 1]) ;
+          if (isbreak(breakcodes, nbc, wait_status(wstat)))
+            return wait_status(wstat) ;
+        }
+      }
+      stralloc_free(&modif) ;
+    }
+    fd_close(fd) ;
+    if (!genalloc_append(pid_t, &pids, &pidw))
+      strerr_diefu1sys(111, "genalloc_append") ;
+  }
+  if (!waitn(genalloc_s(pid_t, &pids), genalloc_len(pid_t, &pids)))
+    strerr_diefu1sys(111, "waitn") ;
+  /* genalloc_free(pid_t, &pids) ; */
+  return 0 ;
+}
diff --git a/src/execline/foreground.c b/src/execline/foreground.c
new file mode 100644
index 0000000..8164dcb
--- /dev/null
+++ b/src/execline/foreground.c
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include <execline/execline.h>
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  int argc1 ;
+  PROG = "foreground" ;
+  argc1 = el_semicolon(++argv) ;
+  if (argc1 >= --argc) strerr_dief1x(100, "unterminated block") ;
+  argv[argc1] = 0 ;
+  el_execsequence(argv, argv+argc1+1, envp) ;
+}
diff --git a/src/execline/forx.c b/src/execline/forx.c
new file mode 100644
index 0000000..25d6d44
--- /dev/null
+++ b/src/execline/forx.c
@@ -0,0 +1,91 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/ushort.h>
+#include <execline/config.h>
+#include <execline/execline.h>
+
+#define USAGE "forx [ -p | -x breakcode,breakcode,... ] var { values... } command..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+static int isbreak (unsigned short *tab, unsigned int n, int code)
+{
+  register unsigned int i = 0 ;
+  for (; i < n ; i++) if ((unsigned short)code == tab[i]) break ;
+  return i < n ;
+}
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  char const *x ;
+  int argc1 ;
+  unsigned short breakcodes[256] ;
+  unsigned int nbc = 0 ;
+  int flagpar = 0 ;
+  PROG = "forx" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "epx:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'e' : break ; /* compat */
+        case 'p' : flagpar = 1 ; break ;
+        case 'x' :
+          if (!ushort_scanlist(breakcodes, 256, l.arg, &nbc)) dieusage() ;
+          break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+
+  if (argc < 2) dieusage() ;
+  x = argv[0] ; if (!*x) dieusage() ;
+  argv++ ; argc-- ;
+  argc1 = el_semicolon(argv) ;
+  if (argc1 >= argc) strerr_dief1x(100, "unterminated block") ;
+  if (!argc1 || (argc1 + 1 == argc)) return 0 ;
+  {
+    unsigned int envlen = env_len(envp) ;
+    unsigned int varlen = str_len(x) ;
+    unsigned int i = 0 ;
+    pid_t pids[flagpar ? argc1 : 1] ;
+    char const *newenv[envlen + 2] ;
+
+    for (; i < (unsigned int)argc1 ; i++)
+    {
+      pid_t pid ;
+      unsigned int vallen = str_len(argv[i]) ;
+      char modif[varlen + vallen + 2] ;
+      byte_copy(modif, varlen, x) ;
+      modif[varlen] = '=' ;
+      byte_copy(modif + varlen + 1, vallen, argv[i]) ;
+      modif[varlen + vallen + 1] = 0 ;
+      if (!env_merge(newenv, envlen + 2, envp, envlen, modif, varlen + vallen + 2))
+        strerr_diefu1sys(111, "build new environment") ;
+      pid = el_spawn0(argv[argc1+1], argv + argc1 + 1, newenv) ;
+      if (!pid) strerr_diefu2sys(111, "spawn ", argv[argc1+1]) ;
+      if (flagpar) pids[i] = pid ;
+      else
+      {
+        int wstat ;
+        if (wait_pid(pid, &wstat) == -1)
+          strerr_diefu2sys(111, "wait for ", argv[argc1+1]) ;
+        if (isbreak(breakcodes, nbc, wait_status(wstat)))
+          return wait_status(wstat) ;
+      }
+    }
+    if (flagpar)
+      if (!waitn(pids, argc1)) strerr_diefu1sys(111, "waitn") ;
+  }
+  return 0 ;
+}
diff --git a/src/execline/getpid.c b/src/execline/getpid.c
new file mode 100644
index 0000000..03a517c
--- /dev/null
+++ b/src/execline/getpid.c
@@ -0,0 +1,29 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "getpid variable prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int len ;
+  PROG = "getpid" ;
+  if (argc < 3) strerr_dieusage(100, USAGE) ;
+  len = str_len(argv[1]) ;
+  if (byte_chr(argv[1], len, '=') < len)
+    strerr_dief2x(100, "invalid variable name: ", argv[1]) ;
+  {
+    char fmt[UINT_FMT + len + 2] ;
+    unsigned int i = len+1 ;
+    byte_copy(fmt, len, argv[1]) ;
+    fmt[len] = '=' ;
+    i += uint_fmt(fmt+i, getpid()) ; fmt[i++] = 0 ;
+    pathexec_r(argv+2, envp, env_len(envp), fmt, i) ;
+  }
+  strerr_dieexec(111, argv[2]) ;
+}
diff --git a/src/execline/heredoc.c b/src/execline/heredoc.c
new file mode 100644
index 0000000..899c2d8
--- /dev/null
+++ b/src/execline/heredoc.c
@@ -0,0 +1,59 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "heredoc [ -d ] fd string command..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  int df = 0 ;
+  PROG = "heredoc" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "d", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'd' : df = 1 ; break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (argc < 3) dieusage() ;
+  {
+    int fd[2] ;
+    unsigned int fdr ;
+    int pid ;
+    if (!uint0_scan(argv[0], &fdr)) strerr_dieusage(100, USAGE) ;
+    if (pipe(fd) < 0) strerr_diefu1sys(111, "pipe") ;
+    pid = df ? doublefork() : fork() ;
+    switch (pid)
+    {
+      case -1: strerr_diefu2sys(111, df ? "double" : "", "fork") ;
+      case 0:
+      {
+        unsigned int len = str_len(argv[1]) ;
+        PROG = "heredoc (child)" ;
+        fd_close(fd[0]) ;
+        if (allwrite(fd[1], argv[1], len) < len)
+          strerr_diefu1sys(111, "allwrite") ;
+        return 0 ;
+      }
+    }
+    fd_close(fd[1]) ;
+    if (fd_move((int)fdr, fd[0]) == -1)
+      strerr_diefu2sys(111, "read on fd ", argv[0]) ;
+  }
+  pathexec_run(argv[2], argv+2, envp) ;
+  strerr_dieexec(111, argv[2]) ;
+}
diff --git a/src/execline/homeof.c b/src/execline/homeof.c
new file mode 100644
index 0000000..40463da
--- /dev/null
+++ b/src/execline/homeof.c
@@ -0,0 +1,28 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <pwd.h>
+#include <errno.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+
+#define USAGE "homeof user"
+
+int main (int argc, char const *const *argv)
+{
+  struct passwd *pw ;
+  PROG = "homeof" ;
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  pw = getpwnam(argv[1]) ;
+  if (!pw)
+  {
+    if (errno)
+      strerr_diefu2sys(111, "get passwd entry for ", argv[1]) ;
+    else
+      strerr_diefu3x(111, "get passwd entry for ", argv[1], ": no such user") ;
+  }
+  if ((buffer_puts(buffer_1small, pw->pw_dir) < 0)
+   || (buffer_putflush(buffer_1small, "\n", 1) < 0))
+    strerr_diefu1sys(111, "write to stdout") ;
+  return 0 ;
+}
diff --git a/src/execline/if.c b/src/execline/if.c
new file mode 100644
index 0000000..9d0b4b4
--- /dev/null
+++ b/src/execline/if.c
@@ -0,0 +1,54 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/ushort.h>
+#include <execline/execline.h>
+
+#define USAGE "if [ -n ] [ -X ] [ -t | -x exitcode ] { command... }"
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  int argc1, wstat ;
+  pid_t pid ;
+  unsigned int not = 0 ;
+  unsigned short e = 1 ;
+  int flagnormalcrash = 0 ;
+  PROG = "if" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "nXtx:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n' : not = 1 ; break ;
+        case 'X' : flagnormalcrash = 1 ; break ;
+        case 't' : e = 0 ; break ;
+        case 'x' : if (ushort_scan(l.arg, &e)) break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  argc1 = el_semicolon(argv) ;
+  if (argc1 >= argc) strerr_dief1x(100, "unterminated block") ;
+  argv[argc1] = 0 ;
+  pid = el_spawn0(argv[0], argv, envp) ;
+  if (!pid) strerr_diefu2sys(111, "spawn ", argv[0]) ;
+  if (wait_pid(pid, &wstat) == -1) strerr_diefu1sys(111, "wait_pid") ;
+  if (!flagnormalcrash && WIFSIGNALED(wstat))
+  {
+    char fmt[UINT_FMT] ;
+    fmt[uint_fmt(fmt, WTERMSIG(wstat))] = 0 ;
+    strerr_dief2x(1, "child crashed with signal ", fmt) ;
+  }
+  if (not == !wait_status(wstat)) return (int)e ;
+  pathexec0_run(argv+argc1+1, envp) ;
+  strerr_dieexec(111, argv[argc1+1]) ;
+}
diff --git a/src/execline/ifelse.c b/src/execline/ifelse.c
new file mode 100644
index 0000000..6d8801e
--- /dev/null
+++ b/src/execline/ifelse.c
@@ -0,0 +1,54 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/uint.h>
+#include <execline/execline.h>
+
+#define USAGE "ifelse [ -n ] [ -X ] { command-if } { command-then... }"
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  int argc1, argc2, wstat ;
+  int not = 0, flagnormalcrash = 0 ;
+  pid_t pid ;
+  PROG = "ifelse" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "nX", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n' : not = 1 ; break ;
+        case 'X' : flagnormalcrash = 1 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  argc1 = el_semicolon(argv) ;
+  if (argc1 >= argc) strerr_dief1x(100, "unterminated if block") ;
+  if (argc1 + 1 == argc) strerr_dief1x(100, "then block required") ;
+  argc2 = el_semicolon(argv + argc1 + 1) ;
+  if (argc1 + argc2 + 1 >= argc) strerr_dief1x(100, "unterminated then block") ;
+  argv[argc1] = 0 ;
+  pid = el_spawn0(argv[0], argv, envp) ;
+  if (!pid) strerr_diefu2sys(111, "spawn ", argv[0]) ;
+  if (wait_pid(pid, &wstat) == -1)
+    strerr_diefu2sys(111, "wait for ", argv[0]) ;
+  argv += ++argc1 ;
+  if (!flagnormalcrash && WIFSIGNALED(wstat))
+  {
+    char fmt[UINT_FMT] ;
+    fmt[uint_fmt(fmt, WSTOPSIG(wstat))] = 0 ;
+    strerr_dief2x(1, "child crashed with signal ", fmt) ;
+  }
+  if (not != !wait_status(wstat)) argv[argc2] = 0 ; else argv += argc2+1 ;
+  pathexec0_run(argv, envp) ;
+  strerr_dieexec(111, *argv) ;
+}
diff --git a/src/execline/ifte.c b/src/execline/ifte.c
new file mode 100644
index 0000000..3fe021e
--- /dev/null
+++ b/src/execline/ifte.c
@@ -0,0 +1,59 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+#include <execline/execline.h>
+
+#define USAGE "ifte [ -X ] [ -n ] { command-then... } { command-else... } command-if..."
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  int argc1, argc2, wstat ;
+  int not = 0, flagnormalcrash = 0 ;
+  pid_t pid ;
+  PROG = "ifte" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "Xn", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'X' : flagnormalcrash = 1 ; break ;
+        case 'n' : not = 1 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  argc1 = el_semicolon(argv) ;
+  if (argc1 >= argc) strerr_dief1x(100, "unterminated then block") ;
+  if (argc1 + 1 == argc) strerr_dief1x(100, "else block required") ;
+  argc2 = el_semicolon(argv + argc1 + 1) ;
+  if (argc1 + argc2 + 1 >= argc) strerr_dief1x(100, "unterminated else block") ;
+  if (argc1 + argc2 + 2 >= argc) strerr_dief1x(100, "empty command-if") ;
+
+  pid = el_spawn0(argv[argc1 + argc2 + 2], argv + argc1 + argc2 + 2, envp) ;
+  if (!pid) strerr_diefu2sys(111, "spawn ", argv[argc1 + argc2 + 2]) ;
+  if (wait_pid(pid, &wstat) == -1)
+    strerr_diefu2sys(111, "wait for ", argv[argc1 + argc2 + 2]) ;
+  if (!flagnormalcrash && WIFSIGNALED(wstat))
+  {
+    char fmt[UINT_FMT] ;
+    fmt[uint_fmt(fmt, WTERMSIG(wstat))] = 0 ;
+    strerr_dief2x(1, "child crashed with signal ", fmt) ;
+  }
+  if (not != !wait_status(wstat)) argv[argc1] = 0 ;
+  else
+  {
+    argv += argc1 + 1 ;
+    argv[argc2] = 0 ;
+  }
+  pathexec0_run(argv, envp) ;
+  strerr_dieexec(111, *argv) ;
+}
diff --git a/src/execline/ifthenelse.c b/src/execline/ifthenelse.c
new file mode 100644
index 0000000..f9fb9d7
--- /dev/null
+++ b/src/execline/ifthenelse.c
@@ -0,0 +1,77 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+#include <execline/execline.h>
+
+#define USAGE "ifthenelse [ -X ] { command-if... } { command-then... } { command-else... } [ remainder... ]"
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  int argc1, argc2, argc3, wstat ;
+  int magicscope = 0, flagnormalcrash = 0 ;
+  pid_t pid ;
+  PROG = "ifthenelse" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "Xs", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'X' : flagnormalcrash = 1 ; break ;
+        case 's' : magicscope = 1 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  argc1 = el_semicolon(argv) ;
+  if (argc1 >= argc) strerr_dief1x(100, "unterminated if block") ;
+  if (argc1 + 1 == argc) strerr_dief1x(100, "then block required") ;
+  argc2 = el_semicolon(argv + argc1 + 1) ;
+  if (argc1 + argc2 + 1 >= argc) strerr_dief1x(100, "unterminated then block") ;
+  argc3 = el_semicolon(argv + argc1 + argc2 + 2) ;
+  if (argc1 + argc2 + argc3 + 2 >= argc)
+    strerr_dief1x(100, "unterminated else block") ;
+
+  argv[argc1] = 0 ;
+  pid = fork() ;
+  pid = el_spawn0(argv[0], argv, envp) ;
+  if (!pid) strerr_diefu2sys(111, "spawn ", argv[0]) ;
+  if (wait_pid(pid, &wstat) == -1)
+    strerr_diefu2sys(111, "wait for ", argv[0]) ;
+  argv += argc1 + 1 ;
+  {
+    char const *const *remainder = argv + argc2 + argc3 + 2 ;
+    if (!flagnormalcrash && WIFSIGNALED(wstat))
+    {
+      char fmt[UINT_FMT] ;
+      fmt[uint_fmt(fmt, WTERMSIG(wstat))] = 0 ;
+      strerr_dief2x(1, "child crashed with signal ", fmt) ;
+    }
+    if (wait_status(wstat))
+    {
+      argv += argc2 + 1 ;
+      argc2 = argc3 ;
+    }
+    if (magicscope)  /* undocumented voodoo - dangerous and powerful */
+    {
+      register unsigned int i = 0 ;
+      for (; remainder[i] ; i++) argv[argc2+i] = remainder[i] ;
+      argv[argc2+i] = 0 ;
+      pathexec0_run(argv, envp) ;
+      strerr_dieexec(111, argv[0]) ;
+    }
+    else
+    {
+      argv[argc2] = 0 ;
+      el_execsequence(argv, remainder, envp) ;
+    }
+  }
+}
diff --git a/src/execline/import.c b/src/execline/import.c
new file mode 100644
index 0000000..5a95886
--- /dev/null
+++ b/src/execline/import.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include "exlsn.h"
+
+#define USAGE "import [ -i | -D default ] [ -n ] [ -s ] [ -C | -c ] [ -d delim ] var prog..."
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  PROG = "import" ;
+  exlsn_main(argc, argv, envp, &exlsn_import, USAGE) ;
+}
diff --git a/src/execline/importas.c b/src/execline/importas.c
new file mode 100644
index 0000000..026efce
--- /dev/null
+++ b/src/execline/importas.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include "exlsn.h"
+
+#define USAGE "importas [ -i | -D default ] [ -n ] [ -s ] [ -C | -c ] [ -d delim ] key var prog..."
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  PROG = "importas" ;
+  exlsn_main(argc, argv, envp, &exlsn_importas, USAGE) ;
+}
diff --git a/src/execline/loopwhilex.c b/src/execline/loopwhilex.c
new file mode 100644
index 0000000..5cb6a4e
--- /dev/null
+++ b/src/execline/loopwhilex.c
@@ -0,0 +1,62 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/ushort.h>
+#include <skalibs/djbunix.h>
+#include <execline/execline.h>
+
+#define USAGE "loopwhilex [ -n ] [ -x exitcode,exitcode,... ] prog..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+static int isbreak (unsigned short *tab, unsigned int n, int code)
+{
+  register unsigned int i = 0 ;
+  for (; i < n ; i++) if ((unsigned short)code == tab[i]) break ;
+  return i < n ;
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  int wstat ;
+  int not = 0, cont = 1 ;
+  unsigned short breakcodes[256] ;
+  unsigned int nbc = 0 ;
+  PROG = "loopwhilex" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "nx:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n' : not = 1 ; break ;
+        case 'x' :
+          if (!ushort_scanlist(breakcodes, 256, l.arg, &nbc)) dieusage() ;
+          break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) dieusage() ;
+
+  if (!nbc)
+  {
+    breakcodes[0] = 0 ;
+    nbc = 1 ;
+    not = !not ;
+  }
+
+  while (cont)
+  {
+    pid_t pid = el_spawn0(argv[0], argv, envp) ;
+    if (!pid) strerr_diefu2sys(111, "spawn ", argv[0]) ;
+    if (wait_pid(pid, &wstat) < 0) strerr_diefu1sys(111, "wait_pid") ;
+    cont = not == isbreak(breakcodes, nbc, wait_status(wstat)) ;
+  }
+  return WIFSIGNALED(wstat) ? WTERMSIG(wstat) : 0 ;
+}
diff --git a/src/execline/multidefine.c b/src/execline/multidefine.c
new file mode 100644
index 0000000..9665418
--- /dev/null
+++ b/src/execline/multidefine.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include "exlsn.h"
+
+#define USAGE "multidefine [ -0 ] [ -r ] [ -n ] [ -C | -c ] [ -d delim ] value { vars... } prog..."
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  PROG = "multidefine" ;
+  exlsn_main(argc, argv, envp, &exlsn_multidefine, USAGE) ;
+}
diff --git a/src/execline/multisubstitute.c b/src/execline/multisubstitute.c
new file mode 100644
index 0000000..acee42f
--- /dev/null
+++ b/src/execline/multisubstitute.c
@@ -0,0 +1,64 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+#include <execline/execline.h>
+#include "exlsn.h"
+
+#define USAGE "see http://skarnet.org/software/execline/multisubstitute.html"
+
+static char const *const commands[8] =
+{
+  "define",
+  "importas",
+  "import",
+  "elglob",
+  "elgetpositionals",
+  "multidefine",
+  0
+} ;
+
+static exlsnfunc_t *const functions[8] =
+{
+  &exlsn_define,
+  &exlsn_importas,
+  &exlsn_import,
+  &exlsn_elglob,
+  &exlsn_exlp,
+  &exlsn_multidefine,
+  0
+} ;
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  exlsn_t info = EXLSN_ZERO ;
+  int argc1 ;
+  PROG = "multisubstitute" ;
+  if (!--argc) strerr_dieusage(100, USAGE) ;
+
+ /* Read a block containing directives */
+  argc1 = el_semicolon(++argv) ;
+  if (argc1 >= argc) strerr_dief1x(100, "unterminated block") ;
+  if (argc1 + 1 == argc) strerr_dieusage(100, USAGE) ;
+
+ /* Parse args and update the substitution info */
+  while (argc1)
+  {
+    int n ;
+    unsigned int i = 0 ;
+    for (; commands[i] ; i++) if (!str_diff(*argv, commands[i])) break ;
+    if (!commands[i]) strerr_dief3x(100, "syntax error: unrecognized", " directive ", *argv) ;
+    n = (*(functions[i]))(argc1, argv, envp, &info) ;
+    if (n < 0) switch (n)
+    {
+      case -3 : strerr_dief3x(100, "syntax error at", " directive ", commands[i]) ;
+      case -2 : strerr_dief3x(100, "wrong key for", " directive ", commands[i]) ;
+      case -1 : strerr_diefu3sys(111, "run", " directive ", commands[i]) ;
+      default : strerr_dief3x(111, "unknown error with", " directive ", commands[i]) ;
+    }
+    argv += n ; argc1 -= n ; argc -= n ;
+  }
+
+ /* Perform the substitution and exec */
+  el_substandrun(argc-1, argv+1, envp, &info) ;
+}
diff --git a/src/execline/pipeline.c b/src/execline/pipeline.c
new file mode 100644
index 0000000..42a230b
--- /dev/null
+++ b/src/execline/pipeline.c
@@ -0,0 +1,85 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <unistd.h>
+#ifdef EXECLINE_OLD_VARNAMES
+#include <skalibs/bytestr.h>
+#endif
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint64.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+#include <execline/execline.h>
+
+#define USAGE "pipeline [ -d ] [ -r | -w ] { command... } command..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  int df = 0, w = 0 ;
+  PROG = "pipeline" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "drw", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'd' : df = 1 ; break ;
+        case 'r' : w = 0 ; break ;
+        case 'w' : w = 1 ; break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  {
+    pid_t pid ;
+    int fd ;
+    int argc1 = el_semicolon(argv) ;
+    if (argc1 >= argc) strerr_dief1x(100, "unterminated block") ;
+    if (argc1 + 1 == argc) strerr_dief1x(100, "empty remainder") ;
+    argv[argc1] = 0 ;
+    if (df)
+    {
+      int p[2] ;
+      if (pipe(p) < 0) strerr_diefu1sys(111, "create pipe") ;
+      pid = doublefork() ;
+      switch (pid)
+      {
+        case -1: strerr_diefu1sys(111, "doublefork") ;
+        case 0:
+          PROG = "pipeline (grandchild)" ;
+          fd_close(p[w]) ;
+          if (fd_move(!w, p[!w]) < 0) strerr_diefu1sys(111, "fd_move") ;
+          pathexec0_run(argv, envp) ;
+          strerr_dieexec(127, argv[0]) ;
+      }
+      fd_close(p[!w]) ;
+      fd = p[w] ;
+    }
+    else
+    {
+      pid = el_spawn1(argv[0], argv, envp, &fd, !w) ;
+      if (!pid) strerr_diefu2sys(111, "spawn ", argv[0]) ;
+    }
+    if (fd_move(w, fd) < 0) strerr_diefu1sys(111, "fd_move") ;
+    {
+#ifdef EXECLINE_OLD_VARNAMES
+      char fmt[UINT64_FMT * 2 + 10] = "!=" ;
+#else
+      char fmt[UINT64_FMT + 2] = "!=" ;
+#endif
+      register unsigned int i = 2 ;
+      i += uint64_fmt(fmt+i, (uint64)pid) ; fmt[i++] = 0 ;
+#ifdef EXECLINE_OLD_VARNAMES
+      byte_copy(fmt+i, 8, "LASTPID=") ; i += 8 ;
+      i += uint64_fmt(fmt+i, (uint64)pid) ; fmt[i++] = 0 ;
+#endif
+      pathexec_r(argv + argc1 + 1, envp, env_len(envp), fmt, i) ;
+    }
+    strerr_dieexec(111, argv[argc1 + 1]) ;
+  }
+}
diff --git a/src/execline/piperw.c b/src/execline/piperw.c
new file mode 100644
index 0000000..9d24947
--- /dev/null
+++ b/src/execline/piperw.c
@@ -0,0 +1,29 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "piperw fdr fdw prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  int fdr, fdw ;
+  int p[2] ;
+  PROG = "piperw" ;
+  if ((argc < 4)
+   || !uint0_scan(argv[1], (unsigned int *)&fdr)
+   || !uint0_scan(argv[2], (unsigned int *)&fdw)
+   || (fdr == fdw))
+    strerr_dieusage(100, USAGE) ;
+  if (pipe(p) == -1)
+    strerr_diefu1sys(111, "create pipe") ;
+  if (p[1] == fdr) p[1] = dup(p[1]) ;
+  if ((p[1] == -1)
+   || (fd_move(fdr, p[0]) == -1)
+   || (fd_move(fdw, p[1]) == -1))
+    strerr_diefu1sys(111, "move fds") ;
+  pathexec_run(argv[3], argv+3, envp) ;
+  strerr_dieexec(111, argv[3]) ;
+}
diff --git a/src/execline/redirfd.c b/src/execline/redirfd.c
new file mode 100644
index 0000000..d9dca14
--- /dev/null
+++ b/src/execline/redirfd.c
@@ -0,0 +1,69 @@
+/* ISC license. */
+
+#include <fcntl.h>
+#include <errno.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "redirfd -[ r | w | u | a | c | x ] [ -n ] [ -b ] fd file prog..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  int fd, fd2 ;
+  unsigned int flags = 0 ;
+  int what = -1 ;
+  int changemode = 0 ;
+  PROG = "redirfd" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "rwuacxnb", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'r' : what = O_RDONLY ; flags &= ~(O_APPEND|O_CREAT|O_TRUNC|O_EXCL) ; break ;
+        case 'w' : what = O_WRONLY ; flags |= O_CREAT|O_TRUNC ; flags &= ~(O_APPEND|O_EXCL) ; break ;
+        case 'u' : what = O_RDWR ; flags &= ~(O_APPEND|O_CREAT|O_TRUNC|O_EXCL) ; break ;
+        case 'a' : what = O_WRONLY ; flags |= O_CREAT|O_APPEND ; flags &= ~(O_TRUNC|O_EXCL) ; break ;
+        case 'c' : what = O_WRONLY ; flags |= O_APPEND ; flags &= ~(O_CREAT|O_TRUNC|O_EXCL) ; break ;
+        case 'x' : what = O_WRONLY ; flags |= O_CREAT|O_EXCL ; flags &= ~(O_APPEND|O_TRUNC) ; break ;
+        case 'n' : flags |= O_NONBLOCK ; break ;
+        case 'b' : changemode = 1 ; break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if ((argc < 3) || (what == -1)) dieusage() ;
+  if (!uint0_scan(argv[0], (unsigned int *)&fd)) dieusage() ;
+  flags |= what ;
+  fd2 = open3(argv[1], flags, 0666) ;
+  if ((fd2 == -1) && (what == O_WRONLY) && (errno == ENXIO))
+  {
+    register int e ;
+    int fdr = open_read(argv[1]) ;
+    if (fdr == -1) strerr_diefu2sys(111, "open_read ", argv[1]) ;
+    fd2 = open3(argv[1], flags, 0666) ;
+    e = errno ;
+    fd_close(fdr) ;
+    errno = e ;
+  }
+  if (fd2 == -1) strerr_diefu2sys(111, "open ", argv[1]) ;
+  if (fd_move(fd, fd2) == -1)
+  {
+    char fmt[UINT_FMT] ;
+    fmt[uint_fmt(fmt, fd2)] = 0 ;
+    strerr_diefu4sys(111, "move fd ", fmt, " to fd ", argv[0]) ;
+  }
+  if (changemode)
+  {
+    if (((flags & O_NONBLOCK) ? ndelay_off(fd) : ndelay_on(fd)) < 0)
+      strerr_diefu1sys(111, "change blocking mode") ;
+  }
+  pathexec_run(argv[2], argv+2, envp) ;
+  strerr_dieexec(111, argv[2]) ;
+}
diff --git a/src/execline/runblock.c b/src/execline/runblock.c
new file mode 100644
index 0000000..a654023
--- /dev/null
+++ b/src/execline/runblock.c
@@ -0,0 +1,149 @@
+/* ISC license. */
+
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+#include <execline/execline.h>
+
+#define USAGE "runblock [ -P ] [ -n argshift ] [ -r ] n"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  genalloc v = GENALLOC_ZERO ; /* array of char const * */
+  unsigned int n, sharp ;
+  unsigned int m = 0 ;
+  unsigned int strict = el_getstrict() ;
+  int flagnopop = 0, flagr = 0 ;
+  PROG = "runblock" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "Prn:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'P' : flagnopop = 1 ; break ;
+        case 'r' : flagr = 1 ; break ;
+        case 'n' : if (!uint0_scan(l.arg, &m)) dieusage() ; break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (argc != 1) dieusage() ;
+  if (!uint0_scan(argv[0], &n) || (!n && !flagr)) dieusage() ;
+
+  {
+    char const *x = env_get2(envp, "#") ;
+    if (!x) strerr_dienotset(100, "#") ;
+    if (!uint0_scan(x, &sharp)) strerr_dieinvalid(100, "#") ;
+  }
+
+ /* Skip n-1 blocks (n if flagr) */
+  {
+    register unsigned int i = 1 ;
+    for (; i < n + flagr ; i++)
+    {
+      unsigned int base = m ;
+      for (;;)
+      {
+        char const *x ;
+        char fmt[UINT_FMT] ;
+        if (++m > sharp) strerr_dief1x(100, "too few arguments") ;
+        fmt[uint_fmt(fmt, m)] = 0 ;
+        x = env_get2(envp, fmt) ;
+        if (!x) strerr_dienotset(100, fmt) ;
+        if ((x[0] == EXECLINE_BLOCK_END_CHAR) && (!EXECLINE_BLOCK_END_CHAR || !x[1])) break ;
+        if ((x[0] != EXECLINE_BLOCK_QUOTE_CHAR) && strict)
+        {
+          char fmtb[UINT_FMT] ;
+          char fmti[UINT_FMT] ;
+          fmti[uint_fmt(fmti, i)] = 0 ;
+          fmtb[uint_fmt(fmtb, m - base)] = 0 ;
+          if (strict == 1)
+            strerr_warnw6x("unquoted positional ", x, " at block ", fmti, " position ", fmtb) ;
+          else
+            strerr_dief6x(100, "unquoted positional ", x, " at block ", fmti, " position ", fmtb) ;
+        }
+      }
+    }
+  }
+
+  if (flagr)  /* put remainder envvars into v */
+  {
+    if (++m == sharp) return 0 ;
+    if (!genalloc_ready(char const *, &v, sharp - m + 2))
+      strerr_diefu1sys(111, "genalloc_ready") ;
+    for (; m <= sharp ; m++)
+    {
+      char const *x ;
+      char fmt[UINT_FMT] ;
+      fmt[uint_fmt(fmt, m)] = 0 ;
+      x = env_get2(envp, fmt) ;
+      if (!x) strerr_dienotset(100, fmt) ;
+      genalloc_append(char const *, &v, &x) ;
+    }
+  }
+  else  /* put envvars from nth block into v */
+  {
+    unsigned int base = m ;
+    for (;;)
+    {
+      char const *x ;
+      char fmt[UINT_FMT] ;
+      if (++m > sharp) strerr_dief1x(100, "too few arguments") ;
+      fmt[uint_fmt(fmt, m)] = 0 ;
+      x = env_get2(envp, fmt) ;
+      if (!x) strerr_dienotset(100, fmt) ;
+      if ((x[0] == EXECLINE_BLOCK_END_CHAR) && (!EXECLINE_BLOCK_END_CHAR || !x[1])) break ;
+      if (x[0] != EXECLINE_BLOCK_QUOTE_CHAR)
+      {
+        if (strict)
+        {
+          char fmtb[UINT_FMT] ;
+          char fmtn[UINT_FMT] ;
+          fmtn[uint_fmt(fmtn, n)] = 0 ;
+          fmtb[uint_fmt(fmtb, m - base)] = 0 ;
+          if (strict == 1)
+            strerr_warnw6x("unquoted positional ", x, " at block ", fmtn, " position ", fmtb) ;
+          else
+            strerr_dief6x(100, "unquoted positional ", x, " at block ", fmtn, " position ", fmtb) ;
+        }
+      }
+      else x++ ;
+      if (!genalloc_append(char const *, &v, &x)) strerr_diefu1sys(111, "envalloc_catb") ;
+    }
+  }
+
+  {
+    char const *z = 0 ;
+    if (!genalloc_append(char const *, &v, &z))
+      strerr_diefu1sys(111, "genalloc_append") ;
+  }
+
+  if (flagnopop)  /* exec now */
+    pathexec_run(genalloc_s(char const *, &v)[0], genalloc_s(char const *, &v), envp) ;
+  else  /* popenv, then exec */
+  {
+    char const *list[11] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "#" } ;
+    unsigned int envlen = env_len(envp) ;
+    register int popped = el_popenv(&satmp, envp, envlen, list, 11) ;
+    if (popped < 0) strerr_diefu1sys(111, "pop environment") ;
+    else
+    {
+      char const *w[envlen - popped + 1] ;
+      if (!env_make(w, envlen - popped, satmp.s, satmp.len))
+        strerr_diefu1sys(111, "env_make") ;
+      w[envlen - popped] = 0 ;
+      pathexec_run(genalloc_s(char const *, &v)[0], genalloc_s(char const *, &v), w) ;
+      stralloc_free(&satmp) ;
+    }
+  }
+  strerr_dieexec(111, genalloc_s(char const *, &v)[0]) ;
+}
diff --git a/src/execline/shift.c b/src/execline/shift.c
new file mode 100644
index 0000000..b09ba38
--- /dev/null
+++ b/src/execline/shift.c
@@ -0,0 +1,121 @@
+/* ISC license. */
+
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+#include <execline/execline.h>
+
+#define USAGE "shift [ -n arg# ] [ -b block# ] prog..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int strict = el_getstrict() ;
+  unsigned int b = 0 ;
+  unsigned int n = 0 ;
+  unsigned int sharp ;
+  unsigned int i = 0 ;
+  PROG = "shift" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "n:b:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n' :
+          if (!uint0_scan(l.arg, &n)) dieusage() ;
+          i = 1 ;
+          break ;
+        case 'b' :
+          if (!uint0_scan(l.arg, &b)) dieusage() ;
+          i = 1 ;
+          break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) dieusage() ;
+  if (i) i = 0 ; else n = 1 ;
+  {
+    char const *x = env_get2(envp, "#") ;
+    if (!x) strerr_dienotset(100, "#") ;
+    if (!uint0_scan(x, &sharp)) strerr_dieinvalid(100, "#") ;
+  }
+
+
+ /* Shift n args */
+
+  if (n > sharp)
+  {
+    if (strict)
+    {
+      char fmtn[UINT_FMT] ;
+      char fmtsharp[UINT_FMT] ;
+      fmtn[uint_fmt(fmtn, n)] = 0 ;
+      fmtsharp[uint_fmt(fmtsharp, sharp)] = 0 ;
+      if (strict == 1)
+        strerr_warnwu5x("shift", " ", fmtn, " arguments: got ", fmtsharp) ;
+      else
+        strerr_diefu5x(100, "shift", " ", fmtn, " arguments: got ", fmtsharp) ;
+    }
+    n = sharp ;
+  }
+
+
+ /* Shift b blocks */
+
+  for (; i < b ; i++)
+  {
+    for (;;)
+    {
+      char const *x ;
+      unsigned int base = n ;
+      char fmt[UINT_FMT] ;
+      fmt[uint_fmt(fmt, ++n)] = 0 ;
+      if (n > sharp)
+      {
+        char fmti[UINT_FMT] ;
+        fmti[uint_fmt(fmt, i)] = 0 ;
+        strerr_diefu6x(100, "shift", " block ", fmti, ": too few arguments (", fmt, ")") ;
+      }
+      x = env_get2(envp, fmt) ;
+      if (!x) strerr_dienotset(100, fmt) ;
+      if ((x[0] == EXECLINE_BLOCK_END_CHAR) && (!EXECLINE_BLOCK_END_CHAR || !x[1])) break ;
+      if ((x[0] != EXECLINE_BLOCK_QUOTE_CHAR) && strict)
+      {
+        char fmti[UINT_FMT] ;
+        char fmtp[UINT_FMT] ;
+        fmti[uint_fmt(fmti, i)] = 0 ;
+        fmtp[uint_fmt(fmtp, n - base)] = 0 ;
+        if (strict == 1)
+          strerr_warnw6x("unquoted positional ", x, " at block ", fmti, " position ", fmtp) ;
+        else
+          strerr_dief6x(100, "unquoted positional ", x, " at block ", fmti, " position ", fmtp) ;
+      }
+    }
+  }
+
+
+ /* n = shift value; modify the env */
+
+  {
+    register unsigned int i = 1 ;
+    char fmt[UINT_FMT] ;
+    fmt[uint_fmt(fmt, sharp - n)] = 0 ;
+    if (!pathexec_env("#", fmt)) strerr_diefu1sys(111, "pathexec_env") ;
+    for (; i <= sharp ; i++)
+    {
+      char fmu[UINT_FMT] ;
+      fmt[uint_fmt(fmt, i)] = 0 ;
+      fmu[uint_fmt(fmu, i + n)] = 0 ;
+      if (!pathexec_env(fmt, i <= (sharp - n) ? env_get2(envp, fmu) : 0))
+        strerr_diefu1sys(111, "pathexec_env") ;
+    }
+  }
+  pathexec(argv) ;
+  strerr_dieexec(111, argv[0]) ;
+}
diff --git a/src/execline/tryexec.c b/src/execline/tryexec.c
new file mode 100644
index 0000000..46d3479
--- /dev/null
+++ b/src/execline/tryexec.c
@@ -0,0 +1,61 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+#include <execline/execline.h>
+
+#define USAGE "tryexec [ -n ] [ -c ] [ -l ] [ -a argv0 ] { command... }"
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  char const *env_zero[1] = { 0 } ;
+  char const *executable = 0 ;
+  char const *argv0 = 0 ;
+  char const **dom = 0 ;
+  char const **sub = 0 ;
+  char const *const *dom_envp = envp ;
+  int not = 0, dash = 0 ;
+  PROG = "tryexec" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "ncla:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n' : not = 1 ; break ;
+        case 'c' : dom_envp = env_zero ; break ;
+        case 'l' : dash = 1 ; break ;
+        case 'a' : argv0 = l.arg ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  {
+    int argc1 = el_semicolon(argv) ;
+    if (!argc1) strerr_dief1x(100, "empty block") ;
+    if (argc1 >= argc) strerr_dief1x(100, "unterminated block") ;
+    argv[argc1] = 0 ;
+    dom = argv + not * (argc1 + 1) ;
+    sub = argv + !not * (argc1 + 1) ;
+  }
+  executable = dom[0] ;
+  if (argv0) dom[0] = argv0 ;
+  if (dash)
+  {
+    register unsigned int n = str_len(dom[0]) ;
+    char dashed[n+2] ;
+    dashed[0] = '-' ;
+    byte_copy(dashed+1, n+1, dom[0]) ;
+    dom[0] = dashed ;
+    pathexec_run(executable, dom, dom_envp) ;
+  }
+  else pathexec_run(executable, dom, dom_envp) ;
+
+  pathexec0_run(sub, envp) ;
+  strerr_dieexec(111, sub[0]) ;
+}
diff --git a/src/execline/umask.c b/src/execline/umask.c
new file mode 100644
index 0000000..81217b6
--- /dev/null
+++ b/src/execline/umask.c
@@ -0,0 +1,20 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "umask value prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int m ;
+  PROG = "umask" ;
+  if (argc < 3) strerr_dieusage(100, USAGE) ;
+  if (!uint_oscan(argv[1], &m)) strerr_dieusage(100, USAGE) ;
+  umask(m) ;
+  pathexec_run(argv[2], argv+2, envp) ;
+  strerr_dieexec(111, argv[2]) ;
+}
diff --git a/src/execline/unexport.c b/src/execline/unexport.c
new file mode 100644
index 0000000..189249c
--- /dev/null
+++ b/src/execline/unexport.c
@@ -0,0 +1,20 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "unexport variable prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int len ;
+  PROG = "unexport" ;
+  if (argc < 3) strerr_dieusage(100, USAGE) ;
+  len = str_len(argv[1]) ;
+  if (byte_chr(argv[1], len, '=') < len)
+    strerr_dief2x(100, "invalid variable name: ", argv[1]) ;
+  pathexec_r(argv+2, envp, env_len(envp), argv[1], len+1) ;
+  strerr_dieexec(111, argv[2]) ;
+}
diff --git a/src/execline/wait.c b/src/execline/wait.c
new file mode 100644
index 0000000..4d550c9
--- /dev/null
+++ b/src/execline/wait.c
@@ -0,0 +1,62 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+#include <execline/execline.h>
+
+#define USAGE "wait [ -r ] { pids... }"
+
+static unsigned int waitall (void)
+{
+  register unsigned int n = 0 ;
+  int wstat ;
+  while (wait(&wstat) > 0) n++ ;
+  return n ;
+}
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  int argc1 ;
+  int flagreap = 0 ;
+  PROG = "wait" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "r", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'r' : flagreap = 1 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  argc1 = el_semicolon(argv) ;
+  if (argc1 >= argc) strerr_dief1x(100, "unterminated block") ;
+  if (!argc1) flagreap ? wait_reap() : waitall() ;
+  else
+  {
+    pid_t tab[argc1] ;
+    register unsigned int i = 0 ;
+    for (; i < (unsigned int)argc1 ; i++)
+    {
+      unsigned int pid ;
+      if (!uint0_scan(argv[i], &pid)) strerr_dieusage(100, USAGE) ;
+      tab[i] = pid ;
+    }
+    if (flagreap)
+    {
+      if (waitn_reap(tab, argc1) < 0)
+        strerr_diefu1sys(111, "waitn_reap") ;
+    }
+    else if (!waitn(tab, argc1)) strerr_diefu1sys(111, "waitn") ;
+  }
+  pathexec0_run(argv + argc1 + 1, envp) ;
+  strerr_dieexec(111, argv[argc1 + 1]) ;
+}
diff --git a/src/include-local/exlsn.h b/src/include-local/exlsn.h
new file mode 100644
index 0000000..3665bca
--- /dev/null
+++ b/src/include-local/exlsn.h
@@ -0,0 +1,36 @@
+/* ISC license. */
+
+#ifndef EXLSN_H
+#define EXLSN_H
+
+#include <skalibs/gccattributes.h>
+#include <skalibs/stralloc.h>
+
+typedef struct exlsn_s exlsn_t, *exlsn_t_ref ;
+struct exlsn_s
+{
+  stralloc vars ;
+  stralloc values ;
+  stralloc data ; /* array of elsubst */
+} ;
+
+#define EXLSN_ZERO { .vars = STRALLOC_ZERO, .values = STRALLOC_ZERO, .data = STRALLOC_ZERO }
+
+extern void exlsn_free (exlsn_t *) ;
+
+typedef int exlsnfunc_t (int, char const **, char const *const *, exlsn_t *) ;
+typedef exlsnfunc_t *exlsnfunc_t_ref ;
+	
+extern exlsnfunc_t exlsn_define ;
+extern exlsnfunc_t exlsn_importas ;
+extern exlsnfunc_t exlsn_import ;
+extern exlsnfunc_t exlsn_elglob ;
+extern exlsnfunc_t exlsn_exlp ;
+extern exlsnfunc_t exlsn_multidefine ;
+
+extern int exlp (unsigned int, char const *const *, exlsn_t *) ;
+extern void el_substandrun (int, char const *const *, char const *const *, exlsn_t *) gccattr_noreturn ;
+extern void el_substandrun_str (stralloc *, unsigned int, char const *const *, exlsn_t *) gccattr_noreturn ;
+extern void exlsn_main (int, char const **, char const *const *, exlsnfunc_t *, char const *) gccattr_noreturn ;
+
+#endif
diff --git a/src/include/execline/execline.h b/src/include/execline/execline.h
new file mode 100644
index 0000000..a5c16ad
--- /dev/null
+++ b/src/include/execline/execline.h
@@ -0,0 +1,67 @@
+/* ISC license. */
+
+#ifndef EXECLINE_H
+#define EXECLINE_H
+
+#include <sys/types.h>
+#include <skalibs/gccattributes.h>
+#include <skalibs/stralloc.h>
+
+#define EXECLINE_BLOCK_QUOTE_CHAR ' '
+#define EXECLINE_BLOCK_END_CHAR '\0'
+
+
+/* Basics */
+
+extern int el_vardupl (char const *, char const *, unsigned int) gccattr_pure ;
+extern unsigned int el_getstrict (void) gccattr_const ;
+extern void el_obsolescent (void) ;
+
+
+/* Environment shifting */
+
+extern int el_pushenv (stralloc *, char const *const *, unsigned int, char const *const *, unsigned int) ;
+extern int el_popenv  (stralloc *, char const *const *, unsigned int, char const *const *, unsigned int) ;
+
+
+/* Sequence */
+
+extern pid_t el_spawn0 (char const *, char const *const *, char const *const *) ;
+extern pid_t el_spawn1 (char const *, char const *const *, char const *const *, int *, int) ;
+extern void el_execsequence (char const *const *, char const *const *, char const *const *) gccattr_noreturn ;
+
+
+/* Block unquoting */
+
+extern int el_semicolon (char const **) ;
+
+
+/* Value transformation */
+
+typedef struct eltransforminfo_s eltransforminfo_t, *eltransforminfo_t_ref ;
+struct eltransforminfo_s
+{
+  char const *delim ;
+  unsigned int crunch : 1 ;
+  unsigned int chomp : 1 ;
+  unsigned int split : 1 ;
+} ;
+
+#define ELTRANSFORMINFO_ZERO { .delim = " \n\r\t", .crunch = 0, .chomp = 0, .split = 0 }
+
+extern int el_transform (stralloc *, unsigned int, eltransforminfo_t const *) ;
+
+
+/* Substitution */
+
+typedef struct elsubst_s elsubst_t, *elsubst_t_ref ;
+struct elsubst_s
+{
+  unsigned int var ;
+  unsigned int value ;
+  unsigned int n ;
+} ;
+
+extern int el_substitute (stralloc *, char const *, unsigned int, char const *, char const *, elsubst_t const *, unsigned int) ;
+
+#endif
diff --git a/src/libexecline/deps-lib/execline b/src/libexecline/deps-lib/execline
new file mode 100755
index 0000000..69d483d
--- /dev/null
+++ b/src/libexecline/deps-lib/execline
@@ -0,0 +1,21 @@
+el_execsequence.o
+el_getstrict.o
+el_obsolescent.o
+el_popenv.o
+el_pushenv.o
+el_semicolon.o
+el_spawn0.o
+el_spawn1.o
+el_substandrun.o
+el_substandrun_str.o
+el_substitute.o
+el_transform.o
+el_vardupl.o
+exlsn_define.o
+exlsn_elglob.o
+exlsn_import.o
+exlsn_multidefine.o
+exlsn_exlp.o
+exlsn_main.o
+exlsn_free.o
+exlp.o
diff --git a/src/libexecline/el_execsequence.c b/src/libexecline/el_execsequence.c
new file mode 100644
index 0000000..6b825af
--- /dev/null
+++ b/src/libexecline/el_execsequence.c
@@ -0,0 +1,41 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#ifdef EXECLINE_OLD_VARNAMES
+#include <skalibs/bytestr.h>
+#endif
+#include <skalibs/djbunix.h>
+#include <skalibs/env.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/uint.h>
+#include <execline/execline.h>
+
+void el_execsequence (char const *const *argv1, char const *const *argv2, char const *const *envp)
+{
+  if (!argv2[0])
+  {
+    pathexec0_run(argv1, envp) ;
+    strerr_dieexec(111, argv1[0]) ;
+  }
+  else
+  {
+    int wstat ;
+    unsigned int j = 2 ;
+#ifdef EXECLINE_OLD_VARNAMES
+    char fmt[UINT_FMT * 2 + 15] = "?=" ;
+#else
+    char fmt[UINT_FMT + 1] = "?=" ;
+#endif
+    pid_t pid = el_spawn0(argv1[0], argv1, envp) ;
+    if (!pid) strerr_warnwu2sys("spawn ", argv1[0]) ;
+    if (wait_pid(pid, &wstat) < 0)
+      strerr_diefu2sys(111, "wait for ", argv1[0]) ;
+    j += uint_fmt(fmt + j, wait_status(wstat)) ; fmt[j++] = 0 ;
+#ifdef EXECLINE_OLD_VARNAMES
+    byte_copy(fmt + j, 13, "LASTEXITCODE=") ; j += 13 ;
+    j += uint_fmt(fmt + j, wait_status(wstat)) ; fmt[j++] = 0 ;
+#endif
+    pathexec_r(argv2, envp, env_len(envp), fmt, j) ;
+  }
+  strerr_dieexec(111, argv2[0]) ;
+}
diff --git a/src/libexecline/el_getstrict.c b/src/libexecline/el_getstrict.c
new file mode 100644
index 0000000..091845c
--- /dev/null
+++ b/src/libexecline/el_getstrict.c
@@ -0,0 +1,18 @@
+/* ISC license. */
+
+#include <skalibs/env.h>
+#include <skalibs/uint.h>
+#include <execline/execline.h>
+
+unsigned int el_getstrict (void)
+{
+  static unsigned int strict = 0 ;
+  static int first = 1 ;
+  if (first)
+  {
+    char const *x = env_get("EXECLINE_STRICT") ;
+    first = 0 ;
+    if (x) uint0_scan(x, &strict) ;
+  }
+  return strict ;
+}
diff --git a/src/libexecline/el_obsolescent.c b/src/libexecline/el_obsolescent.c
new file mode 100644
index 0000000..42b8a11
--- /dev/null
+++ b/src/libexecline/el_obsolescent.c
@@ -0,0 +1,10 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include <execline/execline.h>
+
+void el_obsolescent (void)
+{
+  if (el_getstrict())
+    strerr_warnw3x("this command is marked as obsolescent. Please update your script to use the ", PROG, "x command instead.") ;
+}
diff --git a/src/libexecline/el_popenv.c b/src/libexecline/el_popenv.c
new file mode 100644
index 0000000..3726506
--- /dev/null
+++ b/src/libexecline/el_popenv.c
@@ -0,0 +1,44 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/uint.h>
+#include <execline/execline.h>
+
+int el_popenv (stralloc *sa, char const *const *envp, unsigned int envlen, char const *const *list, unsigned int listlen)
+{
+  unsigned int i = 0, salen = sa->len, count = 0 ;
+  for (; i < envlen ; i++)
+  {
+    unsigned int equal, colon, n ;
+    unsigned int j = 0 ;
+    for (; j < listlen ; j++) if (str_start(envp[i], list[j])) break ;
+    if (j == listlen) goto copyit ;
+    j = str_len(list[j]) ;
+    colon = j + str_chr(envp[i] + j, ':') ;
+    equal = j + str_chr(envp[i] + j, '=') ;
+    if (!envp[i][equal]) goto badenv ;
+    if (colon >= equal) { count++ ; continue ; }
+    if (colon + 1 + uint_scan(envp[i] + colon + 1, &n) != equal) goto copyit ;
+    if (!n) goto copyit ;
+    if (!stralloc_catb(sa, envp[i], colon)) goto err ;
+    if (n > 1)
+    {
+      char fmt[UINT_FMT+1] = ":" ;
+      n = 1 + uint_fmt(fmt+1, n-1) ;
+      if (!stralloc_catb(sa, fmt, n)) goto err ;
+    }
+    if (!stralloc_catb(sa, envp[i] + equal, str_len(envp[i] + equal) + 1)) goto err ;
+    continue ;
+copyit:
+    if (!stralloc_catb(sa, envp[i], str_len(envp[i]) + 1)) goto err ;
+  }
+  return (int)count ;
+
+badenv :
+  errno = EINVAL ;
+err:
+  sa->len = salen ;
+  return -1 ;
+}
diff --git a/src/libexecline/el_pushenv.c b/src/libexecline/el_pushenv.c
new file mode 100644
index 0000000..9b9608d
--- /dev/null
+++ b/src/libexecline/el_pushenv.c
@@ -0,0 +1,49 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/uint.h>
+#include <execline/execline.h>
+
+int el_pushenv (stralloc *sa, char const *const *envp, unsigned int envlen, char const *const *list, unsigned int listlen)
+{
+  unsigned int i = 0, salen = sa->len, count = 0 ;
+  for (; i < envlen ; i++)
+  {
+    unsigned int equal, colon ;
+    unsigned int j = 0 ;
+    for (; j < listlen ; j++) if (str_start(envp[i], list[j])) break ;
+    if (j == listlen) goto copyit ;
+    count++ ;
+    j = str_len(list[j]) ;
+    colon = j + str_chr(envp[i] + j, ':') ;
+    equal = j + str_chr(envp[i] + j, '=') ;
+    if (!envp[i][equal]) goto badenv ;
+    if (colon >= equal)
+    {
+      if (!stralloc_catb(sa, envp[i], equal)
+       || !stralloc_catb(sa, ":1", 2)) goto err ;
+    }
+    else
+    {
+      char fmt[UINT_FMT+1] = ":" ;
+      unsigned int n ;
+      if (colon + 1 + uint_scan(envp[i] + colon + 1, &n) != equal) goto copyit ;
+      n = 1 + uint_fmt(fmt+1, n+1) ;
+      if (!stralloc_catb(sa, envp[i], colon)) goto err ;
+      if (!stralloc_catb(sa, fmt, n)) goto err ;
+    }
+    if (!stralloc_catb(sa, envp[i] + equal, str_len(envp[i] + equal) + 1)) goto err ;
+    continue ;
+copyit:
+    if (!stralloc_catb(sa, envp[i], str_len(envp[i]) + 1)) goto err ;
+  }
+  return (int)count ;
+
+badenv :
+  errno = EINVAL ;
+err:
+  sa->len = salen ;
+  return -1 ;
+}
diff --git a/src/libexecline/el_semicolon.c b/src/libexecline/el_semicolon.c
new file mode 100644
index 0000000..dc99daf
--- /dev/null
+++ b/src/libexecline/el_semicolon.c
@@ -0,0 +1,35 @@
+/* ISC license. */
+
+#include <skalibs/env.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/uint.h>
+#include <execline/execline.h>
+
+int el_semicolon (char const **argv)
+{
+  static unsigned int nblock = 0 ;
+  register int argc1 = 0 ;
+  nblock++ ;
+  for (;; argc1++, argv++)
+  {
+    register char const *arg = *argv ;
+    if (!arg) return argc1 + 1 ;
+    if ((arg[0] == EXECLINE_BLOCK_END_CHAR) && (!EXECLINE_BLOCK_END_CHAR || !arg[1])) return argc1 ;
+    else if (arg[0] == EXECLINE_BLOCK_QUOTE_CHAR) ++*argv ;
+    else
+    {
+      unsigned int strict = el_getstrict() ;
+      if (strict)
+      {
+        char fmt1[UINT_FMT] ;
+        char fmt2[UINT_FMT] ;
+        fmt1[uint_fmt(fmt1, nblock)] = 0 ;
+        fmt2[uint_fmt(fmt2, (unsigned int)argc1)] = 0 ;
+        if (strict >= 2)
+          strerr_dief6x(100, "unquoted argument ", arg, " at block ", fmt1, " position ", fmt2) ;
+        else
+          strerr_warnw6x("unquoted argument ", arg, " at block ", fmt1, " position ", fmt2) ;
+      }
+    }
+  }
+}
diff --git a/src/libexecline/el_spawn0.c b/src/libexecline/el_spawn0.c
new file mode 100644
index 0000000..4b7f50f
--- /dev/null
+++ b/src/libexecline/el_spawn0.c
@@ -0,0 +1,15 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <skalibs/djbunix.h>
+#include <execline/execline.h>
+
+pid_t el_spawn0 (char const *prog, char const *const *argv, char const *const *envp)
+{
+  if (!argv[0])
+  {
+    static char const *const newargv[2] = { "/bin/true", 0 } ;
+    return child_spawn0(newargv[0], newargv, 0) ;
+  }
+  else return child_spawn0(prog, argv, envp) ;
+}
diff --git a/src/libexecline/el_spawn1.c b/src/libexecline/el_spawn1.c
new file mode 100644
index 0000000..4785b92
--- /dev/null
+++ b/src/libexecline/el_spawn1.c
@@ -0,0 +1,15 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <skalibs/djbunix.h>
+#include <execline/execline.h>
+
+pid_t el_spawn1 (char const *prog, char const *const *argv, char const *const *envp, int *fd, int w)
+{
+  if (!argv[0])
+  {
+    static char const *const newargv[2] = { "/bin/true", 0 } ;
+    return child_spawn1(newargv[0], newargv, 0, fd, w) ;
+  }
+  else return child_spawn1(prog, argv, envp, fd, w) ;
+}
diff --git a/src/libexecline/el_substandrun.c b/src/libexecline/el_substandrun.c
new file mode 100644
index 0000000..7dddbca
--- /dev/null
+++ b/src/libexecline/el_substandrun.c
@@ -0,0 +1,13 @@
+/* ISC license. */
+
+#include <skalibs/env.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/skamisc.h>
+#include "exlsn.h"
+
+void el_substandrun (int argc, char const *const *argv, char const *const *envp, exlsn_t *info)
+{
+  satmp.len = 0 ;
+  if (!env_string(&satmp, argv, (unsigned int)argc)) strerr_diefu1sys(111, "env_string") ;
+  el_substandrun_str(&satmp, 0, envp, info) ;
+}
diff --git a/src/libexecline/el_substandrun_str.c b/src/libexecline/el_substandrun_str.c
new file mode 100644
index 0000000..351ec9d
--- /dev/null
+++ b/src/libexecline/el_substandrun_str.c
@@ -0,0 +1,25 @@
+/* ISC license. */
+
+#include <skalibs/djbunix.h>
+#include <skalibs/env.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <execline/execline.h>
+#include "exlsn.h"
+
+void el_substandrun_str (stralloc *src, unsigned int srcbase, char const *const *envp, exlsn_t *info)
+{
+  stralloc dst = STRALLOC_ZERO ;
+  register int r = el_substitute(&dst, src->s + srcbase, src->len, info->vars.s, info->values.s, genalloc_s(elsubst_t, &info->data), genalloc_len(elsubst_t, &info->data)) ;
+  if (r < 0) strerr_diefu1sys(111, "el_substitute") ;
+  exlsn_free(info) ;
+  stralloc_free(src) ;
+  {
+    char const *v[r + 1] ;
+    if (!env_make(v, r, dst.s, dst.len)) strerr_diefu1sys(111, "env_make") ;
+    v[r] = 0 ;
+    pathexec0_run(v, envp) ;
+  }
+  strerr_dieexec(111, dst.s) ;
+}
diff --git a/src/libexecline/el_substitute.c b/src/libexecline/el_substitute.c
new file mode 100644
index 0000000..a94c16d
--- /dev/null
+++ b/src/libexecline/el_substitute.c
@@ -0,0 +1,179 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <execline/execline.h>
+
+typedef struct elsubsu_s elsubsu_t, *elsubsu_t_ref ;
+struct elsubsu_s
+{
+  elsubst_t const *subst ;
+  unsigned int pos ;
+} ;
+
+typedef struct subsuinfo_s subsuinfo_t, *subsuinfo_t_ref ;
+struct subsuinfo_s
+{
+  stralloc dst ;
+  stralloc sa ;
+  genalloc list ; /* array of elsubsu_t */
+  char const *values ;
+} ;
+
+#define SUBSUINFO_ZERO { .dst = STRALLOC_ZERO, .sa = STRALLOC_ZERO, .list = GENALLOC_ZERO, .values = 0 }
+
+#define TEST 0x80
+#define MARK 0x40
+#define KEEPESC 0x20
+#define INCRESC 0x10
+
+#define STATE 0x07
+#define INWORD 0x00
+#define INDOLL 0x01
+#define INDBR 0x02
+#define INVAR 0x03
+#define INVARBR 0x04
+#define ACCEPT 0x05
+
+static int parseword (stralloc *sa, genalloc *list, char const *s, char const *vars, elsubst_t const *substs, unsigned int nsubst)
+{
+  static char const class[5] = "\0\\${}" ;
+  static unsigned char const table[6][5] =
+  {
+    { ACCEPT, ACCEPT, ACCEPT, ACCEPT | TEST, ACCEPT },
+    { INWORD | KEEPESC | INCRESC, INWORD | INCRESC, INWORD | INCRESC, INWORD | TEST | INCRESC, INWORD | INCRESC },
+    { INDOLL | KEEPESC, INDOLL, INDOLL, INDOLL | TEST, INDOLL },
+    { INWORD, INDBR | KEEPESC, INWORD, INWORD | TEST, INWORD },
+    { INWORD, INWORD, INWORD, INWORD | TEST, INWORD | TEST },
+    { INWORD, INVAR | MARK | KEEPESC, INVARBR | MARK | KEEPESC, INVAR | KEEPESC, INVARBR | KEEPESC }
+  } ;
+
+  unsigned int mark = 0, pos = 0, offset = 0, esc = 0, salen = sa->len, listlen = genalloc_len(elsubsu_t, list) ;
+  unsigned char state = INWORD ;
+
+  while (state != ACCEPT)
+  {
+    int nopush = 0 ;
+    unsigned char c = table[byte_chr(class, 5, s[pos])][state] ;
+
+    if (c & TEST)
+    {
+      unsigned int supp = (state == INVARBR) ;
+      unsigned int i = 0 ;
+      for (; i < nsubst ; i++)
+      {
+        if (!str_diffn(vars + substs[i].var, s + mark, pos - mark) && !vars[substs[i].var + pos - mark])
+        {
+          sa->len -= esc >> 1 ; offset += esc >> 1 ;
+          if (esc & 1)
+          {
+            byte_copy(sa->s + mark - offset - 2 - supp, pos - mark + 1 + supp, sa->s + mark - offset + (esc>>1) - 1 - supp) ;
+            sa->len-- ; offset++ ;
+          }
+          else
+          {
+            elsubsu_t cur ;
+            cur.subst = substs + i ;
+            cur.pos = mark - offset - 1 - supp ;
+            if (!genalloc_append(elsubsu_t, list, &cur)) goto err ;
+            offset += sa->len - cur.pos ;
+            sa->len = cur.pos ;
+            if (supp) nopush = 1 ;
+          }
+          break ;
+        }
+      }
+    }
+    if (nopush) offset++ ;
+    else if (!stralloc_catb(sa, s+pos, 1)) goto err ;
+    if (c & MARK) mark = pos ;
+    if (!(c & KEEPESC)) esc = 0 ;
+    if (c & INCRESC) esc++ ;
+    state = c & STATE ; pos++ ;
+  }
+  sa->len-- ;
+  return (int)pos ;
+
+err:
+  sa->len = salen ;
+  list->len = listlen ;
+  return -1 ;
+}
+
+static int substword (subsuinfo_t *info, unsigned int wordstart, unsigned int wordlen, unsigned int n, unsigned int offset)
+{
+  if (n < genalloc_len(elsubsu_t, &info->list))
+  {
+    elsubsu_t *list = genalloc_s(elsubsu_t, &info->list) ;
+    char const *p = info->values + list[n].subst->value ;
+    unsigned int l = list[n].pos + offset ;
+    unsigned int dstbase = info->dst.len ;
+    unsigned int sabase = info->sa.len ;
+    unsigned int i = 0 ;
+    int nc = 0 ;
+    if (!stralloc_readyplus(&info->sa, l)) return -1 ;
+    stralloc_catb(&info->sa, info->sa.s + wordstart, l) ;
+    for ( ; i < list[n].subst->n ; i++)
+    {
+      int r ;
+      unsigned int plen = str_len(p) ;
+      info->sa.len = sabase + l ;
+      if (!stralloc_readyplus(&info->sa, plen + wordlen - l)) goto err ;
+      stralloc_catb(&info->sa, p, plen) ;
+      stralloc_catb(&info->sa, info->sa.s + wordstart + l, wordlen - l) ;
+      r = substword(info, sabase, info->sa.len - sabase, n+1, offset + plen) ;
+      if (r < 0) goto err ;
+      nc += r ;
+      p += plen+1 ;
+    }
+    return nc ;
+   err:
+    info->sa.len = sabase ;
+    info->dst.len = dstbase ;
+    return -1 ;
+  }
+  else
+  {
+    if (!stralloc_readyplus(&info->dst, wordlen+1)) return -1 ;
+    stralloc_catb(&info->dst, info->sa.s + wordstart, wordlen) ;
+    stralloc_0(&info->dst) ;
+    return 1 ;
+  }
+}
+
+int el_substitute (stralloc *dst, char const *src, unsigned int len, char const *vars, char const *values, elsubst_t const *substs, unsigned int nsubst)
+{
+  subsuinfo_t info = SUBSUINFO_ZERO ;
+  unsigned int nc = 0 ;
+  unsigned int i = 0 ;
+  unsigned int dstbase = dst->len ;
+  int wasnull = !dst->s ;
+  info.dst = *dst ;
+  info.values = values ;
+
+  while (i < len)
+  {
+    int r ;
+    genalloc_setlen(elsubsu_t, &info.list, 0) ;
+    info.sa.len = 0 ;
+    r = parseword(&info.sa, &info.list, src + i, vars, substs, nsubst) ;
+    if (r < 0) goto err ;
+    i += r ;
+    r = substword(&info, 0, info.sa.len, 0, 0) ;
+    if (r < 0) goto err ;
+    nc += r ;
+  }
+  genalloc_free(elsubsu_t, &info.list) ;
+  stralloc_free(&info.sa) ;
+  if (!wasnull) stralloc_free(dst) ;
+  *dst = info.dst ;
+  return (int)nc ;
+
+err :
+  genalloc_free(elsubsu_t, &info.list) ;
+  stralloc_free(&info.sa) ;
+  if (wasnull) stralloc_free(&info.dst) ; else info.dst.len = dstbase ;
+  *dst = info.dst ;
+  return -1 ;
+}
diff --git a/src/libexecline/el_transform.c b/src/libexecline/el_transform.c
new file mode 100644
index 0000000..ac84d1d
--- /dev/null
+++ b/src/libexecline/el_transform.c
@@ -0,0 +1,84 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/netstring.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/stralloc.h>
+#include <execline/execline.h>
+
+static void el_crunch (stralloc *sa, unsigned int base, char const *delim)
+{
+  register unsigned int i = base, j = base ;
+  register int crunching = 0 ;
+  for (; i < sa->len ; i++)
+  {
+    if (!crunching) sa->s[j++] = sa->s[i] ;
+    if (delim[str_chr(delim, sa->s[i])]) crunching = 1 ;
+    else if (crunching)
+    {
+      i-- ;
+      crunching = 0 ;
+    }
+  }
+  sa->len = j ;
+}
+
+static int el_split (stralloc *sa, unsigned int base, eltransforminfo_t const *si, int chomped)
+{
+  unsigned int n = 0 ;
+  register unsigned int i = base ;
+  for (; i < sa->len ; i++)
+    if (si->delim[str_chr(si->delim, sa->s[i])])
+    {
+      sa->s[i] = 0 ;
+      n++ ;
+      base = i+1 ;
+    }
+
+  if (sa->len && sa->s[sa->len - 1])
+  {
+    if (si->chomp && !chomped) sa->len = base ;
+    else if (!stralloc_0(sa)) return -1 ;
+    else n++ ;
+  }
+  return n ;
+}
+
+static int el_splitnetstring (stralloc *sa, unsigned int base)
+{
+  unsigned int tmpbase = satmp.len ;
+  unsigned int n = 0, i = base ;
+  while (i < sa->len)
+  {
+    register int r = netstring_decode(&satmp, sa->s + i, sa->len - i) ;
+    if (r < 0) goto err ;
+    if (!stralloc_0(&satmp)) goto err ;
+    i += r ; n++ ;
+  }
+  sa->len = base ;
+  if (!stralloc_catb(sa, satmp.s + tmpbase, satmp.len - tmpbase))
+  {
+    sa->len = i ;
+    goto err ;
+  }
+  satmp.len = tmpbase ;
+  return n ;
+
+err:
+  satmp.len = tmpbase ;
+  return -1 ;
+}
+
+int el_transform (stralloc *sa, unsigned int i, eltransforminfo_t const *si)
+{
+  int chomped = 0 ;
+  if (si->crunch && *si->delim) el_crunch(sa, i, si->delim) ;
+  if (si->chomp && (sa->len > i)
+   && si->delim[str_chr(si->delim, sa->s[sa->len-1])])
+  {
+    sa->len-- ;
+    chomped = 1 ;
+  }
+  return si->split ? *si->delim ? el_split(sa, i, si, chomped) : el_splitnetstring(sa, i) :
+                     stralloc_0(sa) ? 1 : -1 ;
+}
diff --git a/src/libexecline/el_vardupl.c b/src/libexecline/el_vardupl.c
new file mode 100644
index 0000000..7583669
--- /dev/null
+++ b/src/libexecline/el_vardupl.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <execline/execline.h>
+
+int el_vardupl (char const *key, char const *s, unsigned int len)
+{
+  register unsigned int i = 0 ;
+  for (; i < len ; i += str_len(s + i) + 1)
+    if (!str_diff(key, s + i)) return 1 ;
+  return 0 ;
+}
diff --git a/src/libexecline/exlp.c b/src/libexecline/exlp.c
new file mode 100644
index 0000000..060eb68
--- /dev/null
+++ b/src/libexecline/exlp.c
@@ -0,0 +1,78 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/env.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/uint.h>
+#include <execline/execline.h>
+#include "exlsn.h"
+	
+int exlp (unsigned int nmin, char const *const *envp, exlsn_t *info)
+{
+  unsigned int varbase = info->vars.len ;
+  unsigned int valbase = info->values.len ;
+  unsigned int datbase = genalloc_len(elsubst_t, &info->data) ;
+  unsigned int i = 0 ;
+  char const *x = env_get2(envp, "#") ;
+  elsubst_t blah ;
+  unsigned int n, ntot, poszero ;
+  if (!x) return -2 ;
+  if (!uint0_scan(x, &n)) return -2 ;
+  if (el_vardupl("#", info->vars.s, info->vars.len)) return -2 ;
+  if (el_vardupl("@", info->vars.s, info->vars.len)) return -2 ;
+  {
+    register unsigned int strict = el_getstrict() ;
+    if (strict && (n < nmin))
+    {
+      char fmta[UINT_FMT] ;
+      char fmtn[UINT_FMT] ;
+      fmta[uint_fmt(fmta, n)] = 0 ;
+      fmtn[uint_fmt(fmtn, nmin)] = 0 ;
+      if (strict > 1)
+        strerr_dief4x(100, "too few arguments: expected at least ", fmtn, " but got ", fmta) ;
+      else
+        strerr_warnw4x("too few arguments: expected at least ", fmtn, " but got ", fmta) ;
+    }
+  }
+  blah.var = varbase ;
+  blah.value = info->values.len ;
+  blah.n = 1 ;
+  if (!stralloc_catb(&info->vars, "#\0@", 4)
+   || !stralloc_catb(&info->values, x, str_len(x) + 1)
+   || !genalloc_append(elsubst_t, &info->data, &blah)) goto err ;
+  ntot = n > nmin ? n : nmin ;
+  poszero = info->values.len ;
+  for (; i <= ntot ; i++)
+  {
+    char fmt[UINT_FMT] ;
+    unsigned int l = uint_fmt(fmt, i) ;
+    fmt[l] = 0 ;
+    if (el_vardupl(fmt, info->vars.s, info->vars.len)) goto err2 ;
+    x = (i <= n) ? env_get2(envp, fmt) : "" ;
+    if (!x) goto err2 ;
+    blah.var = info->vars.len ;
+    blah.value = info->values.len ;
+    blah.n = 1 ;
+    if (!stralloc_catb(&info->vars, fmt, l+1)
+     || !stralloc_catb(&info->values, x, str_len(x) + 1)
+     || !genalloc_append(elsubst_t, &info->data, &blah)) goto err ;
+  }
+  blah.var = varbase + 2 ;
+  blah.value = poszero + str_len(info->values.s + poszero) + 1 ;
+  blah.n = n ;
+  if (!genalloc_append(elsubst_t, &info->data, &blah)) goto err ;
+  return n ;
+
+ err:
+  info->vars.len = varbase ;
+  info->values.len = valbase ;
+  genalloc_setlen(elsubst_t, &info->data, datbase) ;
+  return -1 ;
+ err2:
+  info->vars.len = varbase ;
+  info->values.len = valbase ;
+  genalloc_setlen(elsubst_t, &info->data, datbase) ;
+  return -2 ;
+}
diff --git a/src/libexecline/exlsn_define.c b/src/libexecline/exlsn_define.c
new file mode 100644
index 0000000..3abe516
--- /dev/null
+++ b/src/libexecline/exlsn_define.c
@@ -0,0 +1,50 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <execline/execline.h>
+#include "exlsn.h"
+
+int exlsn_define (int argc, char const **argv, char const *const *envp, exlsn_t *info)
+{
+  eltransforminfo_t si = ELTRANSFORMINFO_ZERO ;
+  subgetopt_t localopt = SUBGETOPT_ZERO ;
+  elsubst_t blah ;
+  blah.var = info->vars.len ;
+  blah.value = info->values.len ;
+  for (;;)
+  {
+    register int opt = subgetopt_r(argc, argv, "nsCcd:", &localopt) ;
+    if (opt < 0) break ;
+    switch (opt)
+    {
+      case 'n' : si.chomp = 1 ; break ;
+      case 's' : si.split = 1 ; break ;
+      case 'C' : si.crunch = 1 ; break ;
+      case 'c' : si.crunch = 0 ; break ;
+      case 'd' : si.delim = localopt.arg ; break ;
+      default : return -3 ;
+    }
+  }
+  argc -= localopt.ind ; argv += localopt.ind ;
+ 
+  if (argc < 2) return -3 ;
+  if (!*argv[0] || el_vardupl(argv[0], info->vars.s, info->vars.len)) return -2 ;
+  if (!stralloc_catb(&info->vars, argv[0], str_len(argv[0]) + 1)) return -1 ;
+  if (!stralloc_cats(&info->values, argv[1])) goto err ;
+  {
+    register int r = el_transform(&info->values, blah.value, &si) ;
+    if (r < 0) goto err ;
+    blah.n = r ;
+  }
+  if (!genalloc_append(elsubst_t, &info->data, &blah)) goto err ;
+  (void)envp ;
+  return localopt.ind + 2 ;
+
+ err:
+  info->vars.len = blah.var ;
+  info->values.len = blah.value ;
+  return -1 ;
+}
diff --git a/src/libexecline/exlsn_elglob.c b/src/libexecline/exlsn_elglob.c
new file mode 100644
index 0000000..67e939b
--- /dev/null
+++ b/src/libexecline/exlsn_elglob.c
@@ -0,0 +1,78 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <glob.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <execline/execline.h>
+#include "exlsn.h"
+
+static int elgloberrfunc (char const *s, int e)
+{
+  errno = e ;
+  strerr_warnw2sys("while globbing, error reading ", s) ;
+  return 0 ;
+}
+
+int exlsn_elglob (int argc, char const **argv, char const *const *envp, exlsn_t *info)
+{
+  glob_t pglob ;
+  subgetopt_t localopt = SUBGETOPT_ZERO ;
+  elsubst_t blah ;
+  int flags = GLOB_NOSORT | GLOB_NOCHECK ;
+  unsigned int i = 0 ;
+  int verbose = 0 ;
+  blah.var = info->vars.len ;
+  blah.value = info->values.len ;
+  for (;;)
+  {
+    register int opt = subgetopt_r(argc, argv, "vwsme0", &localopt) ;
+    if (opt < 0) break ;
+    switch (opt)
+    {
+      case 'v' : verbose = 1 ; break ;
+      case 'w' : flags |= GLOB_ERR ; break ;
+      case 's' : flags &= ~GLOB_NOSORT ; break ;
+      case 'm' : flags |= GLOB_MARK ; break ;
+      case 'e' : flags |= GLOB_NOESCAPE ; break ;
+      case '0' : flags &= ~GLOB_NOCHECK ; break ;
+      default : return -3 ;
+    }
+  }
+  argc -= localopt.ind ; argv += localopt.ind ;
+
+  if (argc < 2) return -3 ;
+  if (!*argv[0] || el_vardupl(argv[0], info->vars.s, info->vars.len)) return -2 ;
+  if (!stralloc_catb(&info->vars, argv[0], str_len(argv[0]) + 1)) return -1 ;
+
+  pglob.gl_offs = 0 ;
+  switch (glob(argv[1], flags, verbose ? &elgloberrfunc : 0, &pglob))
+  {
+    case 0 : break ;
+    case GLOB_NOMATCH:
+    {
+      pglob.gl_pathc = 0 ;
+      pglob.gl_pathv = 0 ;
+      break ;
+    }
+    default: goto err ;
+  }
+  for ( ; i < (unsigned int)pglob.gl_pathc ; i++)
+    if (!stralloc_catb(&info->values, pglob.gl_pathv[i], str_len(pglob.gl_pathv[i]) + 1))
+      goto globerr ;
+  blah.n = pglob.gl_pathc ;
+  globfree(&pglob) ;
+  if (!genalloc_append(elsubst_t, &info->data, &blah)) goto err ;
+  (void)envp ;
+  return localopt.ind + 2 ;
+
+ globerr:
+  globfree(&pglob) ;
+ err:
+  info->vars.len = blah.var ;
+  info->values.len = blah.value ;
+  return -1 ;
+}
diff --git a/src/libexecline/exlsn_exlp.c b/src/libexecline/exlsn_exlp.c
new file mode 100644
index 0000000..4ca513e
--- /dev/null
+++ b/src/libexecline/exlsn_exlp.c
@@ -0,0 +1,27 @@
+/* ISC license. */
+
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include "exlsn.h"
+
+int exlsn_exlp (int argc, char const **argv, char const *const *envp, exlsn_t *info)
+{
+  subgetopt_t localopt = SUBGETOPT_ZERO ;
+  unsigned int nmin = 0 ;
+  int n ;
+  for (;;)
+  {
+    register int opt = subgetopt_r(argc, argv, "P:", &localopt) ;
+    if (opt < 0) break ;
+    switch (opt)
+    {
+      case 'P' : if (uint0_scan(localopt.arg, &nmin)) break ;
+      default : return -3 ;
+    }
+  }
+  argc -= localopt.ind ; argv += localopt.ind ;
+  if (!argc) return -3 ;
+  n = exlp(nmin, envp, info) ;
+  if (n < 0) return n ;
+  return localopt.ind ;
+}
diff --git a/src/libexecline/exlsn_free.c b/src/libexecline/exlsn_free.c
new file mode 100644
index 0000000..4d9dde3
--- /dev/null
+++ b/src/libexecline/exlsn_free.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <skalibs/stralloc.h>
+#include "exlsn.h"
+
+void exlsn_free (exlsn_t *info)
+{
+  stralloc_free(&info->vars) ;
+  stralloc_free(&info->values) ;
+  stralloc_free(&info->data) ;
+}
+
diff --git a/src/libexecline/exlsn_import.c b/src/libexecline/exlsn_import.c
new file mode 100644
index 0000000..1574027
--- /dev/null
+++ b/src/libexecline/exlsn_import.c
@@ -0,0 +1,76 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/env.h>
+#include <execline/execline.h>
+#include "exlsn.h"
+
+static int exlsn_import_as (int argc, char const **argv, char const *const *envp, exlsn_t *info, unsigned int as)
+{
+  eltransforminfo_t si = ELTRANSFORMINFO_ZERO ;
+  subgetopt_t localopt = SUBGETOPT_ZERO ;
+  elsubst_t blah ;
+  char const *defaultval = 0 ;
+  char const *x ;
+  int insist = 0 ;
+  blah.var = info->vars.len ;
+  blah.value = info->values.len ;
+
+  for (;;)
+  {
+    register int opt = subgetopt_r(argc, argv, "iD:nsCcd:", &localopt) ;
+    if (opt < 0) break ;
+    switch (opt)
+    {
+      case 'i' : insist = 1 ; break ;
+      case 'D' : defaultval = localopt.arg ; break ;
+      case 'n' : si.chomp = 1 ; break ;
+      case 's' : si.split = 1 ; break ;
+      case 'C' : si.crunch = 1 ; break ;
+      case 'c' : si.crunch = 0 ; break ;
+      case 'd' : si.delim = localopt.arg ; break ;
+      default : return -3 ;
+    }
+  }
+  argc -= localopt.ind ; argv += localopt.ind ;
+
+  if ((unsigned int)argc < 1+as) return -3 ;
+  if (!*argv[0] || el_vardupl(argv[0], info->vars.s, info->vars.len)) return -2 ;
+  if (!stralloc_catb(&info->vars, argv[0], str_len(argv[0]) + 1)) return -1 ;
+  x = env_get2(envp, argv[as]) ;
+  if (!x)
+  {
+    if (insist) strerr_dienotset(100, argv[as]) ;
+    x = defaultval ;
+  }
+  if (!x) blah.n = 0 ;
+  else
+  {
+    register int r ;
+    if (!stralloc_cats(&info->values, x)) goto err ;
+    r = el_transform(&info->values, blah.value, &si) ;
+    if (r < 0) goto err ;
+    blah.n = r ;
+  }
+  if (!genalloc_append(elsubst_t, &info->data, &blah)) goto err ;
+  return localopt.ind + 1 + as ;
+
+ err:
+  info->vars.len = blah.var ;
+  info->values.len = blah.value ;
+  return -1 ;
+}
+
+int exlsn_import (int argc, char const **argv, char const *const *envp, exlsn_t *info)
+{
+  return exlsn_import_as(argc, argv, envp, info, 0) ;
+}
+
+int exlsn_importas (int argc, char const **argv, char const *const *envp, exlsn_t *info)
+{
+  return exlsn_import_as(argc, argv, envp, info, 1) ;
+}
diff --git a/src/libexecline/exlsn_main.c b/src/libexecline/exlsn_main.c
new file mode 100644
index 0000000..e50fc2d
--- /dev/null
+++ b/src/libexecline/exlsn_main.c
@@ -0,0 +1,19 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include <execline/execline.h>
+#include "exlsn.h"
+
+void exlsn_main (int argc, char const **argv, char const *const *envp, exlsnfunc_t *func, char const *usage)
+{
+  exlsn_t info = EXLSN_ZERO ;
+  int r = (*func)(argc, argv, envp, &info) ;
+  if (r < 0) switch (r)
+  {
+    case -3 : strerr_dieusage(100, usage) ;
+    case -2 : strerr_dief1x(111, "bad substitution key") ;
+    case -1 : strerr_diefu1sys(111, "complete exlsn function") ;
+    default : strerr_diefu2x(111, "complete exlsn function", ": unknown error") ;
+  }
+  el_substandrun(argc-r, argv+r, envp, &info) ;
+}
diff --git a/src/libexecline/exlsn_multidefine.c b/src/libexecline/exlsn_multidefine.c
new file mode 100644
index 0000000..e64cbfd
--- /dev/null
+++ b/src/libexecline/exlsn_multidefine.c
@@ -0,0 +1,79 @@
+/* ISC license. */
+
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <execline/execline.h>
+#include "exlsn.h"
+
+int exlsn_multidefine (int argc, char const **argv, char const *const *envp, exlsn_t *info)
+{
+  eltransforminfo_t si = ELTRANSFORMINFO_ZERO ;
+  subgetopt_t localopt = SUBGETOPT_ZERO ;
+  unsigned int varbase = info->vars.len ;
+  unsigned int valbase = info->values.len ;
+  unsigned int pos = valbase ;
+  unsigned int i = 0 ;
+  unsigned int max ;
+  char const *x ;
+  int argc1 ;
+  int zeroword = 0, likeread = 0 ;
+  si.split = 1 ;
+  for (;;)
+  {
+    register int opt = subgetopt_r(argc, argv, "0rnCcd:", &localopt) ;
+    if (opt < 0) break ;
+    switch (opt)
+    {
+      case '0' : zeroword = 1 ; break ;
+      case 'r' : likeread = 1 ; break ;
+      case 'n' : si.chomp = 1 ; break ;
+      case 'C' : si.crunch = 1 ; break ;
+      case 'c' : si.crunch = 0 ; break ;
+      case 'd' : si.delim = localopt.arg ; break ;
+      default : return -3 ;
+    }
+  }
+  argc -= localopt.ind ; argv += localopt.ind ;
+
+  if (argc < 2) return -3 ;
+  x = argv[0] ;
+  argv++ ; argc-- ;
+  argc1 = el_semicolon(argv) ;
+  if (argc1 >= argc) return -3 ;
+  if (!stralloc_cats(&info->values, x)) return -1 ;
+  {
+    register int r = el_transform(&info->values, valbase, &si) ;
+    if (r < 0) goto err ;
+    max = r ;
+  }
+  if (!stralloc_0(&info->values)) goto err ;
+  for (; i < (unsigned int)argc1 ; i++)
+  {
+    if (*argv[i])
+    {
+      elsubst_t blah ;
+      blah.var = info->vars.len ;
+      if (el_vardupl(argv[i], info->vars.s, info->vars.len)) goto err2 ;
+      if (!stralloc_catb(&info->vars, argv[i], str_len(argv[i]) + 1)) goto err ;
+      blah.value = i < max ? pos : info->values.len - 1 ;
+      blah.n = (i < max) || !zeroword ;
+      if (!genalloc_append(elsubst_t, &info->data, &blah)) goto err ;
+    }
+    if (i < max) pos += str_len(info->values.s + pos) + 1 ;
+  }
+  if ((i < max) && likeread) genalloc_s(elsubst_t, &info->data)[i-1].n = max - i + 1 ;
+
+  (void)envp ;
+  return localopt.ind + argc1 + 2 ;
+
+ err:
+  info->vars.len = varbase ;
+  info->values.len = valbase ;
+  return -1 ;
+ err2:
+  info->vars.len = varbase ;
+  info->values.len = valbase ;
+  return -2 ;
+}
diff --git a/tools/gen-deps.sh b/tools/gen-deps.sh
new file mode 100755
index 0000000..af31259
--- /dev/null
+++ b/tools/gen-deps.sh
@@ -0,0 +1,79 @@
+#!/bin/sh -e
+
+. package/info
+
+echo '#'
+echo '# This file has been generated by tools/gen-deps.sh'
+echo '#'
+echo
+
+for dir in src/include/${package} src/* ; do
+  for file in $(ls -1 $dir | grep -- \\.h$) ; do
+    {
+      grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ;
+      grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2
+    } | sort -u | {
+      deps=
+      while read dep ; do
+        if echo $dep | grep -q "^${package}/" ; then
+          deps="$deps src/include/$dep"
+        elif test -f "${dir}/$dep" ; then
+          deps="$deps ${dir}/$dep"
+        else
+          deps="$deps src/include-local/$dep"
+        fi
+      done
+      if test -n "$deps" ; then
+        echo "${dir}/${file}:${deps}"
+      fi
+    }
+  done
+done
+
+for dir in src/* ; do
+  for file in $(ls -1 $dir | grep -- \\.c$) ; do
+    {
+      grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ;
+      grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2
+    } | sort -u | {
+      deps=" ${dir}/$file"
+      while read dep ; do
+        if echo $dep | grep -q "^${package}/" ; then
+          deps="$deps src/include/$dep"
+        elif test -f "${dir}/$dep" ; then
+          deps="$deps ${dir}/$dep"
+        else
+          deps="$deps src/include-local/$dep"
+        fi
+      done
+      o=$(echo $file | sed s/\\.c$/.o/)
+      lo=$(echo $file | sed s/\\.c$/.lo/)
+      echo "${dir}/${o} ${dir}/${lo}:${deps}"
+    }
+  done
+done
+echo
+
+for dir in $(ls -1 src | grep -v ^include) ; do
+  for file in $(ls -1 src/$dir/deps-lib) ; do
+    deps=
+    while read dep ; do
+      deps="$deps src/$dir/$dep"
+    done < src/$dir/deps-lib/$file
+    echo "lib$file.a: $deps"
+    if test -x "src/$dir/deps-lib/$file" ; then
+      echo "lib${file}.so: $(echo "$deps" | sed 's/\.o/.lo/g')"
+    fi
+  done
+
+  for file in $(ls -1 src/$dir/deps-exe) ; do
+    deps=
+    while read dep ; do
+      if echo $dep | grep -q -- \\.o$ ; then
+        dep="src/$dir/$dep"
+      fi
+      deps="$deps $dep"
+    done < src/$dir/deps-exe/$file
+    echo "$file: src/$dir/$file.o$deps"
+  done
+done
diff --git a/tools/install.sh b/tools/install.sh
new file mode 100755
index 0000000..89f9428
--- /dev/null
+++ b/tools/install.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+usage() {
+  echo "usage: $0 [-D] [-l] [-m mode] src dst" 1>&2
+  exit 1
+}
+
+mkdirp=false
+symlink=false
+mode=0755
+
+while getopts Dlm: name ; do
+  case "$name" in
+    D) mkdirp=true ;;
+    l) symlink=true ;;
+    m) mode=$OPTARG ;;
+    ?) usage ;;
+  esac
+done
+shift $(($OPTIND - 1))
+
+test "$#" -eq 2 || usage
+src=$1
+dst=$2
+tmp="$dst.tmp.$$"
+
+case "$dst" in
+  */) echo "$0: $dst ends in /" 1>&2 ; exit 1 ;;
+esac
+
+set -C
+set -e
+
+if $mkdirp ; then
+  umask 022
+  case "$2" in
+    */*) mkdir -p "${dst%/*}" ;;
+  esac
+fi
+
+trap 'rm -f "$tmp"' EXIT INT QUIT TERM HUP
+
+umask 077
+
+if $symlink ; then
+  ln -s "$src" "$tmp"
+else
+  cat < "$1" > "$tmp"
+  chmod "$mode" "$tmp"
+fi
+
+mv -f "$tmp" "$dst"
+if test -d "$dst" ; then
+  rm -f "$dst/$(basename $tmp)"
+  if $symlink ; then
+    mkdir "$tmp"
+    ln -s "$src" "$tmp/$(basename $dst)"
+    mv -f "$tmp/$(basename $dst)" "${dst%/*}"
+    rmdir "$tmp"
+  else
+    echo "$0: $dst is a directory" 1>&2
+    exit 1
+  fi
+fi