Shell Programming
System Programming
" One vision, one purpose. "
Copyright © Tony's Studio 2020 - 2022
Chapter Four - Shell
4.1 Get Ready for Shell
4.1.1 Redirect
1
2
3 stdin: <
stdout: > or 1>
stderr: 2>
4.1.2 Pipe
Just wire the output of a command to the input of another command.
Here is a comprehensive example. Output of the first
cat
, which is the content ofa.txt
is wired to the input of the secondcat
, whosestdout
is redirected to fileb.txt
, andstderr
is redirected tostdout
. However, there is no such thing like2>>&1
.
1 cat a.txt | cat >>b.txt 2>&1
4.1.3 Environment Variable
4.1.3.1 Add Environment Variable
Well, as you’ve known before, it is easy to add environment variables to current bash process.
1 export environment_variable=xxxBut if you want this variable added to the bash permanently, you have to put that line to
~/.bashrc
file, which is loaded every time for initialization.
4.1.3.2 Get Environment Variable
For C, there are several ways to get environment variable.
1
2
3
4
5
6
7 // Method 1, use global environ.
extern char** environ;
// Method 2, use parameter of main.
int main(int argc, char* argv[], char* envp[]) {...}
// Method 3, use function.
char* value = getenvp("var_name");
4.1.3.3 Execute Permission
See [chmod](#1.4.8 chmod).
4.2 Shell Programming
4.2.1 Special Symbols
#!/bin/bash
: Must be at the first line to indicate interpreter.
#
: In-line comment. Use\#
if you really want a#
.
;
: Separate multiple commands. e.g.echo Hello; echo there!
,
: Separate arithmetic expression, just like C. Here is an example.
1
2
3
4
5 $ a=$((b = 9, 15 / 3)) # check out 4.2.4
$ echo $a;
$ 5
$ echo $b
$ 9
\
: Escape character, similar to what is in C.
1
2 $ echo \$num
$ $num
4.2.2 Run Bash Script
There are several ways to run a bash script. If we directly run it by ./filename, it must have execute permission. Use
chmod u+x script.sh
. And#!/bin/bash
is preferred, although default bash will be selected if this is missing. The other two don’t need these.
1
2
3 $ ./script.sh
$ bash script.sh
$ source script.shOne more thing, why
./
is needed? Because not like what is in Windows, in Linux, current directory is not added to the environment path for… for security reason. Not any ordinary environment variable, but PATH. Once added, we can simply run a bash script just by its name.
1 export PATH=$PATH:. # ':' connects variables
4.2.3 Exit Status
We can use
exit ret
to exit a bash script with a return valueret
. 0 means success, anything else is considered not good. Ifexit
is missing, then the return value depends on the last statement executed in the script.For the return value, we are recommended to use 0 ~ 125, 126 means not executable, 127 means command not found, and 128+ means signal received. The last return value is stored in
$?
.Like C, expression can be put together by some logic operator.
&&
and||
remains the same meaning and function. But here in bash, 0 means true, others are regarded as false. For&&
, if previous statement is false, bash will abandon latter statements and return false.
4.2.3 Variables
“I always hate variables in script language.”
4.2.3.1 Declaration
Variable name is the same as C, but could not contain ‘$’. The value… I hate this. String value is just plain text if contains no space. Quote it with ‘’ or “” like
var="Hello there!"
. These two are a little different, though.
1
2 $ var=value # no space, no $
$ readonly const=constant # read-only variableIf you do not want a variable any more, you can use
unset
to… unset it. :P
1 unset var
4.2.3.2 Quotes
In Bash, there are three types of quotes. A little tricky, huh?
1
2
3
4
5
6
7
8
9
10
11 # Three types of quote have different behaviors.
$ plain='this is $plain "text" $?'
$ rich="this is not '$plain' text"
$ super=`echo this is not \'$rich\' text`
# Here are the results.
$ echo $plain
this is $plain "text" $?
$ echo $rich
this is not 'this is $plain "text" $?' text
$ echo $super
this is not 'this is not 'this is $plain "text" $?' text' text
4.2.3.3 Internal Variables
Interval Variable Meaning $?
last return value $#
number of parameters, just like argc
$*
all parameters passed to the script, just like argv
as a string$@
all parameters, no IFS, more like argv
, seperated strings$$
pid of current bash process, commonly used as temp file name $!
pid of the last process in background $0
current process name, just like argv[0]
$1
to$9
parameters, just like argv[i]
For
$#
and$@
, things can be really tricky. Here is a good example.This example comes from here: What’s the difference between $@ and $*.
1
2
3
4
5
echo "With *:"
for arg in "$*"; do echo "<$arg>"; done
echo "With @:"
for arg in "$@"; do echo "<$arg>"; doneIts output can be as follows.
1
2
3
4
5
6
7 $ ifs A B "C D"
With *:
<A B C D>
With @:
<A>
<B>
<C D>For
$1
to$9
, we can useshift
to shit them. After shift,$1
will become$2
, and so on. But this won’t affect$0
.
4.2.3.4 Variable Replacement
There are some advanced, but tricky variable assignment.
var=${param:-word}
: Ifparam
is set, thenvar
is set toparam
. Orvar
is set toword
, like a default value.
var=${param:=word}
: Ifparam
is set, thenvar
is set to param. Or bothvar
andparam
is set toword
.
var=${param:?word}
: Ifparam
is set, thenvar
is set to param. Otherwise, script will display error info and exit with return value 1. The error info is as follows.
1
2 -bash: param: word # word is not null
-bash: param: parameter null or not set # word is omitted var=${param:?}
var=${param:+word}
: Ifparam
is set, thenvar
is set toword
. Orvar
is set to null.Err… you know, we can simply use
${param:-word}
alone… It’s just a value, and this is it.
4.2.4 Expression
“I hate expressions in these script languages.”
4.2.4.1 Variables in Expression
Just add dollar sign before a variable to use it. For command variables, both $() and `` are OK.
1
2
3 $variable
$(command)
`command`For arithmetic variables, use
$((...))
to wrap them, or useexpr
command. Or more directly, uselet
command.
1
2
3
4
5 $ a=$((3 + 4 + $b))
$ c=$(expr 5 + $d + 7)
$ let a=a++
$ let a=a+1+5
$ let "a = a * (3 + 4)"For a more powerful way to use variables, you can consider
${}
. In this way, I guess, number is regarded as string. However, in x64 architecture, numbers seem to be a 64 bits signed value in calculation.
format return value $var
or${var}
value of the variable ${#var}
length of the variable ${var:start}
substring of the variable from start to end ${var:start:length}
substring of the variable from start in given length, or till the end
4.2.4.2 Condition Expression
In Bash, we can use
test condition
to check condition, or just[condition]
. There are four types of condition check, string, number, logic and file.For string comparation, there are five operations. Be aware of the space around ‘[‘, ‘]’ and ‘=’.
test meaning [ str1 = str2 ] or [ str1 == str2 ] return true if str1 and str2 are the same [ str1 != str2] return true if str1 and str2 are different [ str ] return true if str is not null [ -n str ] return true if length of str is greater than zero [ -z str ] return true if length of str is zero For arithmetic comparation, we can also use condition expression.
test meaning [ a -eq b ] return true if a == b [ a -ne b ] return true if a != b [ a -ge b] return true if a >= b [ a -le b ] return true if a <= b [ a -gt b ] return true if a > b [ a -lt b ] return true if a < b Or, we can just use ((expression)) to replace [ condition ], which is easier to understand.
For logic condition, it is quite like arithmetic.
test meaning !expr not expr1 -a expr2 and expr1 -o expr2 or For file condition, things are almost the same.
test meaning [ -d file ] return true if file is a directory [ -f file ] return true if file is a file [ -r file ] return true if file is readable [ -w file ] return true if file is writable [ -x file ] return true if file is executable [ -s file ] return true if file size greater than zero
4.2.4.3 Branch and Loop
It’s a little wired to have
If-then-elif-then-else
.
1
2
3
4
5
6
7 if [ condition ]; then
# statements
elif [ condition ]; then
# statements
else
# statements
fiIt’s even more wired with switch-case. :(
1
2
3
4
5
6
7
8
9
10
11 case expression in
pattern1)
statements
;;
pattern2)
statements
;;
*) # default
statements
;;
esacThere are three kinds of loop in Bash,
while
,until
andfor
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 # loop while condition is true
while [ condition ]; do
# statements
done
# loop until condition is true
until [ condition ]; do
# statements
done
# for is a little tricky
for arg in [list]; do
# statements
done
# for can also be like this in a simpler format
for ((i = 0; i < 5; i++)); do
# statements
doneHmm… An extra example, I think, redirect can be appended to loop.
1
2
3
4
5
6
7
8
9
10 # print the 10th line in a file
file="test.txt"
i=0
while read line; do
i=$(($i+1))
if (($i==10)); then
echo $line
break
fi
done < $fileFor
for
loop, list is a string or array, for example, we can write this.
1
2
3 for i in 1 2 3; do
echo $i
doneAnd it will print things out.
1
2
3 1
2
3Actually you can refer to previous example with [$@ and $*](#4.2.3.3 Internal Variables).
4.2.5 Array
Ahh… tired… leave it alone. :(
4.2.x Example
“Talk is cheap, show me the code.”
Here is a comprehensive example. The script has one parameter, and its meaning is as follows.
mine
: find all files that belongs to the current user under the current directory and print their filenames. (not recursively)largest
:print the largest file under the current directory. (not recursively)expand
: move all files recursively to the current directory, and then delete all subfolders (they should be empty now).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
dir=`pwd`
# A recursive function example.
function move {
if [ "$(ls -A ${1})" ]; then # dir must not be empty
for file in ${1}/*; do # empty dir will cause file match plain text
filename=`basename $file`
if [ -d $filename ]; then
new="${1}/$filename"
move $new
else
if [ ${1} != $dir ]; then
old="${1}/$filename"
new="$dir/$filename"
mv $old $new
# echo "Move $old to $new"
fi
fi
done
fi
}
# empty string check
if [ -z $1 ]; then
echo "Usage: ./manage.sh OPERATION"
exit 1
fi
# string compa
if [ $1 == "mine" ]; then
flag=0
for file in ${dir}/*; do
filename=`basename $file`
if [ $filename == `basename $0` ]; then # we don't want itself
continue
fi
if [ -d $filename ]; then # we don't want a directory
continue
fi
if [ -O $filename ]; then # if belongs to the current user
echo -n "$filename "
flag=1
fi
done
if (( $flag == 1 )); then
echo ""
fi
elif [ $1 == "largest" ]; then
largestSize=-1
largestFile=""
if [ "$(ls -A ${dir})" ]; then
for file in ${dir}/*; do
filename=`basename $file`
if [ $filename == `basename $0` ]; then
continue
fi
if [ -d $filename ]; then
continue
fi
size=$(stat -c %s "$filename")
if (( $size > $largestSize )); then
largestSize=$size
largestFile=$filename
fi
done
if [ $largestFile != "" ]; then
echo "Largest file: $largestFile"
fi
fi # if [ "$(ls -A ${dir})" ]; then
elif [ $1 == "expand" ]; then
move $dir
if [ "$(ls -A ${dir})" ]; then
for file in ${dir}/*; do
filename=`basename $file`
if [ -d $filename ]; then
rm -r $filename
fi
done
fi
else
echo "Invalid argument! [mine, largest, expand]"
fi
" Do or do not. There is no try. "
Copyright © Tony's Studio 2020 - 2022