Shell programming can be accomplished by directly executing shell commands at the shell prompt or by storing them in the order of execution, in a text file, called a shell script, a command-line interpreter (CLI) . To execute, simply write the shell script file name, once the file has execute permission (chmod +x filename).
There is different types of shells, such as sh, bash, zsh etc.
The Bourne shell (sh) is regarded as the first UNIX shell ever. The Bourne shell has some major drawbacks, for example it doesn't have in-built functionality to handle logical and arthmetic operations.
More popularly known as the Bash shell, the GNU Bourne-Again shell (bash) was designed to be compatible with the Bourne shell. It allows us to automatically recall previously used commands and edit them with help of arrow keys, unlike the Bourne shell.
The Z Shell (zsh) is a sh shell extension with more improvements for customization, including some features of Bash.
We will discusses shell programming in general with focus on Bash shell as the main shell interpreter.
Hello, Work!
It is common to name the shell script with the ".sh" extension. And the first line begins with a "sha-bang" (#!) which proclaim where the shell interpreter is located. It like this:
#!/bin/bash
or
#!/usr/bin/env bash
The latter finds bash in current env, lends you some flexibility on different systems. To add a shell command like following:
Shell variables are created once are assigned, case sensitive and can consist of a combination of letters and the underscore "_". A variable can contain a number, a character or a string of characters.
PRICE_PER_APPLE=5
MyFirstLetters=ABC
single_quote='Hello world!' # Won't interpolate anything.
double_quote="Hello $MyFirstLetters world!" # Will interpolate.
escape_special_character="Hello \$MyFirstLetters world!" # Use backslash.
Variables can be assigned with the value of a command output, called substitution. Substitution can be done by encapsulating the command with ` `(known as back-ticks) or with $(). When the script runs, it will run the command inside the $()parenthesis or ` ` and capture its output.
An array is initialized by assign space-delimited values enclosed in (). Array members need not be consecutive or contiguous. Some members of the array can be left uninitialized.
The array elements can be accessed with their numeric index. The index of the first element is 0.
echo ${my_array[3]} # orange - note that curly brackets are needed
# adding another array element
my_array[4]="carrot" # value assignment without a $ and curly brackets
# Double quote array expansions to avoid re-splitting elements.
echo "${my_array[@]}" # => apple banana Fruit Basket carrot
echo ${#my_array[@]} # => 5
echo ${my_array[${#my_array[@]}-1]} # => carrot
Special variables
Here are some special variables in shell:
$0 - The filename of the current script.
$n - The Nth argument passed to script was invoked or function was called.
$# - The number of argument passed to script or function.
$@ - All arguments passed to script or function.
$* - All arguments passed to script or function.
$? - The exit status of the last command executed.
$$ - The process ID of the current shell. For shell scripts, this is the process ID under which they are executing.
$! - The process number of the last background command.
# => $ ./variables.sh 1 "2 2" 3
echo "Script Name: $0" # => Script Name: ./variables.sh
echo "Second arg: $2" # => Second arg: 2 2
echo "Arg Num: $#" # => Arg Num: 3
echo "Exit status: $?" # => Exit status: 0
echo "The process ID: $$" # => The process ID: 4589
echo "As a separate argument: $@" # => As a separate argument: 1 2 2 3
echo "As a one argument: $*" # => As a one argument: 1 2 2 3
The following shows the defference between $@ and $*:
# => $ ./variables.sh 1 "2 2" 3
echo '--- $*'
for ARG in "$*"; do
echo $ARG
done
# =>
# --- $*
# 1 2 2 3
echo '--- $@'
for ARG in "$@"; do
echo $ARG
done
# =>
# --- $@
# 1
# 2 2
# 3
Operators
Arithmetic
To calculate simple arithmetics on variables should use: $((expression))
A=3
B=$((100 * $A + 5)) # 305
The basic operators include:
a + b addition (a plus b)
a - b substraction (a minus b)
a * b multiplication (a times b)
a / b division (integer) (a divided by b)
a % b modulo (the integer remainder of a divided by b)
a ****** b exponentiation (a to the power of b)
String
Length
STRING="this is a string"
echo "length STRING: ${#STRING}" # => length STRING: 16
Extraction Extract substring of length $LEN from $STRING starting after position $POS:
STRING="this is a string"
POS=0
LEN=4
echo ${STRING:$POS:$LEN} # => this
If $LEN is omitted, extract substring from $POS to end of line:
STRING="this is a string"
echo ${STRING:10} # => this
echo ${STRING:(-6)} # => this
Replacement Replace all/firest/beginning/end or delete occurrences of substring:
REPLACE_STRING="to be or not to be"
echo "Replace first: ${REPLACE_STRING[*]/be/eat}" # => to eat or not to be
echo "Replace all: ${REPLACE_STRING[*]//be/eat}" # => to eat or not to eat
echo "Delete: ${REPLACE_STRING[*]// not}" # => to be or to be
echo "Replace beginning: ${REPLACE_STRING[*]/%to be/to eat}" # => to be or not to eat
echo "Replace end: ${REPLACE_STRING[*]/#to be/to eat}" # => to eat or not to be
File Test
Shell provide you with several useful commands to do some file tests on file system. The file test operators are mostly used in the if clause. The syntax likes below:
# file test operators
if [ -e "function.sh" ]; then # => or if [ ! -e "function.sh" ];
echo "function.sh is exist!" # => function.sh is exist!
fi
Common options are as follows:
-e: True if the file exists.
-d: True if the file exists and is a directory.
-f: True if the file exists and is a regular file.
-r: True if the file exists and is readable.
-w: True if the file exists and is writable.
-x: True if the file exists and is executable.
Expression
Comparisons
Number
comparison Evaluated to true when
$a -lt $b $a < $b
$a -gt $b $a > $b
$a -le $b $a <= $b
$a -ge $b $a >= $b
$a -eq $b $a is equal to $b
$a -ne $b $a is not equal to $b
String
comparison Evaluated to true when
"$a" = "$b" $a is the same as $b
"$a" == "$b" $a is the same as $b
"$a" != "$b" $a is different from $b
-z "$a" $a is empty
Decision Making
The shell supports logical decision making. Baisc use:
if [ "$NAME" = "Zhangsan" ]; then
echo "Hello Zhangsan!"
elif [ "$NAME" = "Lisi" ]; then
echo "Hi Lisi!"
else
echo "Wow"
fi
The expression used by the conditional construct is evaluated to either true or false. The expression can be a single string or variable. A empty string or a string consisting of spaces or an undefined variable name, are evaluated as false.
Logical combination The expression can be a logical combination of comparisons, negation is denoted by !, ogical AND (conjunction) is &&, logical OR (disjunction) is ||. Conditional expressions should be surrounded by double brackets [[ ]]:
VAR_A=(1 2 3)
VAR_B="be"
VAR_C="cat"
if [[ ${VAR_A[0]} -eq 1 && ($VAR_B = "bee" || $VAR_C = "cat") ]] ; then
echo "True"
fi
NAMES=('Joe Ham' Jenny Sara Tony)
for N in "${NAMES[@]}" ; do
echo "My name is $N"
done
for N in $(echo -e 'Joe Ham' Jenny Sara Tony) ; do
echo "My name is $N"
done
while if condition is true, until if condition is false, executes commands:
WHILE_COUNT=3
while [ $WHILE_COUNT -gt 0 ]; do
echo "Value of count is: $WHILE_COUNT"
WHILE_COUNT=$(($WHILE_COUNT - 1))
done
UNTIL_COUNT=1
until [ $UNTIL_COUNT -gt 3 ]; do
echo "Value of count is: $UNTIL_COUNT"
UNTIL_COUNT=$(($UNTIL_COUNT + 1))
done
break and continue, like most language, can be used to control loop execution:
echo
CTRL_COUNT=3
while [ $CTRL_COUNT -gt 0 ]; do
if [ $CTRL_COUNT -eq 1 ]; then
CTRL_COUNT=$(($CTRL_COUNT - 1))
continue
fi
echo "Value of count is: $CTRL_COUNT"
if [ $CTRL_COUNT -eq 3 ]; then
break
fi
done
Function
The function syntax likes below:
function add {
echo "$(($1 + $2))"
}
A function call is equivalent to a command. Parameters may be passed to a function, by specifying them after the function name.
echo "$(add 1 2)" # => 3
By default all variables are global. You can create a local variables using the local:
function Hello {
local NAME="zhangsan"
echo $NAME
}
echo $NAME # => (NULL)
Pipelines
Background As we know, under normal circumstances every Linux program has three streams opened when it starts; one for input; one for output; and one for printing diagnostic or error messages. The three as special file descriptors, which are represented by:
stdin: 0
stdout: 1
stderr: 2
By default, stdout and stderr are printed to your terminal, that's why we can see them at all. > operator can help us redirect them to a other file descriptor. For example:
Redirect to stdstream use >&, such as: echo "hello work!" 1>&2. We can referene below to understand stdstream, to avoid stderr print to terminal, I redirect stderr to /dev/null:
function out() {
echo "Standard output"
}
function err() {
echo "Standard error" 1>&2
}
echo "out stdout: $(out 2>/dev/null)" # => out stdout: Standard output
echo "err stdout: $(err 2>/dev/null)" # => err stdout:
Pipes
Pipelines is a way to chain commands and connect stdout from one command to the stdin of the next. By default pipelines redirects standard output only. A pipeline is represented by the pipe character: |. To show pipes, we use a command read,which will help us read from stdin:
function isNull() {
read in # rean from stdin and assign to $in
if [[ ${#in} -eq 0 ]]; then
echo "True"
else
echo "False"
fi
}
Shell builtin commands are commands that can be executed within the running shell's process. Most builtin commands will be used directly. Some time Shell will use external commands, for example echo. Under macOS echo just support -n option. The following shows how to use builtin echo:
# 添加/删除某文件
zip -m myfile.zip 3.txt
zip -d myfile.zip 2.txt
unzip可以将压缩文件解压缩,-d用于制定解压路径:
unzip -d ./doc myfile.zip
trap
trap可以捕获系统信号,比如:
Ctrl+C可以触发SIGINT
Ctrl+Z可以触发SIGSTOP,SIGKILL/SIGSTOP 不会被捕获
*Ctrl+D仅仅只是输入EOF,不会触发系统信号
#!/usr/bin/env bash
trap "echo Booh!" SIGINT SIGSTOP
echo "it's going to run until you hit Ctrl+Z"
echo "hit Ctrl+C to be blown away!"
while true
do
sleep 60
done
$ ./trap.sh
it's going to run until you hit Ctrl+Z
hit Ctrl+C to be blown away!
^CBooh!
^CBooh!
^CBooh!
^Z
[2] + 55377 suspended ./trap.sh