You could try smartparens. With the settings below:
Pressing [ SPC becomes [ | ]
Pressing [ [ SPC becomes [[ | ]]
Selecting foo
and pressing [ becomes [ foo ]
Pressing [ a second time becomes [[ foo ]]
(require 'smartparens)
(smartparens-global-mode 1)
(show-paren-mode 1)
(defun sp-shell-insert-spaces (_id action _context)
(when (eq action 'wrap)
(save-excursion
(goto-char (sp-get sp-last-wrapped-region :end-in))
(unless (looking-at "]]")
(insert " "))
(goto-char (sp-get sp-last-wrapped-region :beg-in))
(unless (looking-back "\\[\\[" nil)
(insert " ")))))
(sp-with-modes 'sh-mode
(sp-local-pair "[" "]" :actions '(wrap insert navigate) :post-handlers '(sp-shell-insert-spaces))
(sp-local-pair "[ " " ]" :actions '(wrap insert navigate))
(sp-local-pair "[[" "]]" :actions '(wrap insert navigate))
(sp-local-pair "[[ " " ]]" :actions '(wrap insert navigate)))
AThe code to create a simple if..fi
block would look something like this:
(defun sp-shell-post-handler (id action _context)
(when (eq action 'insert)
(insert " ")
(save-excursion
(indent-according-to-mode)
(newline))))
(sp-local-pair 'sh-mode "if" "fi"
:when '(("SPC""TAB"))
:unless '(sp-in-string-p sp-in-comment-p)
:actions '(insert navigate)
:post-handlers '(:add sp-shell-post-handler))