diff lisp/progmodes/sh-script.el @ 6463:380e8fcde9a2

entered into RCS
author Richard M. Stallman <rms@gnu.org>
date Tue, 22 Mar 1994 05:43:25 +0000
parents
children ec8291e58762
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/progmodes/sh-script.el	Tue Mar 22 05:43:25 1994 +0000
@@ -0,0 +1,895 @@
+;;; sh-script.el --- shell-script editing commands for Emacs
+;; Copyright (C) 1993 Free Software Foundation, Inc.
+
+;; Author: Daniel Pfeiffer, fax (+49 69) 75 88 529, c/o <bonhoure@cict.fr>
+;; Maintainer: FSF
+;; Keywords: shell programming
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs; see the file COPYING.  If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;; Commentary:
+
+;; Major mode for editing shell scripts.  Currently sh, ksh, bash and csh,
+;; tcsh are supported.  Structured statements can be inserted with one
+;; command.
+
+;;; 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
+
+
+;;;###autoload
+(setq auto-mode-alist
+      ;; matches files
+      ;;	- who's path contains /bin/, but not directories
+      (cons '("/bin/" . sh-or-other-mode)
+	    ;;	- that have a suffix .sh or .shar (shell archive)
+	    ;;	- that contain ressources for the various shells
+	    ;;	- startup files for X11
+	    (cons '("\\.sh$\\|\\.shar$\\|/\\.\\(profile\\|bash_profile\\|login\\|bash_login\\|logout\\|bash_logout\\|bashrc\\|t?cshrc\\|xinitrc\\|startxrc\\|xsession\\)$" . sh-mode)
+		  auto-mode-alist)))
+
+
+(defvar sh-mode-syntax-table
+  (let ((table (copy-syntax-table)))
+    (modify-syntax-entry ?\# "<" table)
+    (modify-syntax-entry ?\^l ">#" table)
+    (modify-syntax-entry ?\n ">#" table)
+    (modify-syntax-entry ?\" "\"\"" table)
+    (modify-syntax-entry ?\' "\"'" table)
+    (modify-syntax-entry ?\` "$`" table)
+    (modify-syntax-entry ?$ "_" table)
+    (modify-syntax-entry ?! "_" table)
+    (modify-syntax-entry ?% "_" table)
+    (modify-syntax-entry ?: "_" table)
+    (modify-syntax-entry ?. "_" table)
+    (modify-syntax-entry ?^ "_" table)
+    (modify-syntax-entry ?~ "_" table)
+    table)
+  "Syntax table in use in Shell-Script mode.")
+
+
+
+(defvar sh-use-prefix nil
+  "If non-nil when loading, `$' and `<' will be  C-c $  and  C-c < .")
+
+(defvar sh-mode-map
+  (let ((map (make-sparse-keymap)))
+    (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-s" 'sh-select)
+    (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 (if sh-use-prefix "\C-c$" "$")
+      'sh-query-for-variable)
+    (define-key map "=" 'sh-assignment)
+    (define-key map "\C-c+" 'sh-add)
+    (define-key map (if sh-use-prefix "\C-c<" "<")
+      'sh-maybe-here-document)
+    (define-key map "(" 'pair-insert-maybe)
+    (define-key map "{" 'pair-insert-maybe)
+    (define-key map "[" 'pair-insert-maybe)
+    (define-key map "'" 'pair-insert-maybe)
+    (define-key map "`" 'pair-insert-maybe)
+    (define-key map "\"" 'pair-insert-maybe)
+
+    (define-key map "\t" 'sh-indent-line)
+    (substitute-key-definition 'complete-tag 'comint-dynamic-complete-filename
+			       map (current-global-map))
+    (substitute-key-definition 'newline-and-indent 'sh-newline-and-indent
+			       map (current-global-map))
+    ;; Now that tabs work properly, this might be unwanted.
+    (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))
+    (substitute-key-definition 'manual-entry 'sh-manual-entry
+			       map (current-global-map))
+    (define-key map [menu-bar insert] 
+      (cons "Insert" (make-sparse-keymap "Insert")))
+    (define-key map [menu-bar insert sh-while] 
+      '("While loop" . sh-while))
+    (define-key map [menu-bar insert sh-until] 
+      '("Until loop" . sh-until))
+    (define-key map [menu-bar insert sh-select] 
+      '("Select statement" . sh-select))
+    (define-key map [menu-bar insert sh-indexed-loop] 
+      '("Indexed loop" . sh-indexed-loop))
+    (define-key map [menu-bar insert sh-if] 
+      '("If statement" . sh-if))
+    (define-key map [menu-bar insert sh-for] 
+      '("For loop" . sh-for))
+    (define-key map [menu-bar insert sh-case] 
+      '("Case statement" . sh-case))
+    map)
+  "Keymap used in Shell-Script mode.")
+
+
+
+(defvar sh-find-file-modifies t
+  "*What to do when newly found file has no magic number:
+	nil	do nothing
+	t	insert magic number
+	other	insert magic number, but mark as unmodified.")
+
+
+(defvar sh-query-for-magic t
+  "*If non-nil, ask user before changing or inserting magic number.")
+
+
+(defvar sh-magicless-file-regexp "/\\.[^/]+$"
+  "*On files with this kind of name no magic is inserted or changed.")
+
+
+;; someone who understands /etc/magic better than me should beef this up
+;; this currently covers only SCO Unix and Sinix executables
+;; the elegant way would be to read /etc/magic
+(defvar magic-number-alist '(("L\^a\^h\\|\^?ELF" . hexl-mode)
+			     ("#!.*perl" . perl-mode))
+  "A regexp to match the magic number of a found file.
+Currently this is only used by function `sh-or-other-mode'.")
+
+
+(defvar sh-executable ".* is \\([^ \t]*\\)\n"
+  "*Regexp to match the output of sh builtin `type' command on your machine.
+The regexp must match the whole output, and must contain a \\(something\\)
+construct which matches the actual executable.")
+
+
+
+(defvar sh-chmod-argument "755"
+  "*After saving, if the file is not executable, set this mode.
+The mode can be absolute \"511\" or relative \"u+x\".  Do nothing if this is nil.")
+
+
+(defvar sh-shell-path (or (getenv "SHELL") "/bin/sh")
+  "*The executable of the shell being programmed.")
+
+(defvar sh-shell-argument nil
+  "*A single argument for the magic number, or nil.")
+
+(defvar sh-shell nil
+  "The shell being programmed.  This is set by \\[sh-set-shell].")
+
+(defvar sh-shell-is-csh nil
+  "The shell being programmed.  This is set by \\[sh-set-shell].")
+
+(defvar sh-tab-width 4
+  "The default value for `tab-width' in Shell-Script mode.
+This is the width of tab stops after the indentation of the preceeding line.")
+
+(defvar sh-remember-variable-min 3
+  "*Don't remember variables less than this length for completing reads.")
+
+
+(defvar sh-beginning-of-command
+  "\\([;({`|&]\\|^\\)[ \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-assignment-space '(csh tcsh)
+  "List of shells that allow spaces around the assignment =.")
+
+(defvar sh-here-document-word "+"
+  "Word to delimit here documents.")
+
+
+;process-environment
+(defvar sh-variables
+  '(("addsuffix" tcsh)			("allow_null_glob_expansion" bash)
+    ("ampm" tcsh)			("argv" csh tcsh) 
+    ("autocorrect" tcsh)		("autoexpand" tcsh)	
+    ("autolist" tcsh)			("autologout" tcsh)
+    ("auto_resume" bash)		("BASH" bash)
+    ("BASH_VERSION" bash)		("cdable_vars" bash)
+    ("cdpath" csh tcsh)			("CDPATH" sh ksh bash) 
+    ("chase_symlinks" tcsh)		("child" csh tcsh) 
+    ("COLUMNS" ksh tcsh)		("correct" tcsh) 
+    ("dextract" tcsh)			("echo" csh tcsh) 
+    ("edit" tcsh)			("EDITOR") 
+    ("el" tcsh)				("ENV" ksh bash)	
+    ("ERRNO" ksh)			("EUID" bash)
+    ("FCEDIT" ksh bash)			("FIGNORE" bash)
+    ("fignore" tcsh)			("FPATH" ksh) 
+    ("gid" tcsh)			("glob_dot_filenames" bash)
+    ("histchars" bash csh tcsh)		("HISTFILE" ksh bash)
+    ("HISTFILESIZE" bash)		("histlit" tcsh) 
+    ("history" csh tcsh)		("history_control" bash)
+    ("HISTSIZE" bash)			("home" csh tcsh) 
+    ("HOME")				("HOST" tcsh) 
+    ("hostname_completion_file" bash)	("HOSTTYPE" bash tcsh)
+    ("HPATH" tcsh)			("HUSHLOGIN")
+    ("IFS" sh ksh bash)			("ignoreeof" bash csh tcsh)
+    ("IGNOREEOF" bash)			("ignore_symlinks" tcsh) 
+    ("LANG")				("LC_COLLATE") 
+    ("LC_CTYPE")			("LC_MESSAGES") 
+    ("LC_MONETARY")			("LC_NUMERIC") 
+    ("LC_TIME")				("LINENO" ksh bash)
+    ("LINES" ksh tcsh)			("listjobs" tcsh) 
+    ("listlinks" tcsh)			("listmax" tcsh) 
+    ("LOGNAME")				("mail" csh tcsh) 
+    ("MAIL")				("MAILCHECK") 
+    ("MAILPATH")			("MAIL_WARNING" bash)
+    ("matchbeep" tcsh)			("nobeep" tcsh)
+    ("noclobber" bash csh tcsh)		("noglob" csh tcsh)
+    ("nolinks" bash)			("nonomatch" csh tcsh) 
+    ("NOREBIND" tcsh)			("notify" bash)
+    ("no_exit_on_failed_exec" bash)	("NO_PROMPT_VARS" bash)
+    ("oid" tcsh)			("OLDPWD" ksh bash)
+    ("OPTARG" sh ksh bash)		("OPTERR" bash)
+    ("OPTIND" sh ksh bash)		("PAGER") 
+    ("path" csh tcsh)			("PATH") 
+    ("PPID" ksh bash)			("printexitvalue" tcsh) 
+    ("prompt" csh tcsh)			("prompt2" tcsh) 
+    ("prompt3" tcsh)			("PROMPT_COMMAND" bash)
+    ("PS1" sh ksh bash)			("PS2" sh ksh bash)
+    ("PS3" ksh)				("PS4" ksh bash)
+    ("pushdsilent" tcsh)		("pushdtohome" tcsh)
+    ("pushd_silent" bash)		("PWD" ksh bash)
+    ("RANDOM" ksh bash)			("recexact" tcsh) 
+    ("recognize_only_executables" tcsh)	("REPLY" ksh bash)
+    ("rmstar" tcsh)			("savehist" tcsh) 
+    ("SECONDS" ksh bash)		("shell" csh tcsh) 
+    ("SHELL")				("SHLVL" bash tcsh) 
+    ("showdots" tcsh)			("sl" tcsh)	
+    ("status" csh tcsh)			("SYSTYPE" tcsh) 
+    ("tcsh" tcsh)			("term" tcsh) 
+    ("TERM")				("TERMCAP")	
+    ("time" csh tcsh)			("TMOUT" ksh bash) 
+    ("tperiod" tcsh)			("tty" tcsh) 
+    ("UID" bash)			("uid" tcsh)
+    ("verbose" csh tcsh)		("version" tcsh)
+    ("visiblebell" tcsh)		("VISUAL")
+    ("watch" tcsh)			("who" tcsh)
+    ("wordchars" tcsh))
+  "Alist of all environment and shell variables used for completing read.
+Variables only understood by some shells are associated to a list of those.")
+
+
+
+(defvar sh-font-lock-keywords
+  '(("[ \t]\\(#.*\\)" 1 font-lock-comment-face)
+    ("\"[^`]*\"\\|'.*'\\|\\\\[^\nntc]" . font-lock-string-face))
+  "*Rules for highlighting shell scripts.
+This variable is included into the various variables
+`sh-SHELL-font-lock-keywords'.  If no such variable exists for some shell,
+this one is used.")
+
+
+(defvar sh-sh-font-lock-keywords
+  (append sh-font-lock-keywords
+	  '(("\\(^\\|[^-._a-z0-9]\\)\\(case\\|do\\|done\\|elif\\|else\\|esac\\|fi\\|for\\|if\\|in\\|then\\|until\\|while\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
+  "*Rules for highlighting Bourne shell scripts.")
+
+(defvar sh-ksh-font-lock-keywords
+  (append sh-sh-font-lock-keywords
+	  '(("\\(^\\|[^-._a-z0-9]\\)\\(function\\|select\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
+  "*Rules for highlighting Korn shell scripts.")
+
+(defvar sh-bash-font-lock-keywords
+  (append sh-sh-font-lock-keywords
+	  '(("\\(^\\|[^-._a-z0-9]\\)\\(function\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
+  "*Rules for highlighting Bourne again shell scripts.")
+
+
+(defvar sh-csh-font-lock-keywords
+  (append sh-font-lock-keywords
+	  '(("\\(^\\|[^-._a-z0-9]\\)\\(breaksw\\|case\\|default\\|else\\|end\\|endif\\|foreach\\|if\\|switch\\|then\\|while\\)\\($\\|[^-._a-z0-9]\\)" 2 font-lock-keyword-face t)))
+  "*Rules for highlighting C shell scripts.")
+
+(defvar sh-tcsh-font-lock-keywords sh-csh-font-lock-keywords
+  "*Rules for highlighting Toronto C shell scripts.")
+
+
+
+;; mode-command and utility functions
+
+;;;###autoload
+(defun sh-or-other-mode ()
+  "Decide whether this is a compiled executable or a script.
+Usually the file-names of scripts and binaries cannot be automatically
+distinguished, so the presence of an executable's magic number is used."
+  (funcall (or (let ((l magic-number-alist))
+		 (while (and l
+			     (not (looking-at (car (car l)))))
+		   (setq l (cdr l)))
+		 (cdr (car l)))
+	       'sh-mode)))
+
+
+;;;###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.
+
+The syntax of the statements varies with the shell being used.  The syntax of
+statements can be modified by putting a property on the command or new ones
+defined with `define-sh-skeleton'.  For example
+
+    (put 'sh-until 'ksh '(() \"until \" _ \\n > \"do\" \\n \"done\"))
+or
+    (put 'sh-if 'smush '(\"What? \" \"If ya got ( \" str \" ) ya betta { \" _ \" }\"))
+
+where `sh-until' or `sh-if' have been or will be defined by `define-sh-skeleton'.
+
+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-select]	 select statement
+\\[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-manual-entry]	 Display the Unix manual entry for the current command or shell.
+
+\\[sh-query-for-variable]	 Unless quoted with \\, query for a variable with completions offered.
+\\[sh-maybe-here-document]	 Without prefix, following an unquoted < inserts here document.
+{, (, [, ', \", `
+	Unless quoted with \\, insert the pairs {}, (), [], or '', \"\", ``."
+  (interactive)
+  (kill-all-local-variables)
+  (set-syntax-table sh-mode-syntax-table)
+  (use-local-map sh-mode-map)
+  (make-local-variable 'indent-line-function)
+  (make-local-variable 'comment-start)
+  (make-local-variable 'comment-start-skip)
+  (make-local-variable 'after-save-hook)
+  (make-local-variable 'require-final-newline)
+  (make-local-variable 'sh-shell-path)
+  (make-local-variable 'sh-shell)
+  (make-local-variable 'sh-shell-is-csh)
+  (make-local-variable 'pair-alist)
+  (make-local-variable 'pair-filter)
+  (make-local-variable 'font-lock-keywords)
+  (make-local-variable 'font-lock-keywords-case-fold-search)
+  (make-local-variable 'sh-variables)
+  (setq major-mode 'sh-mode
+	mode-name "Shell-script"
+	;; Why can't Emacs have one standard function with some parameters?
+	;; Only few modes actually analyse the previous line's contents
+	indent-line-function 'sh-indent-line
+	comment-start "# "
+	after-save-hook 'sh-chmod
+	tab-width sh-tab-width
+	;; C shells do
+	require-final-newline t
+	font-lock-keywords-case-fold-search nil
+	pair-alist '((?` _ ?`))
+	pair-filter 'sh-quoted-p)
+  ; parse or insert magic number for exec()
+  (goto-char (point-min))
+  (sh-set-shell
+   (if (looking-at "#![\t ]*\\([^\t\n ]+\\)")
+       (buffer-substring (match-beginning 1) (match-end 1))
+     sh-shell-path))
+  ;; find-file is set by `normal-mode' when called by `after-find-file'
+  (and (boundp 'find-file) find-file
+       (or (eq sh-find-file-modifies t)
+	   (set-buffer-modified-p nil)))
+  (run-hooks 'sh-mode-hook))
+;;;###autoload
+(defalias 'shell-script-mode 'sh-mode)
+
+
+
+(defmacro define-sh-skeleton (command documentation &rest definitions)
+  "Define COMMAND with [DOCSTRING] to insert statements as in DEFINITION ...
+Prior definitions (e.g. from ~/.emacs) are maintained.
+Each definition is built up as (SHELL PROMPT ELEMENT ...).  Alternately
+a synonym definition can be (SHELL . PREVIOUSLY-DEFINED-SHELL).
+
+For the meaning of (PROMPT ELEMENT ...) see `skeleton-insert'.
+Each DEFINITION is actually stored as
+	(put COMMAND SHELL (PROMPT ELEMENT ...)),
+which you can also do yourself."
+  (or (stringp documentation)
+      (setq definitions (cons documentation definitions)
+	    documentation ""))
+  ;; The compiled version doesn't.
+  (require 'backquote)
+  (`(progn
+      (let ((definitions '(, definitions)))
+	(while definitions
+	  ;; skeleton need not be loaded to define these
+	  (or (and (not (if (boundp 'skeleton-debug) skeleton-debug))
+		   (get '(, command) (car (car definitions))))
+	      (put '(, command) (car (car definitions))
+		   (if (symbolp (cdr (car definitions)))
+		       (get '(, command) (cdr (car definitions)))
+		     (cdr (car definitions)))))
+	  (setq definitions (cdr definitions))))
+      (put '(, command) 'menu-enable '(get '(, command) sh-shell))
+      (defun (, command) ()
+	(, documentation)
+	(interactive)
+	(skeleton-insert
+	 (or (get '(, command) sh-shell)
+	     (error "%s statement syntax not defined for shell %s."
+		    '(, command) sh-shell)))))))
+
+
+
+(defun sh-indent-line ()
+  "Indent as far as preceding line, then by steps of `tab-width'.
+If previous line starts with a comment, it's considered empty."
+  (interactive)
+  (let ((previous (save-excursion
+		    (line-move -1)
+		    (back-to-indentation)
+		    (if (looking-at comment-start-skip)
+			0
+		      (current-column)))))
+    (save-excursion
+      (indent-to (if (eq this-command 'newline-and-indent)
+		     previous
+		   (if (< (current-column)
+			  (progn (back-to-indentation)
+				 (current-column)))
+		       (if (eolp) previous 0)
+		     (if (eolp)
+			 (max previous (* (1+ (/ (current-column) tab-width))
+					  tab-width))
+		       (* (1+ (/ (current-column) tab-width)) tab-width))))))
+      (if (< (current-column) (current-indentation))
+	  (skip-chars-forward " \t"))))
+
+
+(defun sh-remember-variable (var)
+  "Make VARIABLE available for future completing reads in this buffer."
+  (or (< (length var) sh-remember-variable-min)
+      (assoc var sh-variables)
+      (setq sh-variables (cons (list var) sh-variables)))
+  var)
+
+
+;; Augment the standard variables by those found in the environment.
+(if (boundp 'process-environment)(let ((l process-environment))
+  (while l
+    (sh-remember-variable (substring (car l)
+				     0 (string-match "=" (car l))))
+    (setq l (cdr l)))))
+
+
+
+(defun sh-quoted-p ()
+  "Is point preceded by an odd number of backslashes?"
+  (eq 1 (% (- (point) (save-excursion
+			(skip-chars-backward "\\\\")
+			(point)))
+	   2)))
+
+
+
+(defun sh-executable (command)
+  "If COMMAND is an executable in $PATH its full name is returned.  Else nil."
+  (let ((point (point))
+	(buffer-modified-p (buffer-modified-p))
+	buffer-read-only after-change-function)
+    (call-process "sh" nil t nil "-c" (concat "type " command))
+    (setq point (prog1 (point)
+		  (goto-char point)))
+    (prog1
+	(and (looking-at sh-executable)
+	     (eq point (match-end 0))
+	     (buffer-substring (match-beginning 1) (match-end 1)))
+      (delete-region (point) point)
+      (set-buffer-modified-p buffer-modified-p))))
+
+
+
+(defun sh-chmod ()
+  "This gets called after saving a file to assure that it be executable.
+You can set the absolute or relative mode with `sh-chmod-argument'."
+  (if sh-chmod-argument
+       (or (file-executable-p buffer-file-name)
+	   (shell-command (concat "chmod " sh-chmod-argument
+				  " " buffer-file-name)))))
+
+;; 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-sh-skeleton sh-case
+  "Insert a case/switch statement in the current shell's syntax."
+  (sh "expression: "
+      "case " str " in" \n
+      > (read-string "pattern: ") ?\) \n
+      > _ \n
+      ";;" \n
+      ( "other pattern, %s: "
+	< str ?\) \n
+	> \n
+	";;" \n)
+      < "*)" \n
+      > \n
+      resume:
+      < < "esac")
+  (ksh . sh)
+  (bash . sh)
+  (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")
+  (tcsh . csh))
+
+
+
+(define-sh-skeleton sh-for
+  "Insert a for loop in the current shell's syntax."
+  (sh "Index variable: "
+      "for " str " in " _ "; do" \n
+      > ?$ (sh-remember-variable str) \n
+      < "done")
+  (ksh . sh)
+  (bash . sh)
+  (csh "Index variable: "
+       "foreach " str " ( " _ " )" \n
+       > ?$ (sh-remember-variable str) \n
+       < "end")
+  (tcsh . csh))
+
+
+
+(define-sh-skeleton sh-indexed-loop
+  "Insert an indexed loop from 1 to n in the current shell's syntax."
+  (sh "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")
+  (ksh . sh)
+  (bash . sh)
+  (csh "Index variable: "
+       "@ " str " = 1" \n
+       "while( $" str " <= "
+       (read-string "upper limit: ")
+       " )" \n
+       > _ ?$ (sh-remember-variable str) \n
+       "@ " str "++" \n
+       < "end")
+  (tcsh . csh))
+
+
+
+(defun sh-add (var delta)
+  "Insert an addition of VAR and prefix DELTA for Bourne type shells."
+  (interactive
+   (list (sh-remember-variable
+	  (completing-read "Variable: " sh-variables
+			   (lambda (element)
+			     (or (not (cdr element))
+				 (memq sh-shell (cdr element))))))
+	 (prefix-numeric-value current-prefix-arg)))
+  (setq delta (concat (if (< delta 0) " - " " + ")
+		      (abs delta)))
+  (skeleton-insert
+   (assq sh-shell
+	 '((sh "`expr $" var delta "`")
+	   (ksh "$(( $" var delta " ))")
+	   (bash "$[ $" var delta " ]")))
+   t))
+
+
+
+(define-sh-skeleton sh-function
+  "Insert a function definition in the current shell's syntax."
+  (sh ()
+      "() {" \n
+      > _ \n
+      < "}")
+  (ksh "name: "
+       "function " str " {" \n
+       > _ \n
+       < "}")
+  (bash "name: "
+       "function " str "() {" \n
+       > _ \n
+       < "}"))
+
+
+
+(define-sh-skeleton sh-if
+  "Insert an if statement in the current shell's syntax."
+  (sh "condition: "
+      "if [ " str " ]; then" \n
+      > _ \n
+      ( "other condition, %s: "
+	< "elif [ " str " ]; then" \n
+	> \n)
+      < "else" \n
+      > \n
+      resume:
+      < "fi")
+  (ksh . sh)
+  (bash . sh)
+  (csh "condition: "
+       "if( " str " ) then" \n
+       > _ \n
+       ( "other condition, %s: "
+	 < "else if ( " str " ) then" \n
+	 > \n)
+       < "else" \n
+       > \n
+       resume:
+       < "endif")
+  (tcsh . csh))
+
+
+
+(define-sh-skeleton sh-select
+  "Insert a select statement in the current shell's syntax."
+  (ksh "Index variable: "
+       "select " str " in " _ "; do" \n
+       > ?$ str \n
+       < "done"))
+(put 'sh-select 'menu-enable '(get 'sh-select sh-shell))
+
+
+
+(define-sh-skeleton sh-until
+  "Insert an until loop in the current shell's syntax."
+  (sh "condition: "
+      "until [ " str " ]; do" \n
+      > _ \n
+      < "done")
+  (ksh . sh)
+  (bash . sh))
+(put 'sh-until 'menu-enable '(get 'sh-until sh-shell))
+
+
+(define-sh-skeleton sh-while
+  "Insert a while loop in the current shell's syntax."
+  (sh "condition: "
+      "while [ " str " ]; do" \n
+      > _ \n
+      < "done")
+  (ksh . sh)
+  (bash . sh)
+  (csh "condition: "
+       "while( " str " )" \n
+       > _ \n
+       < "end")
+  (tcsh . csh))
+
+
+
+(defun sh-query-for-variable (arg)
+  "Unless quoted with `\\', query for variable-name with completions.
+Prefix arg 0 means don't insert `$' before the variable.
+Prefix arg 2 or more means only do self-insert that many times.
+  If { is pressed as the first character, it will surround the variable name."
+  (interactive "*p")
+  (or (prog1 (or (> arg 1)
+		 (sh-quoted-p))
+	(self-insert-command arg))
+    (let (completion-ignore-case
+	  (minibuffer-local-completion-map
+	   (or (get 'sh-query-for-variable 'keymap)
+	       (put 'sh-query-for-variable 'keymap
+		    (copy-keymap minibuffer-local-completion-map))))
+	  (buffer (current-buffer)))
+      ;; local function that depends on `arg' and `buffer'
+      (define-key minibuffer-local-completion-map "{"
+	(lambda () (interactive)
+	  (if (or arg (> (point) 1))
+	      (beep)
+	    (save-window-excursion
+	      (setq arg t)
+	      (switch-to-buffer-other-window buffer)
+	      (insert "{}")))))
+      (insert
+       (prog1
+	   (sh-remember-variable
+	    (completing-read "Variable: " sh-variables
+			     (lambda (element)
+			       (or (not (cdr element))
+				   (memq sh-shell (cdr element))))))
+	 (if (eq t arg) (forward-char 1))))
+      (if (eq t arg) (forward-char 1)))))
+
+
+
+(defun sh-assignment (arg)
+  "Insert self.  Remember previous identifier for future completing read."
+  (interactive "p")
+  (if (eq arg 1)
+      (sh-remember-variable
+       (save-excursion
+	 (buffer-substring
+	  (progn
+	    (if (memq sh-shell sh-assignment-space)
+		(skip-chars-backward " \t"))
+	    (point))
+	  (progn
+	    (skip-chars-backward "a-zA-Z0-9_")
+	    (point))))))
+  (self-insert-command arg))
+
+
+
+(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
+	(goto-char (- (point) 2))
+	(sh-quoted-p))
+      (progn
+	(insert sh-here-document-word)
+	(or (looking-at "[ \t\n]") (insert ? ))
+	(end-of-line 1)
+	(newline)
+	(save-excursion (insert ?\n sh-here-document-word)))))
+
+
+;; various other commands
+
+(autoload 'comint-dynamic-complete-filename "comint"
+  "Dynamically complete the filename at point." t)
+
+
+
+(defun sh-newline-and-indent (&optional arg)
+  "Strip unquoted whitespace, insert newline, and indent like current line.
+Unquoted whitespace is stripped from the current line's end, unless a
+prefix ARG is given."
+  (interactive "*P")
+  (let ((previous (current-indentation))
+	(end-of-line (point)))
+    (if arg ()
+      (skip-chars-backward " \t") 
+      (and (< (point) end-of-line)
+	   (sh-quoted-p)
+	   (forward-char 1))
+      (delete-region (point) end-of-line))
+    (newline)
+    (indent-to previous)))
+
+
+
+(defun sh-set-shell (shell)
+  "Set this buffer's shell to SHELL (a string).
+Calls the value of `sh-set-shell-hook' if set."
+  (interactive "sName or path of shell: ")
+  (save-excursion
+    (goto-char (point-min))
+    (setq sh-shell-path (if (file-name-absolute-p shell)
+				      shell
+				    (or (sh-executable shell)
+					(error "Cannot find %s." shell)))
+	  sh-shell (intern (file-name-nondirectory sh-shell-path))
+	  sh-shell-is-csh (memq sh-shell '(csh tcsh))
+	  font-lock-keywords
+	  (intern-soft (format "sh-%s-font-lock-keywords" sh-shell))
+	  font-lock-keywords (if (and font-lock-keywords
+				      (boundp font-lock-keywords))
+				 (symbol-value font-lock-keywords)
+			       sh-font-lock-keywords)
+	  comment-start-skip (if sh-shell-is-csh
+				 "\\(^\\|[^$]\\|\\$[^{]\\)#+[\t ]*"
+			       "\\(^\\|[^$]\\|\\$[^{]\\)\\B#+[\t ]*")
+	  mode-line-process (format ": %s" sh-shell)
+	  shell (concat sh-shell-path
+			(and sh-shell-argument " ")
+			sh-shell-argument))
+    (and (not buffer-read-only)
+	 (not (if buffer-file-name
+		  (string-match sh-magicless-file-regexp buffer-file-name)))
+	 ;; find-file is set by `normal-mode' when called by `after-find-file'
+	 (if (and (boundp 'find-file) find-file) sh-find-file-modifies t)
+	 (if (looking-at "#!")
+	     (and (skip-chars-forward "#! \t")
+		  (not (string= shell
+				(buffer-substring (point)
+						  (save-excursion (end-of-line)
+								  (point)))))
+		  (if sh-query-for-magic
+		      (y-or-n-p (concat "Replace magic number by ``#! "
+					shell "''? "))
+		    (message "Magic number ``%s'' replaced."
+			     (buffer-substring (point-min) (point))))
+		  (not (delete-region (point) (progn (end-of-line) (point))))
+		  (insert shell))
+	   (insert "#! " shell ?\n))))
+  (run-hooks 'sh-set-shell-hook))
+
+
+
+(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))))
+
+
+
+(defun sh-manual-entry (arg)
+  "Display the Unix manual entry for the current command or shell.
+Universal argument ARG, is passed to `Man-getpage-in-background'."
+  (interactive "P")
+  (let ((command (save-excursion
+		   (sh-beginning-of-command)
+		   (sh-executable
+		    (buffer-substring (point)
+				      (progn (forward-sexp) (point)))))))
+    (setq command (read-input (concat "Manual entry (default "
+				      (symbol-name sh-shell)
+				      "): ")
+			      (if command
+				  (file-name-nondirectory command))))
+    (manual-entry (if (string= command "")
+		      (symbol-name sh-shell)
+		    command)
+		  arg)))
+
+;; sh-script.el ends here