--- /dev/null
+#+TITLE: Making Shell Scripts Executable Just-in-Time
+#+SUBTITLE: A different take on adding exec permissions to shell script in Emacs
+#+DESCRIPTION: Blog post by Eshel Yaron about a different take on adding exec permissions to shell script in Emacs
+#+KEYWORDS: emacs lisp
+#+DATE: 2023-04-08
+
+In my work I often need to write small programs or scripts that
+accomplish very specific tasks. Many of these involve fetching and
+analyzing data from JSON-based APIs, and I tend to use shell scripts
+for that sorta thing. I mostly rely on ~curl~, ~jq~, and ~parallel~,
+as well as the other usual suspects ~grep~, ~sed~, ~tr~, ~head~,
+~sort~, ~uniq~, and the occasional ~awk~.
+
+I usually begin writing these scripts as one-liners in an [[info:emacs#Interactive Shell][Emacs Shell
+buffer]] before moving to a =.sh= file. There I can generalize that
+one-liner and format it nicely, and at some point also test it. Of
+course, to test it I need to run it, and that requires giving the
+newly created shell script /executable permissions/.
+
+In Emacs, there are quite a few ways one can go about making their
+script executable. You can, for instance, do ~M-! M-n chmod +x RET~,
+but if you ask me that's too much typing for such a common task!
+Worse, it's not very /Emacsy/ either. Instead, the way I've been
+doing this for a long time was jumping to Dired with ~C-x C-j~, and
+immediately typing ~M +x RET~ to make the file executable. That works
+well and doesn't require me to type ~chmod~, but it still takes some
+typing and--crucially--it forces me to switch to Dired, when I really
+just wanted was to test my shell script from its own buffer.
+
+Recently, after going through that flow one too many times, I figured
+/there must be a better way/. Ideally I would have simply liked to
+hit ~C-c C-x~ (AKA ~executable-interpret~) in my shell script buffer
+without the need to explicitly make it executable beforehand. I
+wasn't surprised to quickly discover that Emacs comes with a dedicated
+solution for this problem built-in. Typing ~C-h f~ followed by ~exec
+file TAB~ lists among the completion candidates a function
+~executable-make-buffer-file-executable-if-script-p~ that, as its
+lengthy name suggest, makes the visited file executable if it happens
+to be a script.
+
+The standard piece of advice regarding this function, that you find
+written throughout the interwebs by Emacs users and developers alike,
+is that one should put this function in their after ~after-save-hook~.
+
+In 2003, Stefan Monnier [[https://lists.gnu.org/archive/html/emacs-devel/2003-05/msg00249.html][wrote on the emacs-devel mailing list]]:
+
+#+begin_quote
+we have make-buffer-file-executable-if-script-p and I recommend
+everybody add it to his after-save-hook.
+#+end_quote
+
+This solves the problem of manually changing a shell script's
+permissions prior to running it, because the script is made executable
+the second you save it--and saving the shell script buffer to a file
+is anyway a prerequisite for running it.
+
+In the 20 years that passed since Stefan brought up that nifty trick,
+this advice was echoed in many esteemed Emacs blogs:
+
+- Micky Petersen [[https://www.masteringemacs.org/article/script-files-executable-automatically][suggested it]] in his /Mastering Emacs/ book,
+- Marcin Borkowski mentioned it among other [[https://mbork.pl/2015-01-10_A_few_random_Emacs_tips][random Emacs tips]],
+- Oleh Krehel included it in his [[https://oremacs.com/2016/01/18/emacs-rhythmbox/][Emacs as system-wide Rhythmbox]] post, and
+- Bozhidar Batsov [[https://emacsredux.com/blog/2021/09/29/make-script-files-executable-automatically/][wrote about it on his blog]] as well.
+
+Yet, the function itself predates Stefan's comment. It appears that
+originally Noah Friedman wrote it all the way back in the year 2000
+when it was added to Emacs by Dave Love--tracing the function's
+history by going to its definition in =executable.el= and hitting ~M-h
+C-x v h~ reveals the following commit:
+
+#+begin_src diff
+ commit 778e1d17edb36cc53fd7419436311f2e2bc622ff
+ Author: Dave Love <fx@gnu.org>
+ Date: Fri Jun 9 09:38:58 2000 +0000
+
+ ...
+ (make-buffer-file-executable-if-script-p): New function from Noah
+ Friedman.
+
+ diff --git a/lisp/progmodes/executable.el b/lisp/progmodes/executable.el
+ --- a/lisp/progmodes/executable.el
+ +++ b/lisp/progmodes/executable.el
+ @@ -264,1 +270,16 @@
+ -
+ +(defun make-buffer-file-executable-if-script-p ()
+ + "Make file executable according to umask if not already executable.
+ +If file already has any execute bits set at all, do not change existing
+ +file modes."
+ + (and (save-excursion
+ + (save-restriction
+ + (widen)
+ + (goto-char (point-min))
+ + (save-match-data
+ + (looking-at "^#!"))))
+ + (let* ((current-mode (file-modes (buffer-file-name)))
+ + (add-mode (logand ?\111 (default-file-modes))))
+ + (or (/= (logand ?\111 current-mode) 0)
+ + (zerop add-mode)
+ + (set-file-modes (buffer-file-name)
+ + (logior current-mode add-mode))))))
+#+end_src
+
+As we see in the above patch, back in 2000 this function would simply
+look at the start of your buffer, and if begins with a /shebang/ it'd
+ensure that the file has executable permissions. Other than the name
+of the function becoming yet a little longer (the ~executable-~ prefix
+was added, so to follow Elisp namespacing conventions), not much has
+changed in terms of its implementation since then.
+
+Although, as I described earlier, putting
+~executable-make-buffer-file-executable-if-script-p~ into one's
+~after-save-hook~ is a practice promoted by many esteemed members of
+the Emacs community (heck, it even made it to [[https://sachachua.com/dotemacs/index.html][Sacha Chua's config]]), I
+felt uneasy about this solution. I save lots of files, and only a
+minuscule fraction of them are scripts that I wanna make executable.
+That means that the vast majority of my save operations will involve
+some futile busywork and incur a (tiny, but still) needless
+performance penalty. I also think that the way this function decides
+whether or not to make a file executable is too coarse. It doesn't
+examine the file's extension for example, nor does it take into
+account the buffer's major mode. Is it always TRT to make every file
+that starts with a ~#~ and a ~!~ executable? I'm not sure, really.
+It's probably fine, but it makes my security-spidey-sense tingle
+nonetheless.
+
+The solution I came up with is both more conservative (no chance of
+random files becoming executable against my wishes) and more efficient
+(no penalizing all file saves for a few odd scripts). Instead of
+adding ~executable-make-buffer-file-executable-if-script-p~ to my
+after ~after-save-hook~, I settled on adding it as a /advice/ to the
+command ~executable-interpret~. Here's the relevant excerpt from [[file:esy.org][my
+config]]:
+
+#+begin_src emacs-lisp
+ (with-eval-after-load 'executable
+ (define-advice executable-interpret (:before (&rest _) ensure-executable)
+ (unless (file-exists-p buffer-file-name)
+ (basic-save-buffer))
+ (executable-make-buffer-file-executable-if-script-p)))
+#+end_src
+
+This ~:before~ advice takes care of making the visited executable
+exactly when I'm trying to actually execute it--just in time.
margin-left: auto;
margin-right: auto;
width: 90%;
- max-width: 90ch;
+ max-width: 82ch;
/* font-size: 1.1em; */
font-family: "Helvetica Neue", sans-serif;
}
/* font-lock-regexp-grouping-construct */
color: #3fb83f;
}
+.org-diff-added {
+ /* diff-added */
+ color: #a0e0a0;
+ background-color: #003b1f;
+}
+.org-diff-changed {
+ /* diff-changed */
+ color: #efef80;
+ background-color: #363300;
+}
+.org-diff-changed-unspecified {
+ /* diff-changed-unspecified */
+ color: #efef80;
+ background-color: #363300;
+}
+.org-diff-error {
+ /* diff-error */
+ color: #ef6560;
+ font-weight: bold;
+}
+.org-diff-file-header {
+ /* diff-file-header */
+ font-weight: bold;
+}
+.org-diff-function {
+ /* diff-function */
+ background-color: #303230;
+}
+.org-diff-hunk-header {
+ /* diff-hunk-header */
+ background-color: #303230;
+ font-weight: bold;
+}
+.org-diff-index {
+ /* diff-index */
+ font-style: italic;
+}
+.org-diff-indicator-added {
+ /* diff-indicator-added */
+ color: #a0e0a0;
+ background-color: #003b1f;
+}
+.org-diff-indicator-changed {
+ /* diff-indicator-changed */
+ color: #efef80;
+ background-color: #363300;
+}
+.org-diff-indicator-removed {
+ /* diff-indicator-removed */
+ color: #ffbfbf;
+ background-color: #4e1119;
+}
+.org-diff-nonexistent {
+ /* diff-nonexistent */
+ font-weight: bold;
+}
+.org-diff-refine-added {
+ /* diff-refine-added */
+ color: #a0e0a0;
+ background-color: #03512f;
+}
+.org-diff-refine-changed {
+ /* diff-refine-changed */
+ color: #efef80;
+ background-color: #4a4a00;
+}
+.org-diff-refine-removed {
+ /* diff-refine-removed */
+ color: #ffbfbf;
+ background-color: #751a1f;
+}
+.org-diff-removed {
+ /* diff-removed */
+ color: #ffbfbf;
+ background-color: #4e1119;
+}
.timestamp {
color: #5dc0aa;