The d command
Run make or other commands in the background, with output to a file.

Usage: d [ -f file ] c[n][a][p] cmd arg ...
or:    d [ -f file ] m[n][a][p] [ make_arg ... ]
or:    d [ -f file ] s[n][a][p] [ additional_arg ... ]
or:    d [ -f file ] h

Do a task in the background, with its stdout and stderr redirected to a file called out or to file or to a file named by a $yostD_OUTFILE environment variable. The d command usually does
   mv ,out out
and then creates a new out file, but d can be told to append to out instead.

The task argument is composed of individual letters:

One of these is required:
c Do the following command - redirection and piping work if quoted.
m Do a make.
s Do the same command again (extract the last command from out, and run it again (without 'nice' unless you ask for it again).
h Print a history of what happened in this out file.
These are optional:
n Do it nicely.
a Append to out.
p Just print the command that would be executed; don't run it.

The d command comes along with several helper shell functions:

q tail -f out
qq cat out
qt tail -20 out
cq clear ; q
cqq clear ; qq
wq wait "$@" ; printf "\b" ; qq
wqt wait "$@" ; printf "\b" ; qt

The output from d into the file out consists of
   prologue
   output from the command
   epilogue
as shown in the example below, which does this:

223 Z% d c 'sleep 5 ; echo Hello folks'
[3] 3937
224 Z% 
[3]  + 3937 done       yostD_runCmd
224 Z% d sna
[ sleep 5 ; echo Hello folks ]
[3] 3965
225 Z% 
[3]  + 3965 done       yostD_runCmd
225 Z% qq
=dbegin 2006-07-08T16:28:11 PDT Sat
=dhost  Yost.com
=dwd    /Users/yost
=dcmd   sleep 5 ; echo Hello folks   
Hello folks
		  5.06 real         0.00 user         0.02 sys
=dstat  0
=dend   2006-07-08T16:28:17 PDT Sat
======= =======================================================================
=dbegin 2006-07-08T16:28:19 PDT Sat
=dhost  Yost.com
=dwd    /Users/yost
=dcmd   nice sleep 5 ; echo Hello folks    
Hello folks
		  5.06 real         0.00 user         0.02 sys
=dstat  0
=dend   2006-07-08T16:28:24 PDT Sat
======= =======================================================================
226 Z% d sp
sleep 5 ; echo Hello folks     
227 Z% d h
=dcmd   sleep 5 ; echo Hello folks   
=dstat  0
=dcmd   sleep 5 ; echo Hello folks    
=dstat  0
228 Z% 

To use the d command and its helpers, you need to source d.sh from your zsh, bash, ksh, or sh. If your system does not have the tac command, you will need to install it for the s command to work.

Here is d.sh:

# Source this file into sh, ksh, bash, or zsh
# See http://Yost.com/computers/d
#
# Needs the 'tac' command, which cats lines from end to beginning of file.
# Requires shell procedures.
#
# Bugs:
#  * Quoting arguments doesn't work properly.
#  * The background job is shown in a weird way, not showing the command(s)
#    you're really running.
#  * If you have a bad old shell, there won't be a job number handle on the 
#    backgrounded task
#
# The timing of commands could be better.
#
# I have tested this only with zsh 4.2.3
#
# Author: Dave@Yost.com
#   2.0 1991-04-xx
#   4.0 2004-12-19
#   5.0 2006-07-09
#   5.1 2006-07-10
#   5.2 2006-07-14
#   5.3 2006-12-14 fix "d s" bug when out file doesn't exist
#   5.4 2006-12-27 Make it work with bash, ksh, and sh

#-------------------------------------------------------------------------------

yostD_usage () {
  echo 1>&2 "
Usage: d [ -f <file> ] c[n][a][p] <cmd> <arg> ...
or:    d [ -f <file> ] m[n][a][p] [ <make-arg> ... ]
or:    d [ -f <file> ] s[n][a][p] [ <additional-arg> ... ]
or:    d [ -f <file> ] h

Do a task in the background, with its stdout and stderr redirected to
a file called 'out' or <file> or named by \$yostD_OUTFILE (environment variable).
The d command usually does
  mv ,out out
before creating a new out file but can be told to append to out instead.

The first argument to the d command is called the task argument.
The following letters in the task argument determine the task that is run:
  One of these is required:
    c - Do the following command - redirection and piping work (if escaped).
    m - Do a make.
    s - Do the same command again (extract the last command from <file>,
        and run it again (without 'nice' unless you ask for it again).
    h - Print a history of what happened in this out file (h by itself).
  These are optional:
    p - just print the command that would be executed; don't run it
    n - Do it 'nice'ly.
    a - Append to $yostD_OUTFILE

See http://Yost.com/computers/d
"
  return 2
}

#-------------------------------------------------------------------------------

d() {
  yostD_setOutFileVariableIfNotSet
  yostD_args "$@" \
  && \
  if [[ "$yostD_optPrint" = true ]] ; then
    yostD_print
  else
    yostD_run
  fi
}

#-------------------------------------------------------------------------------

yostD_setOutFileVariableIfNotSet() {
  if [[ -z "${yostD_OUTFILE:-}" ]] ; then yostD_OUTFILE=out ; fi
}

#-------------------------------------------------------------------------------

yostD_args() {

  yostD_optAppend=false
  yostD_optNice=
  yostD_optNicePrefix=
  yostD_optPrint=false
  yostD_optPrintAll=false
  yostD_optSame=false

  if [[ $# = 0 ]] ; then return $(yostD_usage) ; fi

  if [[ "${1:-}" = -f ]] ; then
    yostD_OUTFILE=$2
    shift
    shift
  fi

  yostD_task=$1
  shift

  # There must be exactly one command.

  case "$yostD_task" in
  ""|*[\`~!@#$%^\&*\(\)_-+={[}\]|\\:\;'"'"'"\<,\>.?/bdefgijkloqrtuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]*)
    return $(yostD_usage)
    ;;
  h)
    yostD_optPrint=true
    yostD_optPrintAll=true
    ;;
  *c*m* | *m*c* | *c*s* | *s*c* | *m*s* | *s*m* )
    return $(yostD_usage)
    ;;
  *c*)
    if [[ $# = 0 ]] ; then
      return $(yostD_usage)
    else
      yostD_cmd="$@"
    fi
    ;;
  *m*)
    yostD_cmd=make
    if [[ -n "$@" ]] ; then
      yostD_cmd="$yostD_cmd $@"
    fi
    ;;
  *s*)
    yostD_optSame=true
    yostD_cmd="$(yostD_findSameCmd)"
    if [[ $? != 0 ]] ; then
      return 2
    fi
    if [[ -n "$@" ]] ; then
      # Shell syntax.  Hrmph.
      #if [[ $1 = ${yostD_cmd[1]} ]] ; then
      #  echo 1>&2 "d: extra args to s are probably wrong"
      #  return 2
      #fi
      yostD_cmd="$yostD_cmd $@"
    fi
    ;;
  *)
    return $(yostD_usage)
  esac

  # And there can be optional modifiers.
  
  case "$yostD_task" in
  *a*)
    yostD_optAppend=true
  esac

  case "$yostD_task" in
  *n*)
    yostD_optNice="nice"
    yostD_optNicePrefix="nice "
  esac

  case "$yostD_task" in
  *p*)
    yostD_optPrint=true
  esac

  return $(yostD_ensureShellSet)
}

#-------------------------------------------------------------------------------

yostD_findSameCmd() {
  if [[ ! -r $yostD_OUTFILE ]] ; then
    echo 1>&2 "d: can't run same command again; can't read file '$yostD_OUTFILE'"
    return 2
  fi
  yostD_cmd=$(
    tac $yostD_OUTFILE 2> /dev/null \
    | sed -n '
        /^=dcmd *nice  */{
          s///p
          q
        }
        /^=dcmd  */{
          s///p
          q
        }
      '
  )
  if [[ -z "$yostD_cmd" ]] ; then
    echo 1>&2 "d: can't run same command again; can't find usable command in file '$yostD_OUTFILE'"
    return 2
  fi
  echo "$yostD_cmd"
}

#-------------------------------------------------------------------------------

yostD_ensureShellSet() {
  if [[ -n "$SHELL:-" ]] ; then
       SHELL=$(which zsh) \
    || SHELL=$(which bash) \
    || SHELL=$(which ksh) \
    || SHELL=$(which sh) \
    || (
      echo 1>&2 "d: Can't operate because you have no SHELL environment variable"
      return 2
    )
  fi
}

#-------------------------------------------------------------------------------

yostD_print() {
  if [[ "$yostD_optPrintAll" = true ]] ; then
    sed -n '
        /^=dcmd/p
        /^=dstat/p
        ' $yostD_OUTFILE
  else
    echo ${yostD_optNicePrefix}$yostD_cmd
  fi
  return 0
}

#-------------------------------------------------------------------------------

yostD_run() {
  if [[ "$yostD_optSame"    = true ]] ; then
    echo 1>&2 "[ $(yostD_print) ]"
  fi
  if [[ "$yostD_optAppend" != true ]] ; then
    yostD_mvOutFile
  fi \
  && \
  yostD_runCmd &
}

#-------------------------------------------------------------------------------

yostD_mvOutFile() {
  mv -f $yostD_OUTFILE $(yostD_mkBackupName "${yostD_OUTFILE:-out}") 2> /dev/null
  echo -n '' >> $yostD_OUTFILE
}

#-------------------------------------------------------------------------------

yostD_runInternal() {
  if [[ -n "$yostD_optNice" ]] ; then
    time $yostD_optNice $SHELL -c "$yostD_cmd"
  else
    time                $SHELL -c "$yostD_cmd"
  fi
}

yostD_runCmd() {
  {
    # We use sh -c so pipes and semcolons work.
    yostD_outputPrologue \
    && \
    runInternal
    yostD_outputEpilogue
    yostD_beep
  } \
  >> $yostD_OUTFILE 2>&1
}

#-------------------------------------------------------------------------------

yostD_outputPrologue() {
  echo '=dbegin '$(yostD_formattedDate)"
=dhost  ${HOST:-$(hostname)}
=dwd    $(pwd)
=dcmd   ${yostD_optNicePrefix}$yostD_cmd"
}

#-------------------------------------------------------------------------------

yostD_outputEpilogue() {
  echo "=dstat  $?
=dend   "`yostD_formattedDate`"
======= ======================================================================="
}

#-------------------------------------------------------------------------------

yostD_beep() {
  printf "\b" > /dev/tty
}

#-------------------------------------------------------------------------------

yostD_formattedDate() {
  date '+%Y-%m-%dT%H:%M:%S %Z %a'
}


#-------------------------------------------------------------------------------

yostD_mkBackupName() {
  echo ,"$1"
}

#===============================================================================

# Also useful:
# j() { jobs "$@" ; }
# k() { kill %%   ; }

if ! type q   \
&& ! type qq  \
&& ! type qt  \
&& ! type cq  \
&& ! type cqq \
&& ! type wq  \
&& ! type wqt \
&& ! type mvout ; then
  yostD_qOK=true
else
  yostD_qOK=
fi > /dev/null 2>&1

if [[ $yostD_qOK = true ]] ; then
  q    () { tail -f  ${yostD_OUTFILE:-out} ; }
  qq   () { cat      ${yostD_OUTFILE:-out} ; }
  qt   () { tail -20 ${yostD_OUTFILE:-out} ; }
  cq   () { clear                   ; q  ; }
  cqq  () { clear                   ; qq ; }
  wq   () { wait "$@" ; printf "\b" ; qq ; }
  wqt  () { wait "$@" ; printf "\b" ; qt ; }
  mvout() { mv -f ${yostD_OUTFILE:-out} $(yostD_mkBackupName "${yostD_OUTFILE:-out}") ; }
else
  echo 1>&2 "d.sh can't create the 'd' helper commands because of conflicts"
fi

#-------------------------------------------------------------------------------

# Copyright 2006 Dave Yost <Dave@Yost.com>
# This version is
#   d.sh 5.4 2006-12-27
# which at time of this publication can be found at:
#   http://Yost.com/computers/d
# Redistribution and use in the form of source code or derivative data built 
# from the source code, with or without modification, are permitted provided 
# that the following conditions are met:
# 1. THE USER AGREES THAT THERE IS NO WARRANTY.
# 2. If and only if appropriate, the above phrase "This version is" must be 
#    followed by the phrase "a modified form of" or "extracted from" or 
#    "extracted and modified from".
# 3. Redistributions of source code must retain this notice intact.
# 4. Redistributions in the form of derivative data built from the source 
#    code must reproduce this notice intact in the documentation and/or other 
#    materials provided with the distribution, and each file in the derivative 
#    data must reproduce any Yost.com URI included in the original distribution.
# 5. Neither the name of Dave Yost nor the names of its contributors may be 
#    used to endorse or promote products derived from this software without 
#    specific prior written permission.
# This notice comprises all text from "Copyright" above through the end of 
# this sentence.

I have tested this only with zsh 4.2.3. Please feel free to send bug fixes.


http://Yost.com/computers/d/ - this page
1991-04-xx 2.0 Separate, sourceable file for zsh
2004-12-19 4.0
2006-07-09 5.0 Published on the web
2006-07-09 5.1 License fixed
2006-07-14 5.2 'd s' now echoes the command it will execute.
2006-12-14 5.3 fix "d s" bug when out file doesn't exist
2006-12-27 5.4 make it work with bash, ksh, and sh