...
- The basic structure of a command-processing script
- Defining and evaluating bash variables
- Grouping and evaluating values using double quotes ( " " ) or single quotes ( ' ' )
- Functions in bash, and their local variables
- Passing arguments to scripts and functions
- Subtleties of quoting in bash
...
Code Block | ||
---|---|---|
| ||
my_script.sh helloWorld # invoke the "helloWorld" functionality of my_script.sh my_script.sh goodbyeWorld # invoke the "goodbyeWorld" functionality of my_script.sh |
There are many examples of command-processing scripts in bioinformatics: bwa, samtools, bedtools to name but a very few.
The step_01.sh Script
Here's a basic command-processing script with one sub-command. This script can be found in your home directory in ~/workshop/step_01.sh. Make sure it is executable (chmod +x ~/workshop/step_01.sh
).
Code Block | ||||
---|---|---|---|---|
| ||||
#!/bin/bash # Script version global variable. Edit this whenever changes are made. __ADVANCED_BASH_VERSION__="step_01" # function that says "Hello World!" and displays user-specified text. function helloWorld() { local txt1=$1 local txt2=$2 shift; shift local rest=$@ echo "Hello World!" echo " text 1: '$txt1'" echo " text 2: '$txt2'" echo " rest: '$rest'" } # ======================================================================= # Main script command-line processing # ======================================================================= function usage() { echo "advanced_bash.sh, version $__ADVANCED_BASH_VERSION__" echo "" echo "Usage: advanced_bash.sh <command> [arg1 arg2...]" echo "" echo "Commands:" echo "helloWorld [text to display]" echo "" } CMD=$1 # initially $1 will be the command shift # after "shift", $1 will be the 2nd command-line argument; $2 the 3rd, etc. case "$CMD" in helloWorld) helloWorld "$@" # and $@ will ;;be arguments 2, 3, etc. case "$CMD" in helloWorld) helloWorld "$@" ;; *) usage ;; esac |
The Parts
...
The first line (#!/bin/bash
) is called the shebang – #! characters followed by the full path to the program which should execute the script, if it is invoked without an execution context (and if it has execute file permissions of course ).
Code Block | ||
---|---|---|
| ||
# Call a script directly. # As long as it marked as executable (chmod +x), the shell # peeks at the shebang line and passes the script to that program. ~/workshop/step_01.sh # Call a script specifying the executing program. The shebang line will be ignored. bash ~/workshop/step_01.sh |
...
- when referencing a positional argument variable with more than one digit (e.g. ${10})
- to separate the variable evaluation from text immediately following (e.g. "${prefix}_file.txt")
- since underscore characters ("_") are allowed in variable names, the braces are needed so that the shell does not think the variable name is prefix_file.
Functions
A bash function looks like this, with or without the function keywordExample:
Code Block | ||
---|---|---|
|
...
function my_function() {
# code goes here
}
Code Block | ||
---|---|---|
| ||
my_function() {
# code goes here
} |
Function and script arguments
Just like the script as a whole, arguments to a bash function are positional, and are referenced using:
- positional variables $1 $2 ... $9 ${10} ${11}...
- or $@ to refer to all of the arguments
- and $0 to refer to the script name itself
The shift keyword "pops" the first element off the argument list.
Note that while a function can have many arguments, the function definition never contains anything in its "formal argument list".
...
myvar="some text"
echo $myvar
echo ${myvar}
echo $myvar_more_text # no output because the variable myvar_more_text is not defined
echo ${myvar}_more_text |
When defining or evaluating environment variables there's also a difference between enclosing the value in double quotes ( "$foo" ) or single quotes ( '$foo' ) – see Quoting in the shell.
Example:
Code Block | ||
---|---|---|
| ||
myvar="some text"
echo "$myvar"
echo '$myvar'
|
Functions
A bash function looks like this, with or without the function keyword.
|
|
...
|
Function and script arguments
Just like the script as a whole, arguments to a bash function are positional, and are referenced using:
- positional variables $1 $2 ... $9 ${10} ${11}...
- or $@ to refer to all of the arguments
- and $0 to refer to the script name itself
Note that while a function can have many arguments, the function definition never contains anything in its ( ) "formal argument list".
And in bash, arguments passed to both scripts and functions are not enclosed in parentheses, as is the case in most programming languages.
Example:
Code Block | ||
---|---|---|
| ||
function myfn() { echo "arg 1: $1"; echo "arg 2: $2"; echo "all args: $@"; }
myfn foo bar baz |
In our script, the shift keyword "pops" the first element off the argument list.
Since there is no formal argument list, it is good practice to copy function arguments into local variables with names suggesting their role. (e.g. local txt1=$1). The local keyword specifies that the variable scope is only within the function body – it is not visible to the caller or to called functions.
Code Block | ||
---|---|---|
| ||
# Function that says "Hello World!" and displays user-specified text.
function helloWorld() {
local txt1=$1
local txt2=$2
shift; shift
local rest=$@
echo "Hello World!"
echo " text 1: '$txt1'"
echo " text 2: '$txt2'"
echo " rest: '$rest'"
} |
...
Code Block | ||
---|---|---|
| ||
CMD=$1 # initially $1 will be the command shift # after "shift", $1 will be the 2nd command-line argument; $2 the 3rd, etc. # and $@ will be arguments 2, 3, etc. case "$CMD" in helloWorld) helloWorld "$@" ;; *) usage ;; esac |
The 1st (sub-command name) argument is captured in the CMD variable. Calling shift then removes that 1st argument, so that $@ now contains everything after the sub-command name – these name. So script arguments 2, 3, etc., will be positional arguments 1, 2, etc., to whatever function is called.
...
Code Block | ||
---|---|---|
| ||
function usage() { usage() { echo "advanced_bash.sh, version $__ADVANCED_BASH_VERSION__" echo "" echo "Usage: advanced_bash.sh <command> [arg1 arg2...]" echo "advanced_bash.sh, version $__ADVANCED_BASH_VERSION__"" echo "Commands:" echo "helloWorld [text to display]" echo "Usage: advanced_bash.sh <command> [arg1 arg2...]" echo "" echo "Commands:" echo "helloWorld [text to display]" echo "" } |
As we extend our command processing script, we'll add clauses to the case/esac block and add a short usage description to the usage function.
Note that in bash, arguments to both scripts and functions are not enclosed in parentheses, as is the case in most programming languages.
Calling a function or a script
...
""
} |
As we extend our command processing script, we'll add clauses to the case/esac block and add a short usage description to the usage function.
Calling a function or a script
Functions and scripts are called without parentheses around their arguments. Instead, each argument is separated by whitespace (one or more space characters).
Code Block | ||
---|---|---|
| ||
# Call a custom script passing 3 arguments
my_script.sh arg1 arg2 arg3
# Inside a script, call a function passing 3 arguments
my_function arg1 arg2 arg3 |
Importantly, if an argument to be passed itself contains spaces, the argument must be enclosed in single or double quotes (or, more ugly, the spaces can be backspace-quoted, e.g. "\ ")
Code Block | ||
---|---|---|
| ||
# Call a custom script passing 32 arguments my_script.sh arg1 "arg2 arg3has embedded # Insidespaces" a script,# callCall a function passing 32 arguments my_function arg1 'arg2 arg3 |
...
has embedded spaces' |
Example:
Code Block | ||
---|---|---|
| ||
# Call a custom script passing 2 arguments my_script.sh arg1 "arg2 has embedded spaces" # Call a function passing 2 arguments my_function arg1 'arg2 has embedded spaces'function myfn() { echo "arg 1: $1"; echo "arg 2: $2"; echo "all args: $@"; } myfn foo bar baz wonk myfn foo "bar baz" wonk myfn "foo bar" baz wonk |
Running the step_01.sh script
...
After shift; shift is called, helloWorld arguments are:
- $1 - My → previously stored in local variable txt1
- $2 - name → previously stored in local variable txt2
- $1 - is
- $2 - Anna
- $@ - is Anna → stored in local variable rest
...
Expand | |||||
---|---|---|---|---|---|
| |||||
|
Quoting subtleties
You're probably already familiar with the We;ve already touched on difference kinds of Quoting in the shell. But there is an additional subtleties when handling script arguments.
...
Code Block | ||
---|---|---|
| ||
# Wihout quotes, all argument grouping by the script caller is lost helloWorld $@ # With quotes, argument quoting by the script caller is preserved helloWorld "$@" |
This is because enclosing a variable in double-quotes "" preserves any special formatting internal to the variable's value.
Tip | ||
---|---|---|
| ||
It is a good idea to double-quote bash positional argument variables in order to preserve the caller's quoting. Corollary: If some argument isn't coming through as you're expecting, it's probably a quoting issue! |
...