1. Overview

As developers, it’s important to know the languages and tools we use. Also, becoming acquainted with the behavior of our system is essential for day-to-day activities. This knowledge enables us to effectively predict the outcomes of our operations.

Shell scripts, in particular, can be seemingly unpredictable since most shell languages often work differently than other interpreted languages.

In this tutorial, we’ll look at what happens when we edit a shell script while it’s running. In particular, we’ll learn how to make changes successfully and how to avoid affecting the execution while editing the source code.

The commands presented here are written with the Bash shell in mind, so they might not work with other shells.

2. Editing a Shell Script During Execution

The way we edit a file produces different results during the execution of a Bash script. Changes might be applied immediately or ignored, and errors can arise.

For these examples, we’ll use echo and sleep. To clarify, echo outputs text to the standard output, while sleep delays a program for a set amount of time.

2.1. Editing Script With Text Editors

Let’s see how changing the argument of an echo command affects our output.

First, we check our initial script with the cat command and execute it:

$ cat script.sh 
#!/bin/bash
echo 1
sleep 10
echo 2
sleep 10
echo 3
sleep 10
$ bash script.sh

Meanwhile, in a different terminal window, we use the nano text editor to change the 2 to a 20 while the program is sleeping:

$ nano script.sh

We modify the text and press Ctrl+X, followed by Y to save the file and exit.

Let’s now check what the output of the script is in the first terminal:

1
20
3

As we can see, our modification was successful and we managed to change the value.

Now, let’s try to revert the change with the vim text editor. First, we rerun the script:

$ bash script.sh

While the script is sleeping, we use vim to edit the 20 back to a 2:

$ vim script.sh

To do this, we press I (Insert) to start editing. After we’re done, we press Esc, followed by :, W, and Q. The : is used to enter commands, the W to save the file (write) and the Q to quit the editor.

Let’s analyze the output of the script to see if our change was successful:

1
20
3

As we can see, this time the output didn’t reflect the changes. Although we changed the file, Bash still executed the old version.

2.2. Differences in Text Editors

This happens because of the different ways the editors handle the file saving operation:

  • nano overwrites the file
  • vim actually creates a new file

This can be verified through the inode number, which changes after saving with vim but not nano.

inodes are data structures where file information is stored. For example, permissions, the storage device, and the location of the file data are all part of an inode.

Even though the filename remains the same, the inode changes. As a result, the file is different since it’s saved to a new location on the storage and filesystem.

Therefore, editing the file with vim has the same outcome as deleting the original file and creating a new one with the modified contents. When we delete the file, the script continues to execute as it can still access the inode.

3. Modifying a Script Without Errors During Execution

We’ve seen the reason why different tools have different effects on the output of the script. To make sure our changes affect the output during execution, we should use tools that overwrite the file while preserving the inode instead of creating a new one.

However, the place where we make our edits within the script also matters for our modifications to have the desired effects.

3.1. Editing the Current Line of Execution

Previously, we managed to affect the output with small changes to future commands or values. Nonetheless, the outcome can be different when we make a more complex change – changing the instruction that’s currently executing may present challenges.

For example, let’s look at what happens when we replace our script with a different one. Here’s the new script, script_new.sh:

$ cat script_new.sh 
#!/bin/bash
echo "start"
echo 1
sleep 10
echo 2
sleep 10
echo 3
sleep 10

We can see that our new script has a different first instruction. This changed the location of each instruction by shifting them forward.

Now, let’s execute the first script:

$ bash script.sh

After this, we wait while our program executes the first sleep instruction. Meanwhile, in a new terminal window, we use cp to copy the new script to the location of the one we’re currently executing:

$ cp script_new.sh script.sh 

Like nanocp overwrites files instead of creating new ones at a different location.

Let’s look at the output to see what happened:

1
script.sh: line 4: o: command not found
2
3

As we can see, although the program continued executing, there was an error after the first sleep.

3.2. Understanding the Shell Instruction Pointer

The previous error occurred because Bash tried to execute the command o, which didn’t exist in our system. This happened because the shell saves the current edit location in the file by keeping track of the number of characters already interpreted and executed.

Let’s replace the newlines with spaces and look at both scripts side-by-side to see where the error originated:

#!/bin/bash echo 1 sleep 10 echo 2 sleep 10 echo 3 sleep 10 
#!/bin/bash echo "start" echo 1 sleep 10 echo 2 sleep 10 echo 3 sleep 10

The shell saved the place in the script where echo 2 was supposed to be executed next. However, as we changed the file, this character number is now in the middle of the echo 1 instruction.

Since the interpreter saved it as the place where the next command resided, it tries to execute o 1, which is the remainder of the echo 1 command. As it fails, it then continues to execute the next instructions properly.

Hence, we have to be careful when changing the lines of the script that come before the next instruction to execute. The instruction pointer of the shell doesn’t change accordingly, which results in errors if the length of the commands isn’t the same.

4. Avoiding the Effects of Script Modifications

If we want to safely edit our scripts without affecting the current execution, there are still some techniques we can use.

4.1. Using Functions

In Bash, commands usually run one at a time. However, there are some structures that group various commands.

In this case, the shell reads the whole structure at once. Any changes made during execution to the commands inside these won’t affect the output, since the function has already been defined. There are different such structures:

  • loops
  • if-else branches
  • functions

Nonetheless, these aren’t ideal, as they change the meaning of the code and in some edits, might still raise errors.

4.2. Using source

The source command runs scripts in the current shell, instead of spawning a subshell as when we execute a script.

By using source, variables manage to retain the values assigned in the script, which can be useful for subsequent instructions.

It works by reading the file and executing the list of commands. In practice, source only reads the file once, which means we can safely edit without making changes to the execution.

Let’s see how we can use source:

$ source script.sh

Alternatively, we can also call the . built-in:

$ . script.sh

Nevertheless, this solution isn’t optimal. Since it runs in the current shell, sourcing a script can cause instability, due to the interactions between variables, code, and current environment.

Thus, isolating the execution in a subshell is a better option. To do this, we use source in a script and execute that:

$ cat source.sh 
#!/bin/bash
source script.sh
$ bash source.sh

Although it looks counterintuitive, we take advantage of the isolation of variables and functions, while avoiding the effects of modifications in the output.

5. Conclusion

In this article, we analyzed how running scripts are affected by edits. We looked at how to modify our script successfully, as well as what errors can arise and how to avoid them. For example, editing commands that haven’t been executed yet is usually easier than changing the instructions that are currently running.

Modifying code during execution is a common occurrence, whether we want to add another feature or fix a bug. While it can be complicated to understand how scripts will behave, we examined several ways of changing them, such as using different editors and manual copying.

Finally, we also learned how to avoid modifications while executing scripts. This can be useful when we want to keep editing the file, without affecting the current execution.