diff lisp/modes/sh-script.el @ 2:ac2d302a0011 r19-15b2

Import from CVS: tag r19-15b2
author cvs
date Mon, 13 Aug 2007 08:46:35 +0200
parents
children 4103f0995bd7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/modes/sh-script.el	Mon Aug 13 08:46:35 2007 +0200
@@ -0,0 +1,1397 @@
+;;; sh-script.el --- shell-script editing commands for Emacs
+
+;; Copyright (C) 1993, 1994, 1995, 1996 by Free Software Foundation, Inc.
+
+;; Author: Daniel.Pfeiffer@Informatik.START.dbp.de, fax (+49 69) 7588-2389
+;; Version: 2.0e
+;; Maintainer: FSF
+;; Keywords: languages, unix
+
+;; This file is part of XEmacs.
+
+;; XEmacs is free software; you can redistribute it and/or modify it
+;; under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; XEmacs is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with XEmacs; see the file COPYING.  If not, write to the Free
+;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+;; 02111-1307, USA.
+
+;;; Synched up with: FSF 19.34.
+
+;;; Commentary:
+
+;; Major mode for editing shell scripts.  Bourne, C and rc shells as well
+;; as various derivatives are supported and easily derived from.  Structured
+;; statements can be inserted with one command or abbrev.  Completion is
+;; available for filenames, variables known from the script, the shell and
+;; the environment as well as commands.
+
+;;; Known Bugs:
+
+;; - In Bourne the keyword `in' is not anchored to case, for, select ...
+;; - Variables in `"' strings aren't fontified because there's no way of
+;;   syntactically distinguishing those from `'' strings.
+
+;;; Code:
+
+;; page 1:	variables and settings
+;; page 2:	mode-command and utility functions
+;; page 3:	statement syntax-commands for various shells
+;; page 4:	various other commands
+
+(require 'executable)
+
+(defvar sh-ancestor-alist
+  '((ash . sh)
+    (bash . jsh)
+    (dtksh . ksh)
+    (es . rc)
+    (itcsh . tcsh)
+    (jcsh . csh)
+    (jsh . sh)
+    (ksh . ksh88)
+    (ksh88 . jsh)
+    (oash . sh)
+    (pdksh . ksh88)
+    (posix . sh)
+    (tcsh . csh)
+    (wksh . ksh88)
+    (wsh . sh)
+    (zsh . ksh88))
+  "*Alist showing the direct ancestor of various shells.
+This is the basis for `sh-feature'.  See also `sh-alias-alist'.
+By default we have the following three hierarchies:
+
+csh		C Shell
+  jcsh		C Shell with Job Control
+  tcsh		Toronto C Shell
+    itcsh	? Toronto C Shell
+rc		Plan 9 Shell
+  es		Extensible Shell
+sh		Bourne Shell
+  ash		? Shell
+  jsh		Bourne Shell with Job Control
+    bash	GNU Bourne Again Shell
+    ksh88	Korn Shell '88
+      ksh	Korn Shell '93
+	dtksh	CDE Desktop Korn Shell
+      pdksh	Public Domain Korn Shell
+      wksh	Window Korn Shell
+      zsh	Z Shell
+  oash		SCO OA (curses) Shell
+  posix		IEEE 1003.2 Shell Standard
+  wsh		? Shell")
+
+
+(defvar sh-alias-alist
+  ;; XEmacs: Linux is spelled `linux'
+  (nconc (if (eq system-type 'linux)
+	     '((csh . tcsh)
+	       (ksh . pdksh)))
+	 ;; for the time being
+	 '((ksh . ksh88)
+	   (sh5 . sh)))
+  "*Alist for transforming shell names to what they really are.
+Use this where the name of the executable doesn't correspond to the type of
+shell it really is.")
+
+
+(defvar sh-shell-file (or (getenv "SHELL") "/bin/sh")
+  "*The executable file name for the shell being programmed.")
+
+
+(defvar sh-shell-arg
+  ;; bash does not need any options when run in a shell script,
+  '((bash)
+    (csh . "-f")
+    (pdksh)
+    ;; Bill_Mann@praxisint.com says -p with ksh can do harm.
+    (ksh88)
+    ;; -p means don't initialize functions from the environment.
+    (rc . "-p")
+    ;; Someone proposed -motif, but we don't want to encourage
+    ;; use of a non-free widget set.
+    (wksh)
+    ;; -f means don't run .zshrc.
+    (zsh . "-f"))
+  "*Single argument string for the magic number.  See `sh-feature'.")
+
+(defvar sh-shell-variables nil
+  "Alist of shell variable names that should be included in completion.
+These are used for completion in addition to all the variables named
+in `process-environment'.  Each element looks like (VAR . VAR), where
+the car and cdr are the same symbol.")
+
+(defvar sh-shell-variables-initialized nil
+  "Non-nil if `sh-shell-variables' is initialized.")
+
+(defun sh-canonicalize-shell (shell)
+  "Convert a shell name SHELL to the one we should handle it as."
+  (or (symbolp shell)
+      (setq shell (intern shell)))
+  (or (cdr (assq shell sh-alias-alist))
+      shell))
+
+(defvar sh-shell (sh-canonicalize-shell (file-name-nondirectory sh-shell-file))
+  "The shell being programmed.  This is set by \\[sh-set-shell].")
+
+;;; I turned off this feature because it doesn't permit typing commands
+;;; in the usual way without help.
+;;;(defvar sh-abbrevs
+;;;  '((csh eval sh-abbrevs shell
+;;;	 "switch" 'sh-case
+;;;	 "getopts" 'sh-while-getopts)
+
+;;;    (es eval sh-abbrevs shell
+;;;	"function" 'sh-function)
+
+;;;    (ksh88 eval sh-abbrevs sh
+;;;	   "select" 'sh-select)
+
+;;;    (rc eval sh-abbrevs shell
+;;;	"case" 'sh-case
+;;;	"function" 'sh-function)
+
+;;;    (sh eval sh-abbrevs shell
+;;;	"case" 'sh-case
+;;;	"function" 'sh-function
+;;;	"until" 'sh-until
+;;;	"getopts" 'sh-while-getopts)
+
+;;;    ;; The next entry is only used for defining the others
+;;;    (shell "for" sh-for
+;;;	   "loop" sh-indexed-loop
+;;;	   "if" sh-if
+;;;	   "tmpfile" sh-tmp-file
+;;;	   "while" sh-while)
+
+;;;    (zsh eval sh-abbrevs ksh88
+;;;	 "repeat" 'sh-repeat))
+;;;  "Abbrev-table used in Shell-Script mode.  See `sh-feature'.
+;;;Due to the internal workings of abbrev tables, the shell name symbol is
+;;;actually defined as the table for the like of \\[edit-abbrevs].")
+
+
+
+(defvar sh-mode-syntax-table
+  '((csh eval identity sh)
+    (sh eval sh-mode-syntax-table ()
+	;; #'s meanings depend on context which can't be expressed here
+	;; ?\# "<"
+	;; ?\^l ">#"
+	;; ?\n ">#"
+	?\" "\"\""
+	?\' "\"'"
+	?\` ".`"
+	?$ "_"
+	?! "_"
+	?% "_"
+	?: "_"
+	?. "_"
+	?^ "_"
+	?~ "_")
+    (rc eval sh-mode-syntax-table sh
+	?\" "_"
+	?\` "."))
+  "Syntax-table used in Shell-Script mode.  See `sh-feature'.")
+
+
+
+(defvar sh-mode-map
+  (let ((map (make-sparse-keymap))
+	(menu-map (make-sparse-keymap "Insert")))
+    (define-key map "\C-c(" 'sh-function)
+    (define-key map "\C-c\C-w" 'sh-while)
+    (define-key map "\C-c\C-u" 'sh-until)
+    (define-key map "\C-c\C-t" 'sh-tmp-file)
+    (define-key map "\C-c\C-s" 'sh-select)
+    (define-key map "\C-c\C-r" 'sh-repeat)
+    (define-key map "\C-c\C-o" 'sh-while-getopts)
+    (define-key map "\C-c\C-l" 'sh-indexed-loop)
+    (define-key map "\C-c\C-i" 'sh-if)
+    (define-key map "\C-c\C-f" 'sh-for)
+    (define-key map "\C-c\C-c" 'sh-case)
+
+    (define-key map "=" 'sh-assignment)
+    (define-key map "\C-c+" 'sh-add)
+    (define-key map "\C-\M-x" 'sh-execute-region)
+    (define-key map "\C-c\C-x" 'executable-interpret)
+    (define-key map "<" 'sh-maybe-here-document)
+    (define-key map "(" 'skeleton-pair-insert-maybe)
+    (define-key map "{" 'skeleton-pair-insert-maybe)
+    (define-key map "[" 'skeleton-pair-insert-maybe)
+    (define-key map "'" 'skeleton-pair-insert-maybe)
+    (define-key map "`" 'skeleton-pair-insert-maybe)
+    (define-key map "\"" 'skeleton-pair-insert-maybe)
+
+    (define-key map "\t" 'sh-indent-line)
+    (substitute-key-definition 'complete-tag 'comint-dynamic-complete
+			       map (current-global-map))
+    (substitute-key-definition 'newline-and-indent 'sh-newline-and-indent
+			       map (current-global-map))
+    (substitute-key-definition 'delete-backward-char
+			       'backward-delete-char-untabify
+			       map (current-global-map))
+    (define-key map "\C-c:" 'sh-set-shell)
+    (substitute-key-definition 'beginning-of-defun
+			       'sh-beginning-of-compound-command
+			       map (current-global-map))
+    (substitute-key-definition 'backward-sentence 'sh-beginning-of-command
+			       map (current-global-map))
+    (substitute-key-definition 'forward-sentence 'sh-end-of-command
+			       map (current-global-map))
+    (define-key map [menu-bar insert] (cons "Insert" menu-map))
+    (define-key menu-map [sh-while]	'("While Loop" . sh-while))
+    (define-key menu-map [sh-until]	'("Until Loop" . sh-until))
+    (define-key menu-map [sh-tmp-file]	'("Temporary File" . sh-tmp-file))
+    (define-key menu-map [sh-select]	'("Select Statement" . sh-select))
+    (define-key menu-map [sh-repeat]	'("Repeat Loop" . sh-repeat))
+    (define-key menu-map [sh-while-getopts]
+					'("Options Loop" . sh-while-getopts))
+    (define-key menu-map [sh-indexed-loop]
+					'("Indexed Loop" . sh-indexed-loop))
+    (define-key menu-map [sh-if]	'("If Statement" . sh-if))
+    (define-key menu-map [sh-for]	'("For Loop" . sh-for))
+    (define-key menu-map [sh-case]	'("Case Statement" . sh-case))
+    map)
+  "Keymap used in Shell-Script mode.")
+
+
+
+(defvar sh-dynamic-complete-functions
+  '(shell-dynamic-complete-environment-variable
+    shell-dynamic-complete-command
+    comint-dynamic-complete-filename)
+  "*Functions for doing TAB dynamic completion.")
+
+
+(defvar sh-require-final-newline
+  '((csh . t)
+    (pdksh . t)
+    (rc eval . require-final-newline)
+    (sh eval . require-final-newline))
+  "*Value of `require-final-newline' in Shell-Script mode buffers.
+See `sh-feature'.")
+
+
+(defvar sh-comment-prefix
+  '((csh . "\\(^\\|[^$]\\|\\$[^{]\\)")
+    (rc eval identity csh)
+    (sh . "\\(^\\|[ \t|&;()]\\)"))
+  "*Regexp matching what may come before a comment `#'.
+This must contain one \\(grouping\\) since it is the basis for fontifying
+comments as well as for `comment-start-skip'.
+See `sh-feature'.")
+
+
+(defvar sh-assignment-regexp
+  '((csh . "\\<\\([a-zA-Z0-9_]+\\)\\(\\[.+\\]\\)?[ \t]*[-+*/%^]?=")
+    ;; actually spaces are only supported in let/(( ... ))
+    (ksh88 . "\\<\\([a-zA-Z0-9_]+\\)\\(\\[.+\\]\\)?[ \t]*\\([-+*/%&|~^]\\|<<\\|>>\\)?=")
+    (rc . "\\<\\([a-zA-Z0-9_*]+\\)[ \t]*=")
+    (sh . "\\<\\([a-zA-Z0-9_]+\\)="))
+  "*Regexp for the variable name and what may follow in an assignment.
+First grouping matches the variable name.  This is upto and including the `='
+sign.  See `sh-feature'.")
+
+
+(defvar sh-indentation 4
+  "The width for further indentation in Shell-Script mode.")
+
+
+(defvar sh-remember-variable-min 3
+  "*Don't remember variables less than this length for completing reads.")
+
+
+(defvar sh-header-marker nil
+  "When non-`nil' is the end of header for prepending by \\[sh-execute-region].
+That command is also used for setting this variable.")
+
+
+(defvar sh-beginning-of-command
+  "\\([;({`|&]\\|\\`\\|[^\\]\n\\)[ \t]*\\([/~a-zA-Z0-9:]\\)"
+  "*Regexp to determine the beginning of a shell command.
+The actual command starts at the beginning of the second \\(grouping\\).")
+
+
+(defvar sh-end-of-command
+  "\\([/~a-zA-Z0-9:]\\)[ \t]*\\([;#)}`|&]\\|$\\)"
+  "*Regexp to determine the end of a shell command.
+The actual command ends at the end of the first \\(grouping\\).")
+
+
+
+(defvar sh-here-document-word "EOF"
+  "Word to delimit here documents.")
+
+(defvar sh-test
+  '((sh "[  ]" . 3)
+    (ksh88 "[[  ]]" . 4))
+  "Initial input in Bourne if, while and until skeletons.  See `sh-feature'.")
+
+
+(defvar sh-builtins
+  '((bash eval sh-append posix
+	  "alias" "bg" "bind" "builtin" "declare" "dirs" "enable" "fc" "fg"
+	  "help" "history" "jobs" "kill" "let" "local" "popd" "pushd" "source"
+	  "suspend" "typeset" "unalias")
+
+    ;; The next entry is only used for defining the others
+    (bourne eval sh-append shell
+	    "eval" "export" "getopts" "newgrp" "pwd" "read" "readonly"
+	    "times" "ulimit")
+
+    (csh eval sh-append shell
+	 "alias" "chdir" "glob" "history" "limit" "nice" "nohup" "rehash"
+	 "setenv" "source" "time" "unalias" "unhash")
+
+    (dtksh eval identity wksh)
+
+    (es "access" "apids" "cd" "echo" "eval" "false" "let" "limit" "local"
+	"newpgrp" "result" "time" "umask" "var" "vars" "wait" "whatis")
+
+    (jsh eval sh-append sh
+	 "bg" "fg" "jobs" "kill" "stop" "suspend")
+
+    (jcsh eval sh-append csh
+	 "bg" "fg" "jobs" "kill" "notify" "stop" "suspend")
+
+    (ksh88 eval sh-append bourne
+	   "alias" "bg" "false" "fc" "fg" "jobs" "kill" "let" "print" "time"
+	   "typeset" "unalias" "whence")
+
+    (oash eval sh-append sh
+	  "checkwin" "dateline" "error" "form" "menu" "newwin" "oadeinit"
+	  "oaed" "oahelp" "oainit" "pp" "ppfile" "scan" "scrollok" "wattr"
+	  "wclear" "werase" "win" "wmclose" "wmmessage" "wmopen" "wmove"
+	  "wmtitle" "wrefresh")
+
+    (pdksh eval sh-append ksh88
+	   "bind")
+
+    (posix eval sh-append sh
+	   "command")
+
+    (rc "builtin" "cd" "echo" "eval" "limit" "newpgrp" "shift" "umask" "wait"
+	"whatis")
+
+    (sh eval sh-append bourne
+	"hash" "test" "type")
+
+    ;; The next entry is only used for defining the others
+    (shell "cd" "echo" "eval" "set" "shift" "umask" "unset" "wait")
+
+    (wksh eval sh-append ksh88
+	  "Xt[A-Z][A-Za-z]*")
+
+    (zsh eval sh-append ksh88
+	 "autoload" "bindkey" "builtin" "chdir" "compctl" "declare" "dirs"
+	 "disable" "disown" "echotc" "enable" "functions" "getln" "hash"
+	 "history" "integer" "limit" "local" "log" "popd" "pushd" "r"
+	 "readonly" "rehash" "sched" "setopt" "source" "suspend" "true"
+	 "ttyctl" "type" "unfunction" "unhash" "unlimit" "unsetopt" "vared"
+	 "which"))
+  "*List of all shell builtins for completing read and fontification.
+Note that on some systems not all builtins are available or some are
+implemented as aliases.  See `sh-feature'.")
+
+
+
+(defvar sh-leading-keywords
+  '((csh "else")
+
+    (es "true" "unwind-protect" "whatis")
+
+    (rc "else")
+
+    (sh "do" "elif" "else" "if" "then" "trap" "type" "until" "while"))
+  "*List of keywords that may be immediately followed by a builtin or keyword.
+Given some confusion between keywords and builtins depending on shell and
+system, the distinction here has been based on whether they influence the
+flow of control or syntax.  See `sh-feature'.")
+
+
+(defvar sh-other-keywords
+  '((bash eval sh-append bourne
+	  "bye" "logout")
+
+    ;; The next entry is only used for defining the others
+    (bourne eval sh-append shell
+	    "done" "esac" "fi" "for" "function" "in" "return")
+
+    (csh eval sh-append shell
+	 "breaksw" "default" "end" "endif" "endsw" "foreach" "goto"
+	 "if" "logout" "onintr" "repeat" "switch" "then" "while")
+
+    (es "break" "catch" "exec" "exit" "fn" "for" "forever" "fork" "if"
+	"return" "throw" "while")
+
+    (ksh88 eval sh-append bourne
+	   "select")
+
+    (rc "break" "case" "exec" "exit" "fn" "for" "if" "in" "return" "switch"
+	"while")
+
+    ;; The next entry is only used for defining the others
+    (shell "break" "case" "continue" "exec" "exit")
+
+    (zsh eval sh-append bash
+	 "select"))
+  "*List of keywords not in `sh-leading-keywords'.
+See `sh-feature'.")
+
+
+
+(defvar sh-variables
+  '((bash eval sh-append sh
+	  "allow_null_glob_expansion" "auto_resume" "BASH" "BASH_VERSION"
+	  "cdable_vars" "ENV" "EUID" "FCEDIT" "FIGNORE" "glob_dot_filenames"
+	  "histchars" "HISTFILE" "HISTFILESIZE" "history_control" "HISTSIZE"
+	  "hostname_completion_file" "HOSTTYPE" "IGNOREEOF" "ignoreeof"
+	  "LINENO" "MAIL_WARNING" "noclobber" "nolinks" "notify"
+	  "no_exit_on_failed_exec" "NO_PROMPT_VARS" "OLDPWD" "OPTERR" "PPID"
+	  "PROMPT_COMMAND" "PS4" "pushd_silent" "PWD" "RANDOM" "REPLY"
+	  "SECONDS" "SHLVL" "TMOUT" "UID")
+
+    (csh eval sh-append shell
+	 "argv" "cdpath" "child" "echo" "histchars" "history" "home"
+	 "ignoreeof" "mail" "noclobber" "noglob" "nonomatch" "path" "prompt"
+	 "shell" "status" "time" "verbose")
+
+    (es eval sh-append shell
+	"apid" "cdpath" "CDPATH" "history" "home" "ifs" "noexport" "path"
+	"pid" "prompt" "signals")
+
+    (jcsh eval sh-append csh
+	 "notify")
+
+    (ksh88 eval sh-append sh
+	   "ENV" "ERRNO" "FCEDIT" "FPATH" "HISTFILE" "HISTSIZE" "LINENO"
+	   "OLDPWD" "PPID" "PS3" "PS4" "PWD" "RANDOM" "REPLY" "SECONDS"
+	   "TMOUT")
+
+    (oash eval sh-append sh
+	  "FIELD" "FIELD_MAX" "LAST_KEY" "OALIB" "PP_ITEM" "PP_NUM")
+
+    (rc eval sh-append shell
+	"apid" "apids" "cdpath" "CDPATH" "history" "home" "ifs" "path" "pid"
+	"prompt" "status")
+
+    (sh eval sh-append shell
+	"CDPATH" "IFS" "OPTARG" "OPTIND" "PS1" "PS2")
+
+    ;; The next entry is only used for defining the others
+    (shell "COLUMNS" "EDITOR" "HOME" "HUSHLOGIN" "LANG" "LC_COLLATE"
+	   "LC_CTYPE" "LC_MESSAGES" "LC_MONETARY" "LC_NUMERIC" "LC_TIME"
+	   "LINES" "LOGNAME" "MAIL" "MAILCHECK" "MAILPATH" "PAGER" "PATH"
+	   "SHELL" "TERM" "TERMCAP" "TERMINFO" "VISUAL")
+
+    (tcsh eval sh-append csh
+	  "addsuffix" "ampm" "autocorrect" "autoexpand" "autolist"
+	  "autologout" "chase_symlinks" "correct" "dextract" "edit" "el"
+	  "fignore" "gid" "histlit" "HOST" "HOSTTYPE" "HPATH"
+	  "ignore_symlinks" "listjobs" "listlinks" "listmax" "matchbeep"
+	  "nobeep" "NOREBIND" "oid" "printexitvalue" "prompt2" "prompt3"
+	  "pushdsilent" "pushdtohome" "recexact" "recognize_only_executables"
+	  "rmstar" "savehist" "SHLVL" "showdots" "sl" "SYSTYPE" "tcsh" "term"
+	  "tperiod" "tty" "uid" "version" "visiblebell" "watch" "who"
+	  "wordchars")
+
+    (zsh eval sh-append ksh88
+	 "BAUD" "bindcmds" "cdpath" "DIRSTACKSIZE" "fignore" "FIGNORE" "fpath"
+	 "HISTCHARS" "hostcmds" "hosts" "HOSTS" "LISTMAX" "LITHISTSIZE"
+	 "LOGCHECK" "mailpath" "manpath" "NULLCMD" "optcmds" "path" "POSTEDIT"
+	 "prompt" "PROMPT" "PROMPT2" "PROMPT3" "PROMPT4" "psvar" "PSVAR"
+	 "READNULLCMD" "REPORTTIME" "RPROMPT" "RPS1" "SAVEHIST" "SPROMPT"
+	 "STTY" "TIMEFMT" "TMOUT" "TMPPREFIX" "varcmds" "watch" "WATCH"
+	 "WATCHFMT" "WORDCHARS" "ZDOTDIR"))
+  "List of all shell variables available for completing read.
+See `sh-feature'.")
+
+
+
+(defvar sh-font-lock-keywords
+  '((csh eval sh-append shell
+	 '("\\${?[#?]?\\([A-Za-z_][A-Za-z0-9_]*\\|0\\)" 1
+	   font-lock-variable-name-face))
+
+    (es eval sh-append executable-font-lock-keywords
+	'("\\$#?\\([A-Za-z_][A-Za-z0-9_]*\\|[0-9]+\\)" 1
+	  font-lock-variable-name-face))
+
+    (rc eval identity es)
+
+    (sh eval sh-append shell
+	'("\\$\\({#?\\)?\\([A-Za-z_][A-Za-z0-9_]*\\|[-#?@!]\\)" 2
+	  font-lock-variable-name-face))
+
+    ;; The next entry is only used for defining the others
+    (shell eval sh-append executable-font-lock-keywords
+	   '("\\\\[^A-Za-z0-9]" 0 font-lock-string-face)
+	   '("\\${?\\([A-Za-z_][A-Za-z0-9_]*\\|[0-9]+\\|[$*_]\\)" 1
+	     font-lock-variable-name-face)))
+  "*Rules for highlighting shell scripts.  See `sh-feature'.")
+
+(defvar sh-font-lock-keywords-1
+  '((sh "[ \t]in\\>"))
+  "*Additional rules for highlighting shell scripts.  See `sh-feature'.")
+
+(defvar sh-font-lock-keywords-2 ()
+  "*Yet more rules for highlighting shell scripts.  See `sh-feature'.")
+
+(defvar sh-font-lock-keywords-only t
+  "*Value of `font-lock-keywords-only' for highlighting shell scripts.
+Default value is `t' because Emacs' syntax is not expressive enough to
+detect that $# does not start a comment.  Thus comments are fontified by
+regexp which means that a single apostrophe in a comment turns everything
+upto the next one or end of buffer into a string.")
+
+;; mode-command and utility functions
+
+;;;###autoload
+(put 'sh-mode 'mode-class 'special)
+
+;;;###autoload
+(defun sh-mode ()
+  "Major mode for editing shell scripts.
+This mode works for many shells, since they all have roughly the same syntax,
+as far as commands, arguments, variables, pipes, comments etc. are concerned.
+Unless the file's magic number indicates the shell, your usual shell is
+assumed.  Since filenames rarely give a clue, they are not further analyzed.
+
+This mode adapts to the variations between shells (see `sh-set-shell') by
+means of an inheritance based feature lookup (see `sh-feature').  This
+mechanism applies to all variables (including skeletons) that pertain to
+shell-specific features.
+
+The default style of this mode is that of Rosenblatt's Korn shell book.
+The syntax of the statements varies with the shell being used.  The
+following commands are available, based on the current shell's syntax:
+
+\\[sh-case]	 case statement
+\\[sh-for]	 for loop
+\\[sh-function]	 function definition
+\\[sh-if]	 if statement
+\\[sh-indexed-loop]	 indexed loop from 1 to n
+\\[sh-while-getopts]	 while getopts loop
+\\[sh-repeat]	 repeat loop
+\\[sh-select]	 select loop
+\\[sh-until]	 until loop
+\\[sh-while]	 while loop
+
+\\[backward-delete-char-untabify]	 Delete backward one position, even if it was a tab.
+\\[sh-newline-and-indent]	 Delete unquoted space and indent new line same as this one.
+\\[sh-end-of-command]	 Go to end of successive commands.
+\\[sh-beginning-of-command]	 Go to beginning of successive commands.
+\\[sh-set-shell]	 Set this buffer's shell, and maybe its magic number.
+\\[sh-execute-region]	 Have optional header and region be executed in a subshell.
+
+\\[sh-maybe-here-document]	 Without prefix, following an unquoted < inserts here document.
+{, (, [, ', \", `
+	Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``.
+
+If you generally program a shell different from your login shell you can
+set `sh-shell-file' accordingly.  If your shell's file name doesn't correctly
+indicate what shell it is use `sh-alias-alist' to translate.
+
+If your shell gives error messages with line numbers, you can use \\[executable-interpret]
+with your script for an edit-interpret-debug cycle."
+  (interactive)
+  (kill-all-local-variables)
+  (use-local-map sh-mode-map)
+  (make-local-variable 'indent-line-function)
+  (make-local-variable 'indent-region-function)
+  (make-local-variable 'skeleton-end-hook)
+  (make-local-variable 'paragraph-start)
+  (make-local-variable 'paragraph-separate)
+  (make-local-variable 'comment-start)
+  (make-local-variable 'comment-start-skip)
+  (make-local-variable 'require-final-newline)
+  (make-local-variable 'sh-header-marker)
+  (make-local-variable 'sh-shell-file)
+  (make-local-variable 'sh-shell)
+  (make-local-variable 'skeleton-pair-alist)
+  (make-local-variable 'skeleton-pair-filter)
+  (make-local-variable 'comint-dynamic-complete-functions)
+  (make-local-variable 'comint-prompt-regexp)
+  (make-local-variable 'font-lock-keywords)
+  (make-local-variable 'font-lock-defaults)
+  (make-local-variable 'skeleton-filter)
+  (make-local-variable 'skeleton-newline-indent-rigidly)
+  (make-local-variable 'sh-shell-variables)
+  (make-local-variable 'sh-shell-variables-initialized)
+  (setq major-mode 'sh-mode
+	mode-name "Shell-script"
+	indent-line-function 'sh-indent-line
+	;; not very clever, but enables wrapping skeletons around regions
+	indent-region-function (lambda (b e)
+				 (save-excursion
+				   (goto-char b)
+				   (skip-syntax-backward "-")
+				   (setq b (point))
+				   (goto-char e)
+				   (skip-syntax-backward "-")
+				   (indent-rigidly b (point) sh-indentation)))
+	skeleton-end-hook (lambda ()
+			    (or (eolp) (newline) (indent-relative)))
+	paragraph-start (concat page-delimiter "\\|$")
+	paragraph-separate paragraph-start
+	comment-start "# "
+	comint-dynamic-complete-functions sh-dynamic-complete-functions
+	;; we can't look if previous line ended with `\'
+	comint-prompt-regexp "^[ \t]*"
+	font-lock-defaults
+	  `((sh-font-lock-keywords
+	     sh-font-lock-keywords-1
+	     sh-font-lock-keywords-2)
+	    ,sh-font-lock-keywords-only
+	    nil
+	    ((?/ . "w") (?~ . "w") (?. . "w") (?- . "w") (?_ . "w")))
+	skeleton-pair-alist '((?` _ ?`))
+	skeleton-pair-filter 'sh-quoted-p
+	skeleton-further-elements '((< '(- (min sh-indentation
+						(current-column)))))
+	skeleton-filter 'sh-feature
+	skeleton-newline-indent-rigidly t)
+  (save-excursion
+    ;; parse or insert magic number for exec() and set all variables depending
+    ;; on the shell thus determined
+    (goto-char (point-min))
+    (and (zerop (buffer-size))
+	 (not buffer-read-only)
+	 (sh-set-shell sh-shell-file)))
+  (run-hooks 'sh-mode-hook))
+;;;###autoload
+(defalias 'shell-script-mode 'sh-mode)
+
+
+(defun sh-font-lock-keywords (&optional keywords)
+  "Function to get simple fontification based on `sh-font-lock-keywords'.
+This adds rules for comments and assignments."
+  (sh-feature sh-font-lock-keywords
+	      (lambda (list)
+		`((,(concat (sh-feature sh-comment-prefix) "\\(#.*\\)")
+		   2 font-lock-comment-face t)
+		  (,(sh-feature sh-assignment-regexp)
+		   1 font-lock-variable-name-face)
+		  ,@keywords
+		  ,@list))))
+
+(defun sh-font-lock-keywords-1 (&optional builtins)
+  "Function to get better fontification including keywords."
+  (let ((keywords (concat "\\([;(){}`|&]\\|^\\)[ \t]*\\(\\(\\("
+			  (mapconcat 'identity
+				     (sh-feature sh-leading-keywords)
+				     "\\|")
+			  "\\)[ \t]+\\)?\\("
+			  (mapconcat 'identity
+				     (append (sh-feature sh-leading-keywords)
+					     (sh-feature sh-other-keywords))
+				     "\\|")
+			  "\\)")))
+    (sh-font-lock-keywords
+     `(,@(if builtins
+	     `((,(concat keywords "[ \t]+\\)?\\("
+			 (mapconcat 'identity (sh-feature sh-builtins) "\\|")
+			 "\\)\\>")
+		(2 font-lock-keyword-face nil t)
+		(6 font-lock-function-name-face))
+	       ,@(sh-feature sh-font-lock-keywords-2)))
+	 (,(concat keywords "\\)\\>")
+	  2 font-lock-keyword-face)
+	 ,@(sh-feature sh-font-lock-keywords-1)))))
+
+(defun sh-font-lock-keywords-2 ()
+  "Function to get better fontification including keywords and builtins."
+  (sh-font-lock-keywords-1 t))
+
+
+(defun sh-set-shell (shell &optional no-query-flag insert-flag)
+  "Set this buffer's shell to SHELL (a string).
+Makes this script executable via `executable-set-magic'.
+Calls the value of `sh-set-shell-hook' if set."
+  (interactive (list (completing-read "Name or path of shell: "
+				      interpreter-mode-alist
+				      (lambda (x) (eq (cdr x) 'sh-mode)))
+		     (eq executable-query 'function)
+		     t))
+  (setq sh-shell (intern (file-name-nondirectory shell))
+	sh-shell (or (cdr (assq sh-shell sh-alias-alist))
+		     sh-shell))
+  (setq sh-shell-file (executable-set-magic shell (sh-feature sh-shell-arg)))
+  (setq require-final-newline (sh-feature sh-require-final-newline)
+;;;	local-abbrev-table (sh-feature sh-abbrevs)
+	font-lock-keywords nil		; force resetting
+	font-lock-syntax-table nil
+	comment-start-skip (concat (sh-feature sh-comment-prefix) "#+[\t ]*")
+	mode-line-process (format "[%s]" sh-shell)
+	sh-shell-variables nil
+	sh-shell-variables-initialized nil
+	shell (sh-feature sh-variables))
+  (set-syntax-table (sh-feature sh-mode-syntax-table))
+  (while shell
+    (sh-remember-variable (car shell))
+    (setq shell (cdr shell)))
+  (and (boundp 'font-lock-mode)
+       font-lock-mode
+       (font-lock-mode (font-lock-mode 0)))
+  (run-hooks 'sh-set-shell-hook))
+
+
+
+(defun sh-feature (list &optional function)
+  "Index ALIST by the current shell.
+If ALIST isn't a list where every element is a cons, it is returned as is.
+Else indexing follows an inheritance logic which works in two ways:
+
+  - Fall back on successive ancestors (see `sh-ancestor-alist') as long as
+    the alist contains no value for the current shell.
+
+  - If the value thus looked up is a list starting with `eval' its `cdr' is
+    first evaluated.  If that is also a list and the first argument is a
+    symbol in ALIST it is not evaluated, but rather recursively looked up in
+    ALIST to allow the function called to define the value for one shell to be
+    derived from another shell.  While calling the function, is the car of the
+    alist element is the current shell.
+    The value thus determined is physically replaced into the alist.
+
+Optional FUNCTION is applied to the determined value and the result is cached
+in ALIST."
+  (or (if (consp list)
+	  (let ((l list))
+	    (while (and l (consp (car l)))
+	      (setq l (cdr l)))
+	    (if l list)))
+      (if function
+	  (cdr (assoc (setq function (cons sh-shell function)) list)))
+      (let ((sh-shell sh-shell)
+	    elt val)
+	(while (and sh-shell
+		    (not (setq elt (assq sh-shell list))))
+	  (setq sh-shell (cdr (assq sh-shell sh-ancestor-alist))))
+	(if (and (consp (setq val (cdr elt)))
+		 (eq (car val) 'eval))
+	    (setcdr elt
+		    (setq val
+			  (eval (if (consp (setq val (cdr val)))
+				    (let ((sh-shell (car (cdr val)))
+					  function)
+				      (if (assq sh-shell list)
+					  (setcar (cdr val)
+						  (list 'quote
+							(sh-feature list))))
+				      val)
+				  val)))))
+	(if function
+	    (nconc list
+		   (list (cons function
+			       (setq sh-shell (car function)
+				     val (funcall (cdr function) val))))))
+	val)))
+
+
+
+;;; I commented this out because nobody calls it -- rms.
+;;;(defun sh-abbrevs (ancestor &rest list)
+;;;  "Iff it isn't, define the current shell as abbrev table and fill that.
+;;;Abbrev table will inherit all abbrevs from ANCESTOR, which is either an abbrev
+;;;table or a list of (NAME1 EXPANSION1 ...).  In addition it will define abbrevs
+;;;according to the remaining arguments NAMEi EXPANSIONi ...
+;;;EXPANSION may be either a string or a skeleton command."
+;;;  (or (if (boundp sh-shell)
+;;;	  (symbol-value sh-shell))
+;;;      (progn
+;;;	(if (listp ancestor)
+;;;	    (nconc list ancestor))
+;;;	(define-abbrev-table sh-shell ())
+;;;	(if (vectorp ancestor)
+;;;	    (mapatoms (lambda (atom)
+;;;			(or (eq atom 0)
+;;;			    (define-abbrev (symbol-value sh-shell)
+;;;			      (symbol-name atom)
+;;;			      (symbol-value atom)
+;;;			      (symbol-function atom))))
+;;;		      ancestor))
+;;;	(while list
+;;;	  (define-abbrev (symbol-value sh-shell)
+;;;	    (car list)
+;;;	    (if (stringp (car (cdr list)))
+;;;		(car (cdr list))
+;;;	      "")
+;;;	    (if (symbolp (car (cdr list)))
+;;;		(car (cdr list))))
+;;;	  (setq list (cdr (cdr list)))))
+;;;      (symbol-value sh-shell)))
+
+
+(defun sh-mode-syntax-table (table &rest list)
+  "Copy TABLE and set syntax for successive CHARs according to strings S."
+  (setq table (copy-syntax-table table))
+  (while list
+    (modify-syntax-entry (car list) (car (cdr list)) table)
+    (setq list (cdr (cdr list))))
+  table)
+
+
+(defun sh-append (ancestor &rest list)
+  "Return list composed of first argument (a list) physically appended to rest."
+  (nconc list ancestor))
+
+
+(defun sh-modify (skeleton &rest list)
+  "Modify a copy of SKELETON by replacing I1 with REPL1, I2 with REPL2 ..."
+  (setq skeleton (copy-sequence skeleton))
+  (while list
+    (setcar (or (nthcdr (car list) skeleton)
+		(error "Index %d out of bounds" (car list)))
+	    (car (cdr list)))
+    (setq list (nthcdr 2 list)))
+  skeleton)
+
+
+(defun sh-indent-line ()
+  "Indent as far as preceding non-empty line, then by steps of `sh-indentation'.
+Lines containing only comments are considered empty."
+  (interactive)
+  (let ((previous (save-excursion
+		    (while (and (not (bobp))
+				(progn
+				  (forward-line -1)
+				  (back-to-indentation)
+				  (or (eolp)
+				      (eq (following-char) ?#)))))
+		    (current-column)))
+	current)
+    (save-excursion
+      (indent-to (if (eq this-command 'newline-and-indent)
+		     previous
+		   (if (< (current-column)
+			  (setq current (progn (back-to-indentation)
+					       (current-column))))
+		       (if (eolp) previous 0)
+		     (delete-region (point)
+				    (progn (beginning-of-line) (point)))
+		     (if (eolp)
+			 (max previous (* (1+ (/ current sh-indentation))
+					  sh-indentation))
+		       (* (1+ (/ current sh-indentation)) sh-indentation))))))
+    (if (< (current-column) (current-indentation))
+	(skip-chars-forward " \t"))))
+
+
+(defun sh-execute-region (start end &optional flag)
+  "Pass optional header and region to a subshell for noninteractive execution.
+The working directory is that of the buffer, and only environment variables
+are already set which is why you can mark a header within the script.
+
+With a positive prefix ARG, instead of sending region, define header from
+beginning of buffer to point.  With a negative prefix ARG, instead of sending
+region, clear header."
+  (interactive "r\nP")
+  (if flag
+      (setq sh-header-marker (if (> (prefix-numeric-value flag) 0)
+				 (point-marker)))
+    (if sh-header-marker
+	(save-excursion
+	  (let (buffer-undo-list)
+	    (goto-char sh-header-marker)
+	    (append-to-buffer (current-buffer) start end)
+	    (shell-command-on-region (point-min)
+				     (setq end (+ sh-header-marker
+						  (- end start)))
+				     sh-shell-file)
+	    (delete-region sh-header-marker end)))
+      (shell-command-on-region start end (concat sh-shell-file " -")))))
+
+
+(defun sh-remember-variable (var)
+  "Make VARIABLE available for future completing reads in this buffer."
+  (or (< (length var) sh-remember-variable-min)
+      (getenv var)
+      (assoc var sh-shell-variables)
+      (setq sh-shell-variables (cons (cons var var) sh-shell-variables)))
+  var)
+
+
+
+(defun sh-quoted-p ()
+  "Is point preceded by an odd number of backslashes?"
+  (eq -1 (% (save-excursion (skip-chars-backward "\\\\")) 2)))
+
+;; statement syntax-commands for various shells
+
+;; You are welcome to add the syntax or even completely new statements as
+;; appropriate for your favorite shell.
+
+(define-skeleton sh-case
+  "Insert a case/switch statement.  See `sh-feature'."
+  (csh "expression: "
+       "switch( " str " )" \n
+       > "case " (read-string "pattern: ") ?: \n
+       > _ \n
+       "breaksw" \n
+       ( "other pattern, %s: "
+	 < "case " str ?: \n
+	 > _ \n
+	 "breaksw" \n)
+       < "default:" \n
+       > _ \n
+       resume:
+       < < "endsw")
+  (es)
+  (rc "expression: "
+      "switch( " str " ) {" \n
+      > "case " (read-string "pattern: ") \n
+      > _ \n
+      ( "other pattern, %s: "
+	< "case " str \n
+	> _ \n)
+      < "case *" \n
+      > _ \n
+      resume:
+      < < ?})
+  (sh "expression: "
+      "case " str " in" \n
+      > (read-string "pattern: ") ?\) \n
+      > _ \n
+      ";;" \n
+      ( "other pattern, %s: "
+	< str ?\) \n
+	> _ \n
+	";;" \n)
+      < "*)" \n
+      > _ \n
+      resume:
+      < < "esac"))
+(put 'sh-case 'menu-enable '(sh-feature sh-case))
+
+
+
+(define-skeleton sh-for
+  "Insert a for loop.  See `sh-feature'."
+  (csh eval sh-modify sh
+       1 "foreach "
+       3 " ( "
+       5 " )"
+       15 "end")
+  (es eval sh-modify rc
+      3 " = ")
+  (rc eval sh-modify sh
+      1 "for( "
+      5 " ) {"
+      15 ?})
+  (sh "Index variable: "
+      "for " str " in " _ "; do" \n
+      > _ | ?$ & (sh-remember-variable str) \n
+      < "done"))
+
+
+
+(define-skeleton sh-indexed-loop
+  "Insert an indexed loop from 1 to n.  See `sh-feature'."
+  (bash eval identity posix)
+  (csh "Index variable: "
+       "@ " str " = 1" \n
+       "while( $" str " <= " (read-string "upper limit: ") " )" \n
+       > _ ?$ str \n
+       "@ " str "++" \n
+       < "end")
+  (es eval sh-modify rc
+      3 " =")
+  (ksh88 "Index variable: "
+	 "integer " str "=0" \n
+	 "while (( ( " str " += 1 ) <= "
+	 (read-string "upper limit: ")
+	 " )); do" \n
+	 > _ ?$ (sh-remember-variable str) \n
+	 < "done")
+  (posix "Index variable: "
+	 str "=1" \n
+	 "while [ $" str " -le "
+	 (read-string "upper limit: ")
+	 " ]; do" \n
+	 > _ ?$ str \n
+	 str ?= (sh-add (sh-remember-variable str) 1) \n
+	 < "done")
+  (rc "Index variable: "
+      "for( " str " in" " `{awk 'BEGIN { for( i=1; i<="
+      (read-string "upper limit: ")
+      "; i++ ) print i }'}) {" \n
+      > _ ?$ (sh-remember-variable str) \n
+      < ?})
+  (sh "Index variable: "
+      "for " str " in `awk 'BEGIN { for( i=1; i<="
+      (read-string "upper limit: ")
+      "; i++ ) print i }'`; do" \n
+      > _ ?$ (sh-remember-variable str) \n
+      < "done"))
+
+
+(defun sh-shell-initialize-variables ()
+  "Scan the buffer for variable assignments.
+Add these variables to `sh-shell-variables'."
+  (message "Scanning buffer `%s' for variable assignments..." (buffer-name))
+  (save-excursion
+    (goto-char (point-min))
+    (setq sh-shell-variables-initialized t)
+    (while (search-forward "=" nil t)
+      (sh-assignment 0)))
+  (message "Scanning buffer `%s' for variable assignments...done"
+	   (buffer-name)))
+
+(defvar sh-add-buffer)
+
+(defun sh-add-completer (string predicate code)
+  "Do completion using `sh-shell-variables', but initialize it first.
+This function is designed for use as the \"completion table\",
+so it takes three arguments:
+  STRING, the current buffer contents;
+  PREDICATE, the predicate for filtering possible matches;
+  CODE, which says what kind of things to do.
+CODE can be nil, t or `lambda'.
+nil means to return the best completion of STRING, or nil if there is none.
+t means to return a list of all possible completions of STRING.
+`lambda' means to return t if STRING is a valid completion as it stands."
+  (let ((sh-shell-variables
+	 (save-excursion
+	   (set-buffer sh-add-buffer)
+	   (or sh-shell-variables-initialized
+	       (sh-shell-initialize-variables))
+	   (nconc (mapcar (lambda (var)
+			    (let ((name
+				   (substring var 0 (string-match "=" var))))
+			      (cons name name)))
+			  process-environment)
+		  sh-shell-variables))))
+    (cond ((null code)
+	   (try-completion string sh-shell-variables predicate))
+	  ((eq code t)
+	   (all-completions string sh-shell-variables predicate))
+	  ((eq code 'lambda)
+	   (assoc string sh-shell-variables)))))
+
+(defun sh-add (var delta)
+  "Insert an addition of VAR and prefix DELTA for Bourne (type) shell."
+  (interactive
+   (let ((sh-add-buffer (current-buffer)))
+     (list (completing-read "Variable: " 'sh-add-completer)
+	   (prefix-numeric-value current-prefix-arg))))
+  (insert (sh-feature '((bash . "$[ ")
+			(ksh88 . "$(( ")
+			(posix . "$(( ")
+			(rc . "`{expr $")
+			(sh . "`expr $")
+			(zsh . "$[ ")))
+	  (sh-remember-variable var)
+	  (if (< delta 0) " - " " + ")
+	  (number-to-string (abs delta))
+	  (sh-feature '((bash . " ]")
+			(ksh88 . " ))")
+			(posix . " ))")
+			(rc . "}")
+			(sh . "`")
+			(zsh . " ]")))))
+
+
+
+(define-skeleton sh-function
+  "Insert a function definition.  See `sh-feature'."
+  (bash eval sh-modify ksh88
+	3 "() {")
+  (ksh88 "name: "
+	 "function " str " {" \n
+	 > _ \n
+	 < "}")
+  (rc eval sh-modify ksh88
+	1 "fn ")
+  (sh ()
+      "() {" \n
+      > _ \n
+      < "}"))
+
+
+
+(define-skeleton sh-if
+  "Insert an if statement.  See `sh-feature'."
+  (csh "condition: "
+       "if( " str " ) then" \n
+       > _ \n
+       ( "other condition, %s: "
+	 < "else if( " str " ) then" \n
+	 > _ \n)
+       < "else" \n
+       > _ \n
+       resume:
+       < "endif")
+  (es "condition: "
+      "if { " str " } {" \n
+       > _ \n
+       ( "other condition, %s: "
+	 < "} { " str " } {" \n
+	 > _ \n)
+       < "} {" \n
+       > _ \n
+       resume:
+       < ?})
+  (rc eval sh-modify csh
+      3 " ) {"
+      8 '( "other condition, %s: "
+	   < "} else if( " str " ) {" \n
+	   > _ \n)
+      10 "} else {"
+      17 ?})
+  (sh "condition: "
+      '(setq input (sh-feature sh-test))
+      "if " str "; then" \n
+      > _ \n
+      ( "other condition, %s: "
+	< "elif " str "; then" \n
+	> _ \n)
+      < "else" \n
+      > _ \n
+      resume:
+      < "fi"))
+
+
+
+(define-skeleton sh-repeat
+  "Insert a repeat loop definition.  See `sh-feature'."
+  (es nil
+      "forever {" \n
+      > _ \n
+      < ?})
+  (zsh "factor: "
+      "repeat " str "; do"\n
+      > _ \n
+      < "done"))
+(put 'sh-repeat 'menu-enable '(sh-feature sh-repeat))
+
+
+
+(define-skeleton sh-select
+  "Insert a select statement.  See `sh-feature'."
+  (ksh88 "Index variable: "
+	 "select " str " in " _ "; do" \n
+	 > ?$ str \n
+	 < "done"))
+(put 'sh-select 'menu-enable '(sh-feature sh-select))
+
+
+
+(define-skeleton sh-tmp-file
+  "Insert code to setup temporary file handling.  See `sh-feature'."
+  (bash eval identity ksh88)
+  (csh (file-name-nondirectory (buffer-file-name))
+       "set tmp = /tmp/" str ".$$" \n
+       "onintr exit" \n _
+       (and (goto-char (point-max))
+	    (not (bolp))
+	    ?\n)
+       "exit:\n"
+       "rm $tmp* >&/dev/null" >)
+  (es (file-name-nondirectory (buffer-file-name))
+      "local( signals = $signals sighup sigint; tmp = /tmp/" str ".$pid ) {" \n
+      > "catch @ e {" \n
+      > "rm $tmp^* >[2]/dev/null" \n
+      "throw $e" \n
+      < "} {" \n
+      > _ \n
+      < ?} \n
+      < ?})
+  (ksh88 eval sh-modify sh
+	 6 "EXIT")
+  (rc (file-name-nondirectory (buffer-file-name))
+       "tmp = /tmp/" str ".$pid" \n
+       "fn sigexit { rm $tmp^* >[2]/dev/null }")
+  (sh (file-name-nondirectory (buffer-file-name))
+      "TMP=/tmp/" str ".$$" \n
+      "trap \"rm $TMP* 2>/dev/null\" " ?0))
+
+
+
+(define-skeleton sh-until
+  "Insert an until loop.  See `sh-feature'."
+  (sh "condition: "
+      '(setq input (sh-feature sh-test))
+      "until " str "; do" \n
+      > _ \n
+      < "done"))
+(put 'sh-until 'menu-enable '(sh-feature sh-until))
+
+
+
+(define-skeleton sh-while
+  "Insert a while loop.  See `sh-feature'."
+  (csh eval sh-modify sh
+       2 "while( "
+       4 " )"
+       10 "end")
+  (es eval sh-modify rc
+      2 "while { "
+      4 " } {")
+  (rc eval sh-modify csh
+      4 " ) {"
+      10 ?})
+  (sh "condition: "
+      '(setq input (sh-feature sh-test))
+      "while " str "; do" \n
+      > _ \n
+      < "done"))
+
+
+
+(define-skeleton sh-while-getopts
+  "Insert a while getopts loop.  See `sh-feature'.
+Prompts for an options string which consists of letters for each recognized
+option followed by a colon `:' if the option accepts an argument."
+  (bash eval sh-modify sh
+	18 "${0##*/}")
+  (csh nil
+       "while( 1 )" \n
+       > "switch( \"$1\" )" \n
+       '(setq input '("- x" . 2))
+       > >
+       ( "option, %s: "
+	 < "case " '(eval str)
+	 '(if (string-match " +" str)
+	      (setq v1 (substring str (match-end 0))
+		    str (substring str 0 (match-beginning 0)))
+	    (setq v1 nil))
+	 str ?: \n
+	 > "set " v1 & " = $2" | -4 & _ \n
+	 (if v1 "shift") & \n
+	 "breaksw" \n)
+       < "case --:" \n
+       > "shift" \n
+       < "default:" \n
+       > "break" \n
+       resume:
+       < < "endsw" \n
+       "shift" \n
+       < "end")
+  (ksh88 eval sh-modify sh
+	 16 "print"
+	 18 "${0##*/}"
+	 36 "OPTIND-1")
+  (posix eval sh-modify sh
+	 18 "$(basename $0)")
+  (sh "optstring: "
+      "while getopts :" str " OPT; do" \n
+      > "case $OPT in" \n
+      > >
+      '(setq v1 (append (vconcat str) nil))
+      ( (prog1 (if v1 (char-to-string (car v1)))
+	  (if (eq (nth 1 v1) ?:)
+	      (setq v1 (nthcdr 2 v1)
+		    v2 "\"$OPTARG\"")
+	    (setq v1 (cdr v1)
+		  v2 nil)))
+	< str "|+" str ?\) \n
+	> _ v2 \n
+	";;" \n)
+      < "*)" \n
+      > "echo" " \"usage: " "`basename $0`"
+      " [+-" '(setq v1 (point)) str
+      '(save-excursion
+	 (while (search-backward ":" v1 t)
+	   (replace-match " ARG] [+-" t t)))
+      (if (eq (preceding-char) ?-) -5)
+      "] [--] ARGS...\"" \n
+      "exit 2" \n
+      < < "esac" \n
+      < "done" \n
+      "shift " (sh-add "OPTIND" -1)))
+(put 'sh-while-getopts 'menu-enable '(sh-feature sh-while-getopts))
+
+
+
+(defun sh-assignment (arg)
+  "Remember preceding identifier for future completion and do self-insert."
+  (interactive "p")
+  (self-insert-command arg)
+  (if (<= arg 1)
+      (sh-remember-variable
+       (save-excursion
+	 (if (re-search-forward (sh-feature sh-assignment-regexp)
+				(prog1 (point)
+				  (beginning-of-line 1))
+				t)
+	     (match-string 1))))))
+
+
+
+(defun sh-maybe-here-document (arg)
+  "Inserts self.  Without prefix, following unquoted `<' inserts here document.
+The document is bounded by `sh-here-document-word'."
+  (interactive "*P")
+  (self-insert-command (prefix-numeric-value arg))
+  (or arg
+      (not (eq (char-after (- (point) 2)) last-command-char))
+      (save-excursion
+	(backward-char 2)
+	(sh-quoted-p))
+      (progn
+	(insert sh-here-document-word)
+	(or (eolp) (looking-at "[ \t]") (insert ? ))
+	(end-of-line 1)
+	(while
+	    (sh-quoted-p)
+	  (end-of-line 2))
+	(newline)
+	(save-excursion (insert ?\n sh-here-document-word)))))
+
+
+;; various other commands
+
+(autoload 'comint-dynamic-complete "comint"
+  "Dynamically perform completion at point." t)
+
+(autoload 'shell-dynamic-complete-command "shell"
+  "Dynamically complete the command at point." t)
+
+(autoload 'comint-dynamic-complete-filename "comint"
+  "Dynamically complete the filename at point." t)
+
+(autoload 'shell-dynamic-complete-environment-variable "shell"
+  "Dynamically complete the environment variable at point." t)
+
+
+
+(defun sh-newline-and-indent ()
+  "Strip unquoted whitespace, insert newline, and indent like current line."
+  (interactive "*")
+  (indent-to (prog1 (current-indentation)
+	       (delete-region (point)
+			      (progn
+				(or (zerop (skip-chars-backward " \t"))
+				    (if (sh-quoted-p)
+					(forward-char)))
+				(point)))
+	       (newline))))
+
+
+
+(defun sh-beginning-of-command ()
+  "Move point to successive beginnings of commands."
+  (interactive)
+  (if (re-search-backward sh-beginning-of-command nil t)
+      (goto-char (match-beginning 2))))
+
+
+(defun sh-end-of-command ()
+  "Move point to successive ends of commands."
+  (interactive)
+  (if (re-search-forward sh-end-of-command nil t)
+      (goto-char (match-end 1))))
+
+(provide 'sh-script)
+;; sh-script.el ends here
+