diff options
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.* @@ -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> @@ -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 @@ -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. @@ -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: @@ -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> : 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> : 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> : 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> ? </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> ? +<tt>#!/usr/bin/execlineb</tt> ? 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 ? 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 ?) - 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> : <em>chomp</em>. Do not print the last +<em>delimchar</em>. </li> + <li> <tt>-d</tt> <em>delimchar</em> : 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> : 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 -d ""</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 <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 1</tt> is read from a block and spawned; then +<tt>echo 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 1</tt> and +<tt>echo 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 ?) </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ë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> \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 ""</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 ""</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 -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> <em>sharp</em> : substitute at least +<em>sharp</em>+1 positional parameters, from 0 to +max(<em>n</em>, <em>sharp</em>). If <em>n</em><<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 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> : verbose. If there is a problem while globbing, print +a warning message on stderr. </li> + <li> <tt>-w</tt> : 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> : sort the matches. By default, the results are +left unsorted. </li> + <li> <tt>-m</tt> : mark. Append a slash to each word that corresponds +to a directory. </li> + <li> <tt>-e</tt> : 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> : 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> : keep the <tt>PATH</tt> environment variable. </li> + <li> <tt>-c</tt> : 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> : <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> : <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> : empty the environment. <em>prog</em> is executed with no environment variables. <strong>Warning: </strong><em>prog</em> will run with an empty PATH, so make sure it does not rely on it. </li> + <li> <tt>-l</tt> : login. Prepends <em>prog</em>'s argv[0] with a dash. </li> + <li> <tt>-a <em>argv0</em></tt> : 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 -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 <em>script</em></tt> : 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 <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 ! </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> : 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><&-'</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> : 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>>&<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>>&<em>b</em> <em>b</em><&-'</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> : 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> : 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> <em>breakcodes</em> : <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> : 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> <em>breakcodes</em> : <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 ! </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 KB - and most simple scripts are smaller than that. Modern systems +have a much higher limit: for instance, it is 64 KB on FreeBSD-4.6, +and 128 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 ? 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 ?" +</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> + <instruction> = <> | external options <arglist> <instruction> | builtin options <arglist> <blocklist> <instruction> + <arglist> = <> | arg <arglist> + <blocklist> = <> | <block> <blocklist> + <block> = { <arglist> } | { <instrlist> } + <instrlist> = <> | <instruction> <instrlist> +</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 1</tt> <em>block</em> then execs into the +remaining <tt>echo 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> : 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><<</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> : treat a crash of <em>prog1</em> as a non-zero ("false") exit. + <li> <tt>-n</tt> : negate the test (exit on true, exec into <em>prog2</em> on false) </li> + <li> <tt>-x</tt> <em>exitcode</em> : exit <em>exitcode</em> instead of 1 if the test fails. </li> + <li> <tt>-t</tt> : 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> : negate the test. </li> + <li> <tt>-X</tt> : 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> : do not exit if <em>progif</em> crashes; instead, +proceed as if the test had returned false. </li> + <li> <tt>-n</tt> : 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> : if <em>progif</em> crashes, do not exit; proceed +as if it had returned false. </li> + <li> <tt>-s</tt> : 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> <em>default</em> : 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 ""</tt>. </li> + <li> <tt>-i</tt> : 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 ? </h2> + +<p> + execline is a (non-interactive) scripting language, like <tt>sh</tt> ; +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> ?</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ë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> <em>exitcodes</em> : <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> : 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> : 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> : 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> : 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> : make <em>prog1...</em> the writer and +<em>prog2...</em> the reader. This is the default. </li> + <li> <tt>-w</tt> : 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> : open <em>file</em> for reading. </li> + <li> <tt>-w</tt> : open <em>file</em> for writing, truncating it if it already exists. </li> + <li> <tt>-u</tt> : open <em>file</em> for reading and writing. </li> + <li> <tt>-a</tt> : open <em>file</em> for appending, creating it if it doesn't exist. </li> + <li> <tt>-c</tt> : open <em>file</em> for appending. Do not create it if it doesn't exist. </li> + <li> <tt>-x</tt> : open <em>file</em> for writing, creating it, failing if it already exists. </li> + <li> <tt>-n</tt> : open <em>file</em> in non-blocking mode. </li> + <li> <tt>-b</tt> : 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><<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>><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><><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>>><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> : 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 && make & & 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> : 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 |