I'm sure emacs used to do it my way, but things appear to have changed (probably years ago, I've just been living with the pain).

I want this (to conform to corp. standards):

    curl -k -A http://foo.com |
    grep -m1 multiform |
    tr '=' '\n' |
    tail -1 |
    cut -d "'" -f 2

But emacs-25.1 insists that this is the right way to indent:

    curl -k -A http://foo.com |
        grep -m1 multiform |
        tr '=' '\n' |
        tail -1 |
        cut -d "'" -f 2

... quite apart from our standards, it's ugly.

I've tried messing around with sh-learn-line-indent and it offers rules like:

close-all ")"
list-intro ";"

... but playing with them gives no joy at all.

Any ideas?

  • Looks like being worth a bug-report. Nov 2, 2016 at 7:12
  • You can see if the settings from this question (not the answer): emacs.stackexchange.com/q/26329/184 help.
    – T. Verron
    Nov 2, 2016 at 8:47
  • I did read that one but it appears to be another version of emacs??? Anyway, mine doesn't have variables sh-indentation-*. And I have tried yr suggestion of moving aside .emacs .elisp.d and .config/emacs. Nothing seems to make any difference. How does other people emacs' treat the above?
    – wef
    Nov 2, 2016 at 10:50
  • ... and I'll enter a bug report if I get nowhere with this - good suggestion!
    – wef
    Nov 2, 2016 at 10:53

2 Answers 2


For the indentation after the | you should be able to get the right result by going to the incorrect line (i.e. the one that starts with grep) and then do M-x smie-config-set-indent RET before "|" RET nil RET.

But smie-config is not up to the task when it comes to tweaking the behavior of the close paren in the way you want. For that you'll need actual code. One possible approach could look like:

(defun my-sh-indent-rules ()
  (when (eq ?= (char-before))
    (skip-chars-backward "[:alnum:]_=")

(add-hook 'sh-mode-hook
          (lambda ()
            (add-hook 'smie-indent-functions
                      nil 'local)))

BTW, if you want to handle the | alignment "by hand" rather than via smie-config, you could do it this way:

(defun my-sh-smie-rules (orig-fun kind token)
  (pcase (cons kind token)
   (`(:before . "|") nil)
   (_ (funcall orig-fun kind token))))
(advice-add 'sh-smie-sh-rules :around #'my-sh-smie-rules)

wef: This is the final bit of the puzzle that Stefan gave me in my muddle below - how to save the customisations for next time. As he said, I needed to teach smie about my preferences eg with M-x smie-config-set-indent then run M-x smie-config-save and look at the variable smie-config. Finally, I copied those rules (in my case, just one rule) into .emacs:

(defun my-sh-indent-rules ()
  (smie-config-local '((nil :before "|" (column . 8)))))
(add-hook 'smie-indent-functions #'my-sh-indent-rules)

There may be other ways to do this eg with the commands in sh-mode eg M-x sh-learn-line-indent - but I wasn't able to get it to work.

  • Hmmm - M-x smie-config-set-indent gave me choices for Adjust rule: of: after ";" and before ";" but it objected to: before "|" with "No match"
    – wef
    Nov 3, 2016 at 0:40
  • my-sh-indent-rules appears to work!! Thank you for that. I tried upvoting the answer but I don't have reputation for that.
    – wef
    Nov 3, 2016 at 0:46
  • M-x smie-config-show-indent on the 'grep' line shows: Rules used: : before "|" -> (column . 8), :elem basic -> 4, :after "|" -> nil
    – wef
    Nov 3, 2016 at 6:06
  • The output you give for smie-config-show-indent looks right, so you just need to M-x smie-config-set-indent at the same place and set before "|" to nil.
    – Stefan
    Nov 3, 2016 at 12:41
  • Hoo boy! that worked like a bought one! Thanks so much! Now I need to try and work out why I missed that - I thought I'd tried all the combinations and permutations. Obviously not!
    – wef
    Nov 4, 2016 at 4:21

The following is a broken (or incomplete) answer ... I'm trying to fix my original problem using the sh-mode calls only instead of the lower-level smie calls that Stefan was kind enough to provide, just to see if it can be done. Reading those docs and running in a fresh emacs -Q session, I did this:

M-x sh-learn-line-indent RET before "|" RET nil

The style then worked for me and indented the lines ending in "|" - but not the final ")" - I'll still need Stefan's code for that. Then:

M-x sh-name-style RET bobs
M-x sh-save-styles-to-buffer RET *scratch*

and then used that code to create this:

(defun my-sh-indent-rules ()
  (setq sh-styles-alist
           (sh-basic-offset . 4)
           (sh-first-lines-indent . 0)
           (sh-indent-after-case . +)
           (sh-indent-after-do . +)
           (sh-indent-after-done . 0)
           (sh-indent-after-else . +)
           (sh-indent-after-if . +)
           (sh-indent-after-loop-construct . +)
           (sh-indent-after-open . +)
           (sh-indent-comment . t)
           (sh-indent-for-case-alt . ++)
           (sh-indent-for-case-label . +)
           (sh-indent-for-continuation . +)
           (sh-indent-for-do . 0)
           (sh-indent-for-done . 0)
           (sh-indent-for-else . 0)
           (sh-indent-for-fi . 0)
           (sh-indent-for-then . 0))))
  (sh-load-style "bobs"))
(add-hook 'sh-mode-hook #'my-sh-indent-rules)

... note that it has no before "|" rule. Perhaps it doesn't know how to do that?

Anyway, running that in a fresh emacs -Q session it doesn't work even after a M-x sh-load-style RET bobs, so I'm reverting to the default behaviour.

I'm happy with Stefan's smie solution above, but I'd be interested to know how to make it work with sh-mode.

  • I think sh-styles-* stuff only works if you set sh-use-smie to nil.
    – npostavs
    Dec 28, 2018 at 1:51
  • @npostavs - you're right, it works now, at least for indenting the pipeline. It may need more work to get the ')' to indent correctly.
    – wef
    Dec 29, 2018 at 2:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.