In Linux development, shell scripts are often written to perform tasks, usually with each script doing one thing. As tasks increase, the number of scripts grows, and the places where they can be reused also increase. At this point, it is necessary to extract common functions from the scripts and place them in a general script that other scripts can reuse.
This article introduces how to execute external scripts in shell scripts, how to call functions from external scripts, and methods related to script reuse.
Ways to Execute External Scripts
Suppose there is a script a.sh
in the current directory, with the following content:
#!/bin/bash
echo "a.sh..."
There are several ways to execute external scripts within a script:
-
source external_script_name
In the b.sh
script located in the current directory, the content is as follows:
#!/bin/bash
source a.sh
echo "b.sh..."
When executing ./b.sh
, the result is as follows:
[root@ecs-centos-7 ~]# ./b.sh
a.sh...
b.sh...
In the script, the command source a.sh
will first execute the a.sh
script in the current directory, so the result will first output a.sh...
and then output the print of the b.sh
script itself.
-
dot external_script_name
Change the statement in b.sh
that executes the a.sh
script to dot + space + a.sh
. The modified script content is as follows:
Note: There must be a space between the dot and a.sh
, otherwise an error will occur during execution.
#!/bin/bash
. a.sh
echo "b.sh..."
When executing ./b.sh
, the result is as follows:
[root@ecs-centos-7 ~]# ./b.sh
a.sh...
b.sh...
In the above script, . a.sh
will first execute the a.sh
script, resulting in the output of a.sh...
followed by b.sh...
.
-
sh external_script_name
sh external_script_name
and ./external_script_name
are the same, either can be chosen. Below is an example using the former:
Change the b.sh
script from source a.sh
to sh a.sh
. The modified script content is as follows:
#!/bin/bash
sh a.sh
echo "b.sh..."
When executing ./b.sh
, the result is as follows:
[root@ecs-centos-7 ~]# ./b.sh
a.sh...
b.sh...
It can be seen that the output result is the same as the above two methods.
What Are the Differences Between the Three Methods?
There are three ways to call external scripts: source external_script
, dot external_script
, and sh external_script
. What are the differences between them?
Among them, source external_script
and dot external_script
are the same; the current script inherits the global variables and functions of the external script, which is equivalent to importing the functions and global variables of the external script into the current script.
Modify the a.sh
and b.sh
scripts, as follows:
Content of a.sh
script:
#!/bin/bash
VAR_A=10
func_a()
{
echo "a.sh...pid:$$,param:$1"
}
Content of b.sh
script:
#!/bin/bash
source a.sh
func_a $1
echo "vara:$VAR_A"
echo "b.sh...pid:$$"
When executing ./b.sh 5
, the result is:
[root@ecs-centos-7 ~]# ./b.sh 5
a.sh...pid:21485,param:5
vara:10
b.sh...pid:21485
The $$ in both scripts refers to the process ID of the executing script. From the result, it can be seen that both a.sh
and b.sh
are executed within the same process, so executing source a.sh
in b.sh
will import the global variable VAR_A
and function func_a
from a.sh
into b.sh
.
Printing the variable VAR_A
in b.sh
outputs the same value as in a.sh
, and calling the func_a
function also indicates that it is calling the function from a.sh
.
source external_script
and dot external_script
are the same, so changing source a.sh
in b.sh
to . a.sh
and executing ./b.sh 5
will yield the same result.
Since the sh external_script
method executes the current script and external script in two different processes, the current script cannot directly use the functions and global variables from the external script.
Modify the a.sh
and b.sh
scripts, as follows:
Content of a.sh
script:
#!/bin/bash
test_a()
{
echo "a.sh...test_a"
}
echo "a.sh...pid:$$"
Content of b.sh
script:
#!/bin/bash
sh a.sh
echo "b.sh...pid:$$"
test_a
When executing ./b.sh
, the result is:
[root@ecs-centos-7 ~]# ./b.sh
a.sh...pid:21818
b.sh...pid:21817
./b.sh:行7: test_a: 未找到命令
From the result, it can be seen that the process IDs of a.sh
and b.sh
are different; the b.sh
script process cannot find the test_a
function, so calling the test_a
function in b.sh
will prompt command not found
.
Calling Functions from External Scripts
The previous section mentioned that the sh external_script
method cannot directly use functions and global variables from the external script. Here are a few methods to solve this problem:
-
case branch selection
This method is similar to the switch-case statement in programming code, using the case
keyword in shell scripts to implement it.
Content of a.sh
script:
#!/bin/bash
VAR_A=10
test_a()
{
echo "test_a..pid:$$,p1:$1,p2:$2"
}
get_var()
{
echo ${VAR_A}
}
case "$1" in
ta)
test_a $2 $3
;;
var)
get_var
;;
*)
echo "parameter err..."
esac
Content of b.sh
script:
#!/bin/bash
echo "b.sh...pid:$$"
sh a.sh ta 3 5
ret=$(sh a.sh var)
echo "ret:$ret"
When executing ./b.sh
, the result is:
[root@ecs-centos-7 ~]# ./b.sh
b.sh...pid:24813
test_a..pid:24814,p1:3,p2:5
ret:10
The b.sh
script first prints its own process ID.
The statement sh a.sh ta 3 5
calls the a.sh
script, passing three parameters: ta
, 3
, and 5
. When executing a.sh
, the first parameter ta
matches the case and calls the test_a
function, passing the remaining two parameters 3
and 5
to the function.
The statement ret=$(sh a.sh var)
calls the a.sh
script, passing a var
parameter. After matching the case, it calls the get_var
function, which outputs the value of the global variable VAR_A
from the script. The $()
is used to get the return value of the command in parentheses, so here the value of ret
is the value of the global variable VAR_A
from the a.sh
script.
Note: If you want to get the return value of a function, you can print the output value using echo
in the function, and then use $(function_name parameter_list)
to get the value printed in the function, as shown in the b.sh
script with ret=$(sh a.sh var)
. The value of the variable ret
is the value printed by the get_var
function in the a.sh
script.
It should be noted that if there are echo
debugging logs in the function, then the debugging logs will also be returned together.
-
Function Call Template
The method introduced above using the case
keyword to match and call different functions has a drawback: every time a function is added to the a.sh
script, a new branch needs to be added to the case, and it needs to be noted whether the function has parameters and whether the number of parameters is correct.
We can add the following statement at the end of each external calling script to solve the above problem:
if [ $# -ge 1 ]; then
name="$1"
shift 1
$name "$@"
fi
The above statement first checks the number of parameters passed when calling the script; only when the number of parameters is greater than or equal to 1 is it valid. The first parameter passed indicates the function name, and all parameters from the second to the last will be passed to the function.
Here, shift 1
shifts the parameters passed to the script to the left by one position. For example, if there are three parameters $1 $2 $3
, after shifting, $2
moves to the position of $1
, $3
moves to the position of $2
, and the number of parameters becomes 2.
Reason: The first parameter in the passed script parameters indicates the function name, and the parameters from the second one onwards are the function parameters. If no left shift is performed, the first parameter (the function name) will also be passed to the function as a parameter.
Below is the complete script content:
Content of a.sh
script:
#!/bin/bash
VAR_A=10
test_a()
{
echo "test_a..pid:$$,p1:$1,p2:$2"
}
get_var()
{
echo ${VAR_A}
}
if [ $# -ge 1 ]; then
name="$1"
shift 1
$name "$@"
fi
Content of b.sh
script:
#!/bin/bash
echo "b.sh...pid:$$"
sh a.sh test_a 3 5
ret=$(sh a.sh get_var)
When executing ./b.sh
, the result is:
[root@ecs-centos-7 ~]# ./b.sh
b.sh...pid:25086
test_a..pid:25087,p1:3,p2:5
ret:10
It can be seen that the result is the same as the method above using case
.
Now, other scripts can call functions in the reusable script using sh a.sh function_name parameter_list
in this way, and the return value of the function can be obtained using $(sh a.sh function_name parameter_list)
.
-
Advantages and Disadvantages of Both
Compared to the case branch selection method, the advantage of the function call template is that the caller only needs to care about the function name in the reusable script, the parameters passed to the function, and the return value of the function.
The disadvantage is that if multiple scripts call the functions in the reusable script, when the function name in the reusable script changes, all places that called it need to be modified.
The disadvantage of the function call template is precisely the advantage of the case branch selection method: the case branch selection method calls different functions based on the passed string parameter, where the string parameter acts as an alias for the function. As long as this parameter remains unchanged, the function names in the script can change freely.
The comparison of the advantages and disadvantages above is relative; in practical applications, the differences may not be very obvious, and in most cases, both methods can be used.
Conclusion
In the process of writing shell scripts, one often encounters inexplicable problems. Some problems may remain unsolved despite extensive effort. Script reuse can extract common functionalities, forming modular functions, which not only helps reduce errors during script writing but also aids in the maintenance of scripts in the future.
Liang Xu’s WeChat
Add Liang Xu’s WeChat to receive 3 sets of essential materials for programmers
→ Selected technical materials shared
→ High-level exchange community
Recommended Reading:
Hi: Please install this magical plugin for both VSCode and IDEA
The poem-like code written by Lei Jun in 1994, I have run it!
Detailed explanation of the ss command for Linux network status tools
5T technical resources giveaway! Including but not limited to: C/C++, Linux, Python, Java, PHP, artificial intelligence, microcontrollers, Raspberry Pi, etc. Reply “1024” in the public account to get it for free!!
Leave a Comment
Your email address will not be published. Required fields are marked *