A habit I'm thinking of adopting for my shell scripting:

Write scripts in the form of a sequence of function definitions, with no commands at the top level that actually do anything. Then, at the very end, write a compound statement that calls one of the functions and ends with an exit. Like this:

#!/bin/bash
subroutine() { ... }
main() { ... }
{ main "$@"; exit $?; }

The idea is that the shell doesn't actually _do_ anything while reading this script until it reads the braced compound statement at the end. And then it's committed to exiting during the execution of that statement. So it won't ever read from the script file again.

With a normal shell script, it's dangerous to edit the script file while an instance of the shell is still running it, because the shell will read from the modified version of the file starting at the file position it had got to in the old one, perhaps reading a partial command or the wrong command and doing something you didn't want. But with a script in this style, the shell finishes reading the script before it does anything, so it's safe to edit.

(Of course, if your editor saves the new script file to a different file name and renames it over the top, you're safe anyway. But not all editors do: emacs, in particular, reopens the existing file and overwrites its contents.)

@simontatham I would never use this... It requires that you avoid ctrl-c. Why? And if you are testing a script why is it in the background anyway?

If you are so scared of your program crashing and running in a type of zombie state, why use nested functions at all, that almost guarantees bad behaviour. Furthermore, don't normal people use 'set -e'?

Idk maybe I'm just overthinking it or just don't understand..

@ponies I'm completely confused by everything you just said!

What's the problem you've spotted with Ctrl-C?

Who said the script was running in the background? More usually I'm running the script in one terminal window, and editing it in another, or in a GUI editor.

What nested functions are you talking about? I didn't show any.

How does 'set -e' help? That's solving a completely different problem.

@simontatham well my point is that stopping and starting the program again makes your issue irrelevant, right?

@ponies yes, certainly. If you always stop the script running, then edit it, then start it again, you don't need this protection.

But it's _useful_ to be able to edit a program so that the next run will work better, while the previous run is still going. It means you can fix each problem as you see it, instead of having to remember what they all were once the program finishes running, maybe hours later.

In almost any other language – interpreted _or_ compiled – you can do this safely. It's only in shell where editing a running script causes a disaster. Why _wouldn't_ someone want to eliminate that exception, and make shell scripts behave the same as everything else?

Why do shells actually do this differently than other interpreters? Does anything depend on it?

I guess generating commands and piping them into as shell is the usecase?

@asjo @simontatham now that I am invested in this, I have read the claim that zsh does not do this, although I haven't tested. You would assume the fish guys would have fixed something like this too, right? But again, I am not sure

I just tested with zsh - same as bash.

My test: I open a file called hep.sh with this content:

for i in $(seq 1 10); do date sleep 2s done echo "HEP"

and then I run zsh hep.sh and I add a line before the "date" line saying echo "YAY" and save the file, while the script is running:

$ zsh hep.sh Thu 15 May 21:47:22 CEST 2025 Thu 15 May 21:47:24 CEST 2025 Thu 15 May 21:47:26 CEST 2025 Thu 15 May 21:47:28 CEST 2025 Thu 15 May 21:47:30 CEST 2025 Thu 15 May 21:47:32 CEST 2025 Thu 15 May 21:47:34 CEST 2025 Thu 15 May 21:47:36 CEST 2025 Thu 15 May 21:47:38 CEST 2025 Thu 15 May 21:47:40 CEST 2025 HEP hep.sh:7: command not found: ne HEP

I'm not sure if the same syntax works for fish; it does for bash:

$ bash hep.sh Thu 15 May 21:50:09 CEST 2025 Thu 15 May 21:50:11 CEST 2025 Thu 15 May 21:50:13 CEST 2025 Thu 15 May 21:50:15 CEST 2025 Thu 15 May 21:50:17 CEST 2025 Thu 15 May 21:50:19 CEST 2025 Thu 15 May 21:50:21 CEST 2025 Thu 15 May 21:50:23 CEST 2025 Thu 15 May 21:50:25 CEST 2025 Thu 15 May 21:50:27 CEST 2025 hep.sh: line 6: syntax error near unexpected token `done' hep.sh: line 6: `done'

fish behaves differently (and the syntax is a little different), I tested with:

for i in $(seq 1 10); date sleep 2s end echo "YAY"

and then fish yay.sh, adding a line inside the loop and saving while it was running; result:

$ fish yay.sh Thu 15 May 21:54:18 CEST 2025 Thu 15 May 21:54:20 CEST 2025 Thu 15 May 21:54:22 CEST 2025 Thu 15 May 21:54:24 CEST 2025 Thu 15 May 21:54:26 CEST 2025 Thu 15 May 21:54:28 CEST 2025 Thu 15 May 21:54:30 CEST 2025 Thu 15 May 21:54:32 CEST 2025 Thu 15 May 21:54:34 CEST 2025 Thu 15 May 21:54:36 CEST 2025 YAY