How to call one shell script from another shell script?
I have two shell scripts, a.sh and b.sh. How can I call b.sh from within the shell script a.sh? What are the different methods to execute one shell script from another, and what are the advantages and disadvantages of each approach?
There are several methods to call one shell script from another in Linux/Unix systems. The most common approaches include using the source or . command, executing the script directly with ./script.sh, using the bash command, or sourcing the script within a function. Each method has different implications for variable scope, execution context, and error handling.
Contents
- Using the source or . Command
- Direct Execution with ./script.sh
- Using the bash Command
- Sourcing Scripts in Functions
- Best Practices and Recommendations
- Error Handling and Debugging
Using the source or . Command
The source command (or its equivalent .) executes the script within the current shell context rather than creating a new subshell. This means that variables and functions defined in the sourced script become available in the calling script.
Syntax:
source b.sh
# or
. b.sh
Advantages:
- Variable Preservation: Variables and functions defined in
b.shremain accessible ina.sh - Performance: No subprocess creation overhead
- Clean Exit Codes: Exit status is properly propagated
- Less Memory Usage: No additional shell process is started
Disadvantages:
- Namespace Pollution: All functions and variables from
b.shpollute the current shell’s namespace - Debugging Complexity: Harder to trace execution flow when multiple scripts are sourced
- Risk of Overwriting: May overwrite existing variables or functions in the calling script
Example:
#!/bin/bash
# a.sh
echo "Starting a.sh"
source b.sh
echo "Variable from b.sh: $MY_VAR"
echo "Function from b.sh: $(my_function)"
echo "Exiting a.sh"
As explained in shell scripting documentation, when you source a script, the environment variables and functions become available in the current shell context, which is particularly useful for maintaining state between script calls.
Direct Execution with ./script.sh
This is the most straightforward method where you execute the second script as a separate process.
Syntax:
./b.sh
# or
/path/to/b.sh
Advantages:
- Isolation: Runs in a clean, isolated environment
- No Namespace Pollution: Variables and functions from
b.shdon’t affecta.sh - Easier Debugging: Separate processes make it easier to trace execution
- Parallel Execution: Can be run in background with
&
Disadvantages:
- Variable Scope: Variables set in
b.share not available ina.sh - Performance Overhead: Creates a new shell process
- Exit Code Handling: Need to explicitly check exit codes
- Memory Usage: Additional process consumes more memory
Example:
#!/bin/bash
# a.sh
echo "Starting a.sh"
./b.sh
echo "Exit code from b.sh: $?"
echo "Exiting a.sh"
According to Techsive’s guide, this method is especially useful when you need to test scripts in a specific shell, or when the execution permission can’t be changed for some reason.
Using the bash Command
This method explicitly calls bash to execute the script, providing more control over the shell environment.
Syntax:
bash b.sh
# or
/bin/bash b.sh
Advantages:
- Shell Control: Can specify different shell versions
- Isolation: Clean execution environment
- Argument Passing: Easy to pass arguments to the script
- Security: Can be used with restricted shells
Disadvantages:
- Still Isolated: Variables not shared between scripts
- Additional Process: Creates another shell process
- Configuration Differences: May behave differently than current shell
- Path Dependency: Requires bash to be available in the specified path
Example:
#!/bin/bash
# a.sh
echo "Starting a.sh"
bash b.sh
echo "Exit code from b.sh: $?"
echo "Exiting a.sh"
The Bash Wikipedia entry explains that any startup file can execute commands from any other file, and scripts written in conformance with POSIX guidelines should be executable by any shell system application.
Sourcing Scripts in Functions
This approach combines the benefits of sourcing with better namespace management by containing the sourced script within a function.
Syntax:
load_b_script() {
source b.sh
}
load_b_script
Advantages:
- Namespace Control: Variables and functions are contained within the function scope
- Selective Loading: Can conditionally load scripts
- Cleaner Interface: Provides a clear API for script interaction
- Error Handling: Better error handling capabilities
Disadvantages:
- Complexity: More complex to implement and maintain
- Variable Access: Need to explicitly export variables to access them outside
- Debugging Challenges: Can be harder to debug when errors occur
Example:
#!/bin/bash
# a.sh
echo "Starting a.sh"
load_b_script() {
source b.sh
# Export needed variables
export MY_VAR
}
load_b_script
echo "Variable from b.sh: $MY_VAR"
echo "Exiting a.sh"
Best Practices and Recommendations
When to Use Each Method:
-
Use
source/.when:- You need to share variables between scripts
- You want to define reusable functions
- Performance is critical
- You’re building a modular script collection
-
Use direct execution (
./script.sh) when:- You need complete isolation
- The script should not affect the calling script’s environment
- You want to run scripts in parallel
- Security is a concern (less risk of variable injection)
-
Use
bash script.shwhen:- You need to specify a particular shell version
- You want to pass arguments easily
- You’re working with restricted shell environments
- You need better control over the execution environment
Performance Considerations:
As noted in AttuneOps’ Shell Automation Essentials, each external call adds overhead, so you should use shell constructs like loops and conditionals to do more within the script itself when possible.
Code Organization Tips:
- Modular Design: Break large scripts into smaller, focused scripts
- Clear APIs: Define clear interfaces between scripts when using sourcing
- Error Handling: Always check exit codes when executing external scripts
- Documentation: Document dependencies and expected behaviors between scripts
Error Handling and Debugging
Exit Code Handling:
#!/bin/bash
# a.sh
./b.sh
if [ $? -ne 0 ]; then
echo "Error executing b.sh"
exit 1
fi
Debug Mode:
#!/bin/bash
# a.sh
set -x # Enable debugging
source b.sh
set +x # Disable debugging
Error Handling Patterns:
#!/bin/bash
# a.sh
# Method 1: Check exit code
./b.sh
if [ $? -ne 0 ]; then
echo "b.sh failed"
exit 1
fi
# Method 2: Use && operator
./b.sh && echo "Success" || echo "Failed"
# Method 3: Custom error function
error_handler() {
echo "Error in $1 with exit code $2"
exit $2
}
./b.sh || error_handler "b.sh" $?
Conclusion
Key Takeaways:
- Source/. Command: Best for sharing variables and functions, but pollutes namespace
- Direct Execution: Provides isolation but doesn’t share variables, has performance overhead
- Bash Command: Offers shell control but still creates isolation
- Function Sourcing: Balances namespace control with shared functionality
Practical Recommendations:
- For shared configuration and utilities, use
source/.with clear naming conventions - For independent operations, use direct execution with proper error handling
- Consider the trade-offs between performance and isolation based on your specific use case
- Always implement proper error handling regardless of the method chosen
- Document script dependencies and expected behavior clearly
Related Questions:
-
Q: How can I pass arguments to the called script?
A: Use./b.sh arg1 arg2orbash b.sh arg1 arg2 -
Q: How can I return values from the called script?
A: Use echo/print in the called script and capture withresult=$(./b.sh) -
Q: How can I run scripts in parallel?
A: Use./b.sh &for background execution or process management tools
Choose the method that best fits your specific requirements for variable sharing, performance, and isolation. Each approach has its place in shell scripting, and understanding these differences will help you write more robust and maintainable scripts.