[Home] Zsh logo

tailsh

Zsh Wizard

Download tailsh Return to Examples
  1. #!/bin/zsh
  2. #
  3. # tailsh - runs a file through sh, ignoring lines before
  4. # the line reading "#!". Useful if someone mails you a shell
  5. # script and you're too lazy to save to a temp file, strip the stuff
  6. # from the top, chmod it to be executable, run the script and
  7. # delete the temporary file.
  8. #
  9. # It works on things other than /bin/sh. What it actually
  10. # does is pass the lines following the first #! line into the
  11. # command named on the #! line.
  12. #
  13. # Usage:
  14. # tailsh [-p path] [-c command] [-o options] [file . . .]
  15. #
  16. # Where
  17. # -p path means tailsh will perform a cd to path before running
  18. # each file. Files given as parameters to tailsh
  19. # are intelligently renamed so that they can still be found
  20. # if relative paths were given.
  21. # -c command overrides the command to run the script through.
  22. # useful if the script was written by someone who puts
  23. # /bin/sh in a different place. This switch overrides
  24. # *all* files given on the line, even though each file
  25. # may have a different command in its #! line
  26. # -o options supplies options to the file, making it believe
  27. # they were supplied as command-line options.
  28. # This really only works with scripts that invoke
  29. # a shell of some sort with any reliability. All it
  30. # does is put them after the temp file name it creates.
  31. # Importantly, it works with sh and zsh.
  32. # file is the name of a file to be sourced in this fashion.
  33. # If no files are supplied, tailsh will run the entire
  34. # standard input as if it were a single file. tailsh is
  35. # more efficient in stdin mode.
  36. #
  37. # Examples:
  38. # tailsh -o '-c' part1of3.shar part2of3.shar part3of3.shar
  39. # Run each of the three shar files you were just mailed.
  40. # Depending on what your version of shar is like it should
  41. # completely decode the whole thing.
  42. #
  43. # Written by Deborah Pickett <debbiep@csse.monash.edu.au>
  44. #
  45. # To do: Maybe find a way of doing the stdin style on a filename
  46. # provided. On the other hand, it's more general if you can supply
  47. # a filename rather than rely on stdin. The present compromise
  48. # is probably best. Redirect from stdin if you can, otherwise
  49. # name a file. The down side is that it doesn't appear to work for
  50. # substituted files, i.e., <(some command). Use =(some command)
  51. # instead.
  52. # Also need a better mechanism for passing perceived parameters;
  53. # the -o switch isn't very portable.
  54. # local variables
  55. local neg sws file line linenum opt newpath overridecmd passopts\
  56. usage="Usage: $0:t [-p path] [-c command] [-o options] file . . ." \
  57. excl=!
  58. # fetch options
  59. while getopts :p:c:o: opt
  60. do
  61. case $opt in
  62. 'p') newpath=$OPTARG ;;
  63. 'c') overridecmd=$OPTARG ;;
  64. 'o') passopts=$OPTARG ;;
  65. ':') echo $usage >&2 ; return 1 ;;
  66. '?') echo $usage >&2 ; return 1 ;;
  67. esac
  68. done
  69. # put options behind us
  70. shift $[ $OPTIND - 1 ]
  71. # extendedglob and shwordsplit need to be set thusly
  72. [[ -o extendedglob ]] || neg=1
  73. setopt extendedglob
  74. [[ -o shwordsplit ]] && sws=1
  75. unsetopt shwordsplit
  76. # tailsh proper
  77. if [[ $# -ne 0 ]]
  78. then
  79. # files specified - read them in one at a time
  80. for file
  81. do
  82. # if file doesn't start with "/" it was relative and needs $PWD
  83. # prepended so that it still can be found when -c was used.
  84. if [[ "$newpath" != "" && "$file[1]" != "/" ]]
  85. then
  86. file=$PWD/$file
  87. fi
  88. # check for existence of file
  89. if [[ ! -r $file ]]
  90. then
  91. echo "${0:t}: $file: no such file" >&2
  92. continue
  93. fi
  94. # find first line containing a #!
  95. # This sed script outputs the line number followed by the command
  96. # itself. It exits after the first #! is found; this avoids
  97. # duplicates. It's simpler to extract it from an array, thus
  98. # the surrounding brackets.
  99. # (This looks hideous and there's good reason.
  100. # Owing to what I think is a misfeature of zsh's handling of
  101. # ! characters, I can't quote any as "\!" or '\!' because it
  102. # is treated as ! in a zsh function if nobanghist is off,
  103. # \! in a zsh function if nobanghist is on,
  104. # and \! in a zsh script regardless of the settings of nobanghist.
  105. # I solve this by setting a shell variable to contain the
  106. # exclamation mark. [This has probably been solved in zsh version 3,
  107. # but I haven't changed it because this way we get backwards
  108. # compatibility.] )
  109. line=($(sed -n '/#'$excl'/{=;s/#'$excl' *//p;q;}' < $file))
  110. if [[ "$line" != "" ]]
  111. then
  112. # there was a #! - find its line number.
  113. linenum=${line[1]}
  114. # cd to the path given by -c if it was specified
  115. if [[ "$newpath" != "" ]]
  116. then
  117. cd $newpath
  118. fi
  119. # run the command. "eval" is used in case the command
  120. # was somthing like "cat >foo" and the shell needs to
  121. # see it. It also helps sidestep that annoying ! feature.
  122. eval ${overridecmd:-${line[2,-1]}} \
  123. =(tail +$[$linenum+1] $file) ${=passopts}
  124. # cd back to the old path to make it look like nothing happened.
  125. if [[ "$newpath" != "" ]]
  126. then
  127. cd $OLDPWD
  128. fi
  129. else
  130. # file didn't have a #! - ignore it.
  131. echo "${0:t}: $file: no #"\!" line" >&2
  132. fi
  133. done
  134. else
  135. # no files named - must be wanting stdin mode.
  136. linenum=0;
  137. # keep reading lines till something happens.
  138. while read -r -u0 line
  139. do
  140. if [[ $line[1,2] = \#\! ]]
  141. then
  142. # found a #! - flag the fact we have found it.
  143. linenum=1
  144. if [[ "$newpath" != "" ]]
  145. then
  146. cd $newpath
  147. fi
  148. # duplicate our stdin and give it to the command.
  149. # If no command is supplied (i.e. the line was #! and nothing
  150. # else), it's a null command, which defaults to calling
  151. # cat(1) implicitly. Just an interesting side-effect.
  152. eval ${overridecmd:-${line##\#\! #}} "<&0" "${=passopts}"
  153. if [[ "$newpath" != "" ]]
  154. then
  155. cd $OLDPWD
  156. fi
  157. # and break out of the loop.
  158. break
  159. fi
  160. done
  161. if [[ $linenum = 0 ]]
  162. then
  163. # No #! line - output warning message.
  164. echo "${0:t}: stdin: no #"\!" line" >&2
  165. fi
  166. fi
  167. # restore option settings
  168. [[ "$neg" = 1 ]] && unsetopt extendedglob
  169. [[ "$sws" = 1 ]] && setopt shwordsplit