I don’t know how long I’ve been using the shell, but in my memory, it seems inseparable from system-level scheduled tasks and various system operation gadgets. For a while, I felt that my understanding of the shell was still relatively shallow, and I wanted to fill the gaps through books and other resources. Nothing else, I felt the need to summarize and think, then internalize a learning ability to break through some bottlenecks.

Example 1

## Define global variable aa at the top, and define local variable aa in the test function, execute and print the current output value of aa.
root@agub20 /tmp $ cat tt.sh 
#!/bin/bash

aa=123
function test() {
  local -r aa=456
  echo $aa
}

test
----

root@agub20 /tmp $ pwd;ll -h|grep tt.sh
/tmp
-rw-r--r--  1 root root   **** tt.sh

At first, when I first encountered the shell, I didn’t really care about the execution of shell scripts, for example:

sh /dir1/dir2/xxx.sh
/dir1/dir2/xxx.sh
./dir1/dir2/xxx.sh
"/dir1/dir2/xxx.sh"
cd /tmp; bash xxx.sh

What are the differences between them? At that time, I might not have noticed such details. I only knew that there were many ways to execute shell scripts, and at the beginning, I mostly used ./xxx.sh. Later on, I almost exclusively used bash.

## shellcheck checks that the syntax of tt.sh is correct.
root@agub20 /tmp $ shellcheck tt.sh   ## apt install shellcheck
root@agub20 /tmp $ 

root@agub20 /tmp $ sh /tmp/tt.sh
/tmp/tt.sh: 4: Syntax error: "(" unexpected

root@agub20 /tmp $ /tmp/tt.sh
-bash: /tmp/tt.sh: Permission denied

root@agub20 /tmp $ "/tmp/tt.sh"
-bash: /tmp/tt.sh: Permission denied

root@agub20 /tmp $ cd /tmp;bash tt.sh 
456

In fact, you will find that the compatibility of shell bash execution is the highest. Sometimes, to improve the compatibility of the tt.sh script, you need to add some permissions to tt.sh and also modify the interpreter.

Example 2

root@agub20 /tmp $ cat tt.sh 
#!/usr/bin/env bash

## function: xxxx
## author: xxx
## last update: xxx

NUM_AA=123

function test() {
  local -r NUM_AA=456
  echo $NUM_AA
}

test
-----
root@agub20 /tmp $ chmod +x tt.sh ; ll -h|grep tt.sh
-rwxr-xr-x  1 root root   **** tt.sh*

Then test it

## shellcheck execution, check tt.sh for syntax errors.
root@agub20 /tmp $ shellcheck tt.sh 
root@agub20 /tmp $ 

root@agub20 /tmp $ sh /tmp/tt.sh
/tmp/tt.sh: 4: Syntax error: "(" unexpected

root@agub20 /tmp $ /tmp/tt.sh
456
 
root@agub20 /tmp $ "/tmp/tt.sh"
456

root@agub20 /tmp $ cd /tmp;bash tt.sh
456

In fact, you will find that bash execution compatibility is always the best. Changes in the bash interpreter also make scripts more compatible across different operating systems.

#!/bin/bash ---> #!/usr/bin/env bash

When a shell script has hundreds of lines of code, you have to face the need to control variables, and you have to use features like local, readonly, etc., to prevent variable pollution. Executing with sh xxx.sh or sh /dir1/dir2/xx.sh always results in errors. So what is the difference between sh and bash execution?

Bash has more features and extensions, making it more powerful and flexible. In contrast, sh is a more basic Shell with fewer features. The difference in script execution compatibility is immediately apparent. Just like how apt is more user-friendly under Ubuntu/Debian, providing simpler commands and better dependency handling, thus replacing apt-get. In community shell script code, it is more common to see "/dir1/dir2/xx.sh" or /dir1/dir2/xx.sh.

By reading the shell script code under the Kubernetes community, I learned many small details. For example, to get the docker command on the current system, do you use which docker or type -p docker? DOCKER_OPTS=${DOCKER_OPTS:-""} What does :- mean? And +=, in what scenarios is it appropriate to use in shell? When concatenating xxx/xxx/xxx and tag variables in docker pull xxx/xxx/xxx:tag, should the command use an array variable or a string variable?

The usage of IFS, etc. With the help of ChatGPT’s code explanation and search engine supplementation, I found many small knowledge points about shell to be particularly magical. I also discovered shortcomings in my previous code. For example, declare, set, wait, return are generally not thought of in common scenarios, or I am not clear about when to use them appropriately. When to use full commands or abbreviations?

For example, function parameters can achieve functionality without being used in the code below. Adding them not only tells you that this is a function but also adds a label to the code for easier maintenance. Previously, when writing functions, I would not write function by default, thinking why write it, since it doesn’t affect execution.

function test() {
  ***
}

Similar to adding labels to functions, there is also the use of :: parameters. When the project code is large, adding labels to distinguish the execution logic and relationships of scripts makes the code more readable and maintainable. I never knew about this part before. Seeing that the original author’s record in the vscode editor was from 4 years ago, I realized there are many things I still don’t know. It seems that at each stage, my understanding of shell gains new insights.

function kube::build::docker_push() {
***
}

Reflection

In the end, for code robustness, you need to know what is appropriate at the moment, not just implementation. Just to clearly understand what you are writing and its impact, and to know what you are doing now. It’s like a sword of Damocles hanging over your mind.

Earlier, in crontab, I ignored the execution path of binaries under the root user. I didn’t care about the execution paths of commands like iptables, kubectl, ping, etc. After several failures, I realized that the execution environment variables differ under different tasks. Commands that succeed when executed manually under root bash may prompt command not found in crontab if not configured properly.

Using the following command,

GET_NUM=`xxx /dir1/dir2/xxx.sh|grep xxx|wc -l`

When capturing results, I lacked some understanding of exception handling. Actually, using declare -i GET_NUM would make the code more robust, rather than just filtering with if, elif, etc. Understanding the subtle differences in actual use of cp, mv, rm, and special operations like input empty :> are all very beneficial.

When you don’t know how to further improve your coding skills or how to distinguish appropriate code usage in different scenarios, you might want to look at the code of active communities that interest you. Calm down and understand why these predecessors wrote it that way. I believe you will gain a lot from it.