Tweaking shell-script indentation in GNU Emacs

The default indentation levels of GNU Emacs for the case statements of sh(1) scripts (and derivative or compatible shells) are a bit smal for my taste (4 columns). This makes case statements in shell scripts look almost “ok”, but I don’t like the small indentation for the rest of my scripts. Here is how the default indentation would look for a part of my ~/.bashrc file:

A demo of the default Emacs indentation of 4 columns for case labels in sh-mode

I like 8 column indentation in nested statements, so one of the things I do in my ~/elisp/keramida-hooks.el startup file is:

(defun gker-setup-sh-mode ()
  "My own personal preferences for `sh-mode'.

This is a custom function that sets up the parameters I usually
prefer for `sh-mode'.  It is automatically added to
`sh-mode-hook', but is can also be called interactively."
  (interactive)
  (setq sh-basic-offset 8
        sh-indentation 8))
(add-hook 'sh-mode-hook 'gker-setup-sh-mode)

Setting the basic indentation to 8 columns has an undesired side-effect though. It indents the labels and the code of case statements too much for my taste. The default indentation levels set up by sh-mode for these statements indent the case label by sh-basic-offset columns and the commands after a case label by twice the same amount.

The same part of my ~/.bashrc file becomes then:

The effect of larger (8 column) indentation on case statements of sh-mode

The extra indentation of case labels seems a bit excessive now. It consumes 8 extra columns of precious horizontal space. For someone who likes reading source code in relatively narrow windows like me, this is a waste of space that would look more useful if it contained code instead of empty space / indentation.

GNU Emacs is, however, as I’ve written several times in the past, an infinitely customizable, extensible editor. There are, in fact, at least two different ways of customizing the indentation level of case labels and case commands (after those labels) in shell scripts. The first option is to modify the syntax table of sh-mode for these two syntax elements right there, inside the same hook that tweaks the default indentation offset from 4 columns to 8 columns:

(defun gker-setup-sh-mode ()
  "My own personal preferences for `sh-mode'.

This is a custom function that sets up the parameters I usually
prefer for `sh-mode'.  It is automatically added to
`sh-mode-hook', but is can also be called interactively."
  (interactive)
  (setq sh-basic-offset 8
        sh-indentation 8
        ;; Tweak the indentation level of case-related syntax elements, to avoid
        ;; excessive indentation because of the larger than default value of
        ;; `sh-basic-offset' and other indentation options.
        sh-indent-for-case-label 0
        sh-indent-for-case-alt '+)
(add-hook 'sh-mode-hook 'gker-setup-sh-mode)

This way of setting up the indentation for the two case-related syntax elements has the advantage that it is as close as possible to the sh-basic-offset and the other related changes. A small comment above the case-related syntax elements is sufficient to document why these syntax changes are needed.

Another option is to set the default values of these two syntax elements of sh-mode, by something like:

(setq-default sh-indent-for-case-label 0)
(setq-default sh-indent-for-case-alt '+)

This way of reducing the indentation for case-related shell script code should work too. It’s not as nice as the first option, but it can be used to achieve similar results, even if it runs outside of the mode hooks for sh-mode.

After one of these changes has been evaluated, the indentation of case-statements in sh-mode changes to

Indentation of case-labels and case commands fixed with sh-basic-offset at 8 columns

This is much better. At least it’s much better according to my taste, and it precisely matches the default style of indentation I use when I am writing scripts in that other popular editor.

As an extra, bonus side-effect, by configuring the syntax elements of sh-mode correctly, I can now mark any part of a shell script as the active region, and the M-x indent-region command (bound to C-M-\ by default in Emacs) will DTRT and indent the marked region with the correct style, i.e. the one I like using :-)

5 thoughts on “Tweaking shell-script indentation in GNU Emacs

  1. Leonidas Tsampros

    Hehe. Nice post. Actually, I was a little bit baffled by “(setq-default sh-indent-for-case-alt ‘+)” but with a little bit of help from the built-in Emacs documentation system , the + symbol is evaluated to sh-basic-offset.

  2. Ian Eure

    All the variables you set can be manipulated through customize (M-x customize-variable RET sh-basic-offset RET), which should be a bit faster than using a mode hook.

    If you do want to stick to the hook, you might consider using a lambda function instead:

    (add-hook ‘sh-mode-hook (lambda () (setq …)))

  3. Pingback: Bookmarks about Smal

  4. Bruce Korb

    One other tiny thing to remember: these variables get local copies in any open shell script mode buffers, so you need to type M-x shell-script-mode for them to take effect.

Comments are closed.