tailsh
- #!/bin/zsh
- #
- # tailsh - runs a file through sh, ignoring lines before
- # the line reading "#!". Useful if someone mails you a shell
- # script and you're too lazy to save to a temp file, strip the stuff
- # from the top, chmod it to be executable, run the script and
- # delete the temporary file.
- #
- # It works on things other than /bin/sh. What it actually
- # does is pass the lines following the first #! line into the
- # command named on the #! line.
- #
- # Usage:
- # tailsh [-p path] [-c command] [-o options] [file . . .]
- #
- # Where
- # -p path means tailsh will perform a cd to path before running
- # each file. Files given as parameters to tailsh
- # are intelligently renamed so that they can still be found
- # if relative paths were given.
- # -c command overrides the command to run the script through.
- # useful if the script was written by someone who puts
- # /bin/sh in a different place. This switch overrides
- # *all* files given on the line, even though each file
- # may have a different command in its #! line
- # -o options supplies options to the file, making it believe
- # they were supplied as command-line options.
- # This really only works with scripts that invoke
- # a shell of some sort with any reliability. All it
- # does is put them after the temp file name it creates.
- # Importantly, it works with sh and zsh.
- # file is the name of a file to be sourced in this fashion.
- # If no files are supplied, tailsh will run the entire
- # standard input as if it were a single file. tailsh is
- # more efficient in stdin mode.
- #
- # Examples:
- # tailsh -o '-c' part1of3.shar part2of3.shar part3of3.shar
- # Run each of the three shar files you were just mailed.
- # Depending on what your version of shar is like it should
- # completely decode the whole thing.
- #
- # Written by Deborah Pickett <debbiep@csse.monash.edu.au>
- #
- # To do: Maybe find a way of doing the stdin style on a filename
- # provided. On the other hand, it's more general if you can supply
- # a filename rather than rely on stdin. The present compromise
- # is probably best. Redirect from stdin if you can, otherwise
- # name a file. The down side is that it doesn't appear to work for
- # substituted files, i.e., <(some command). Use =(some command)
- # instead.
- # Also need a better mechanism for passing perceived parameters;
- # the -o switch isn't very portable.
- # local variables
- local neg sws file line linenum opt newpath overridecmd passopts\
- usage="Usage: $0:t [-p path] [-c command] [-o options] file . . ." \
- excl=!
- # fetch options
- while getopts :p:c:o: opt
- do
- case $opt in
- 'p') newpath=$OPTARG ;;
- 'c') overridecmd=$OPTARG ;;
- 'o') passopts=$OPTARG ;;
- ':') echo $usage >&2 ; return 1 ;;
- '?') echo $usage >&2 ; return 1 ;;
- esac
- done
- # put options behind us
- shift $[ $OPTIND - 1 ]
- # extendedglob and shwordsplit need to be set thusly
- [[ -o extendedglob ]] || neg=1
- setopt extendedglob
- [[ -o shwordsplit ]] && sws=1
- unsetopt shwordsplit
- # tailsh proper
- if [[ $# -ne 0 ]]
- then
- # files specified - read them in one at a time
- for file
- do
- # if file doesn't start with "/" it was relative and needs $PWD
- # prepended so that it still can be found when -c was used.
- if [[ "$newpath" != "" && "$file[1]" != "/" ]]
- then
- file=$PWD/$file
- fi
- # check for existence of file
- if [[ ! -r $file ]]
- then
- echo "${0:t}: $file: no such file" >&2
- continue
- fi
- # find first line containing a #!
- # This sed script outputs the line number followed by the command
- # itself. It exits after the first #! is found; this avoids
- # duplicates. It's simpler to extract it from an array, thus
- # the surrounding brackets.
- # (This looks hideous and there's good reason.
- # Owing to what I think is a misfeature of zsh's handling of
- # ! characters, I can't quote any as "\!" or '\!' because it
- # is treated as ! in a zsh function if nobanghist is off,
- # \! in a zsh function if nobanghist is on,
- # and \! in a zsh script regardless of the settings of nobanghist.
- # I solve this by setting a shell variable to contain the
- # exclamation mark. [This has probably been solved in zsh version 3,
- # but I haven't changed it because this way we get backwards
- # compatibility.] )
- line=($(sed -n '/#'$excl'/{=;s/#'$excl' *//p;q;}' < $file))
- if [[ "$line" != "" ]]
- then
- # there was a #! - find its line number.
- linenum=${line[1]}
- # cd to the path given by -c if it was specified
- if [[ "$newpath" != "" ]]
- then
- cd $newpath
- fi
- # run the command. "eval" is used in case the command
- # was somthing like "cat >foo" and the shell needs to
- # see it. It also helps sidestep that annoying ! feature.
- eval ${overridecmd:-${line[2,-1]}} \
- =(tail +$[$linenum+1] $file) ${=passopts}
- # cd back to the old path to make it look like nothing happened.
- if [[ "$newpath" != "" ]]
- then
- cd $OLDPWD
- fi
- else
- # file didn't have a #! - ignore it.
- echo "${0:t}: $file: no #"\!" line" >&2
- fi
- done
- else
- # no files named - must be wanting stdin mode.
- linenum=0;
- # keep reading lines till something happens.
- while read -r -u0 line
- do
- if [[ $line[1,2] = \#\! ]]
- then
- # found a #! - flag the fact we have found it.
- linenum=1
- if [[ "$newpath" != "" ]]
- then
- cd $newpath
- fi
- # duplicate our stdin and give it to the command.
- # If no command is supplied (i.e. the line was #! and nothing
- # else), it's a null command, which defaults to calling
- # cat(1) implicitly. Just an interesting side-effect.
- eval ${overridecmd:-${line##\#\! #}} "<&0" "${=passopts}"
- if [[ "$newpath" != "" ]]
- then
- cd $OLDPWD
- fi
- # and break out of the loop.
- break
- fi
- done
- if [[ $linenum = 0 ]]
- then
- # No #! line - output warning message.
- echo "${0:t}: stdin: no #"\!" line" >&2
- fi
- fi
- # restore option settings
- [[ "$neg" = 1 ]] && unsetopt extendedglob
- [[ "$sws" = 1 ]] && setopt shwordsplit