Basic-Shell-Script
# 本章内容
- Getting Started
- Using variables
- Inline Redirecting Input
- Performing math
- Exiting the script
# Getting Started
- 所有脚本 (shell, python...) 都要使用 shabang (
#!) 置于行首
#!/usr/bin/bash
#!/usr/bin/python3
提示
使用 which -a python3 命令可以列出环境变量中已经包含的所有 pyton3 的路径
注意
使用 .\ script.sh 时 shell 脚本默认在 subshell 中执行
如果需要将脚本运行在当前 shell 中,可以使用 . script.sh (在 bash 中有效)
举例:
$ cat script.sh
echo $var
$ var="hello world"
$ . script.sh # 说明`. script.sh`使得脚本在当前shell中执行
hello world
$ ./script.sh # 说明`./script.sh`使得脚本不在当前shell中执行
$ pwd
/tmp
$ /tmp/script.sh # 不在当前shell中执行
$
2
3
4
5
6
7
8
9
10
11
12
13
使用 echo 添加文本信息,以帮助脚本用户了解脚本中发生的情况。
- 如果需要使用 echo 展示
"和'需要使用 back slash 进行转译 (\",')。 - 添加 '-n' 命令使得 echo 不会在结尾输出换行符。举例如下:
$ cat script.sh #!/usr/bin/bash echo -n "The time and date are: " date $ ./script.sh The time and date are: Mon 12 Jun 2023 03:30:04 AM EDT1
2
3
4
5
6- 如果需要使用 echo 展示
# Using variables
设置局部变量
为了便于区别,建议局部变量都使用小写字母,全局变量都是用大写字母。这样可以防止局部变量的命名和系统默认的全局变量命名发生冲突。
在变量名中、等号的左右值中都不要使用 space,否则导致变量名被识别为一个变量。
# 正确使用方法 my_variales="hello world" # my_variables会被识别为一个命令,该命令有两个参数 my_variales = "hello world"1
2
3
4
5举例:
$ my_variales = "hello world" my_variales: command not found $1
2
3- 局部变量只能在当前 shell 进程中使用,其他任何 shell 进程 (该 shell 进程的子进程或者父进程) 都无法使用在当前 shell 进程中设置的局部变量。如果需要在其他 shell 进程中使用当前 shell 进程中创建的变量,可以将变量创建为全局变量。
$ var=hello $ echo $var hello $ bash $ echo $var $1
2
3
4
5
6
7- 若赋给变量的值中间有 space,需要使用
"将值括起来或者使用\对 space 进行转译。 - 若要将 shell
',"等,需要使用\对进行转译。
# 正确使用方法 my_variales="hello \'ning\'" # or my_variales=hello\ \'ning\'1
2设置全局变量
全局变量可以被当前进程的 shell 和子进程的 shell 使用。
使用命令
export将变量设置为全局变量。
export my_variables="I am Global now"1提示
如果需要在其他进程 shell (除当前进程的 shell 和子进程的 shell 外) 中也使用该全局变量,可以把它加入.zshrc or .bashrc 中。
- 更改子进程 shell 中全局变量的值不会改变父进程 shell 中的全局变量值。
使用命令
unset删除环境变量
my_variable="I am going to be removed"
unset my_variables
2
在子进程中 unset 删除的全局变量不会删除其在父进程的值。
$ export my_variable="I am Global now"
$ echo $my_variable
I am Global now
$
$ bash
$ echo $my_variable
I am Global now
$ unset my_variable
$ echo $my_variable
$ exit
exit
$ echo $my_variable
I am Global now
$
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 设置全局变量
PATH
# 可以不使用"",但是有时会产生错误,为了养成好习惯都使用""(因为有时候路径中有空格,不使用""就会造成错误)
PATH="$PATH:/home/christine/Scripts"
# or
PATH="/home/christine/Scripts:$PATH"
2
3
4
5
- 设置变量数组
$ mytest=(zero one two three four)
$ echo $mytest
zero
$ echo ${mytest[2]}
two
$ echo ${mytest[*]}
zero one two three four
$ unset mytest
$ echo ${mytest[*]}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
shell script 中设置的变量,当脚本执行结束后,定义在 shell 脚本中的变量也会被删除。
$ cat test3
#!/bin/bash
days=10
guest="Katie"
echo "$guest checked in $days days ago"
days=5
guest="Jessica"
echo "$guest checked in $days days ago"
$
$ ./script.sh
Katie checked in 10 days ago
Jessica checked in 5 days ago
2
3
4
5
6
7
8
9
10
11
12
注意
在脚本中使用变量时需要使用 $ ,给变量赋值时不需要 $ 。
举例:
$ cat test4
#!/bin/bash
# assigning a variable value to another variable
value1=10
value2=$value1
echo "value1 is $value1"
echo "value2 is $value2"
2
3
4
5
6
7
output:
value1 is 10
value2 is 10
2
# command subsitution
(command substitution: 在脚本运行线程的子进程中运行命令,并返回命令的输出)
将一个命令的输出赋值给某个变量在 shell script 中是非常常见的操作,有两种方法可以实现:
- backtick```(重音符)
- $()
# backtick
$ testing=`date`
# $()
$ testing=$(date)
2
3
4
有了 command substitution 我们就可以做很多 fancy 的事情,比如:
#!/bin/bash
# copy the /usr/bin directory listing to a log file
today=$(date +%y%m%d) # 或者使用 today=`date +%y%m%d`
echo "today is $today"
ls -al /usr/bin > log.$today
2
3
4
5
下面所示的代码中,command substitution 和 ./script 都会创建 subshell,然后使用 echo $var 查看变量 var 的值,但是为什么他们一个会保留父进程的局部变量,一个不会呢?
$ var=5
$ (echo "$var")
5
$ cat script.sh
#!/bin/bash
echo $var
$ ./script.sh
$
2
3
4
5
6
7
8
9
10
这是因为 command substitution 会 fork 出一个子进程,然后运行程序;而 ./script 会 fork 一个子进程,然后调用 exec 执行一个外部命令,外部命令的执行会覆盖子进程的进程空间。
参考资料:
Why is a variable visible in a subshell? (opens new window)
Differences between fork and exec (opens new window)
# Redirecting input
输入重定向还有一种方式叫做 inline input redirection .
这种重定向方式使得我们可以从命令行中输入而不是从文件中。需要在开头使用的开头和结尾使用一个 marker 字符串 (marker 可以是任意的字符或者字符串,而且结尾的 marker 字符必须在一行的开头)。
# 本例中marker为i_am_a_marker,并且在shell中而不是shell脚本中运行本例
$ wc << i_am_a_marker
heredoc> It have 4 words
heredoc> i_am_a_marker
1 4 16
2
3
4
5
将 cat 命令的标准输入重定向为从命令行输入
$ cat >> testfile << EOF
heredoc> hello
heredoc> good morning
heredoc> EOF
$ cat testfile
hello
good morning
2
3
4
5
6
7
8
# Performing Math
在 shell script 中有两种方式可以对数字进行计算:
# expr
Unix Bourne shell 提供命令 expr 专门用来处理数学运算。
$ expr 1 + 5
6
2
expr 可以使用的数学操作符有:。
| ARG1 | ARG2 | Return ARG1 if neither argument is null or 0; otherwise, return ARG2 . |
|---|---|
| ARG1 & ARG2 | Return ARG1 if neither argument is null or 0; otherwise, return 0. |
| ARG1 < ARG2 | Return 1 if ARG1 is less than ARG2 ; otherwise, return 0. |
| ARG1 = ARG2 | Return 1 if ARG1 is equal to ARG2 ; otherwise, return 0. |
| ARG1 <= ARG2 | Return 1 if ARG1 is less than or equal to ARG2 ; otherwise, return 0. |
| ARG1 != ARG2 | Return 1 if ARG1 is not equal to ARG2 ; otherwise, return 0. |
| ARG1 >= ARG2 | Return 1 if ARG1 is greater than or equal to ARG2 ; otherwise, return 0. |
| ARG1 > ARG2 | Return 1 if ARG1 is greater than ARG2 ; otherwise, return 0. |
| ARG1 + ARG2 | Return the arithmetic sum of ARG1 and ARG2 . |
| ARG1 - ARG2 | Return the arithmetic difference of ARG1 and ARG2 . |
| ARG1 * ARG2 | Return the arithmetic product of ARG1 and ARG2 . |
| ARG1 / ARG2 | Return the arithmetic quotient of ARG1 divided by ARG2 . |
| ARG1 % ARG2 | Return the arithmetic remainder of ARG1 divided by ARG2 . |
| STRING : REGEXP | Return the pattern match if REGEXP matches a pattern in STRING . |
| match STRING REGEXP | Return the pattern match if REGEXP matches a pattern in STRING . |
| substr STRING POS LENGTH | Return the substring LENGTH characters in length, starting at position POS (starting at 1). |
| index STRING CHARS | Return position in STRING where CHARS is found; otherwise, return 0. |
| length STRING + TOKEN (EXPRESSION) | Return the numeric length of the string STRING . |
| + | Interpret TOKEN as a string, even if it's a keyword. |
| (EXPRESSION) | Return the value of EXPRESSION . |
但是如果运算符在 bash 中有其他含义 (比如 * 在 bash 中是一个通配符),那么就可能产生错误,需要使用 backslash 来进行 Escape:
$ expr 1 + 3
4
$ expr 1 * 3
expr: syntax error
$ expr 1 \* 3
3
2
3
4
5
6
# dollar sign + square brackets
因此,bash 的设计者提供了另一种简单的方式,使得执行时如果使用到了通配符等,不需要进行 Escape: 使用 dollar sign + square brackets 来表示数学运算。
$ var1=$[1 + 5]
$ echo $var1
6
$ var2=$[$var1 * 2]
$ echo $var2
12
2
3
4
5
6
# Bash shell 数学运算的局限性
Bash shell 中只能进行整型运算,无法进行浮点数的运算。
$ cat test8
#!/bin/bash
var1=100
var2=45
var3=$[$var1 / $var2]
echo The final result is $var3
$ chmod u+x test8
$ ./test8
The final result is 2
2
3
4
5
6
7
8
9
10
提示
zsh 中提供了浮点数运算。
- Bash 中无法使用浮点运算的解决方法
bc (bash calculator) 是一种允许在 bash 中进行浮点运算的编程语言,类似于在 python 解释器进行数学运算,bc 中可以出现:
- Numbers (both integer and floating point)
- Variables (both simple variables and arrays)
- Comments (lines starting with a pound sign or the C language /* */ pair)
- Expressions Programming statements (such asif-then statements)
- Functions
使用方法:
$ bc
>>> 12 * 5.4
64.8
>>> scale
0
>>> scale = 4
>>> 12 / 13
.9230
>>> var1 = 10
>>> var2 = 4
>>> var3 = var1 / var2
>>> print var3
2.5000
>>> quit
2
3
4
5
6
7
8
9
10
11
12
13
14
提示
使用内置变量 scale 来设置需要保留的浮点位数。
# 在 scripts 中使用 bc
使用 command substitution 将 bc 运算后的结果保存到变量中,使用格式为:
variable=$(echo "options; expression" | bc)
其中 option 用来设置 bc 中的变量和调整 scale (精确位数);expression 用来定义数学表达式。
举例:
$ cat test9
#!/bin/bash
var1=$(echo " scale=4; 3.44 / 5" | bc)
echo The answer is $var1
2
3
4
可以使用 shell script 中的变量在 bc 中进行运算操作:
$ cat test10
#!/bin/bash
var1=100
var2=45
var3=$(echo "scale=4; $var1 / $var2" | bc)
echo The answer for this is $var3
2
3
4
5
6
# inline input redirection
上面这种方法的缺点是:如果计算涉及的计算较复杂,而且不能在一个数学表达式中直接计算出来时,使用上面这种方式就不太合适,这个时候,我们就可以使用 inline input redirection :
variable=$(bc << EOF
options
statements
expressions
EOF
)
2
3
4
5
6
option: 用来设置精度
statements: 用来设置变量
expressions: 用来设置算数表达式
上面的例子中 EOF 作为 marker
inline input redirection 前面我们已经介绍过了,所以这里只举一个例子进行说明:
$ cat test12
#!/bin/bash
var1=10.46
var2=43.67
var3=33.2
var4=71
var5=$(bc << EOF
scale = 4
a1 = ( $var1 * $var2)
b1 = ($var3 * $var4)
a1 + b1
EOF
)
echo The final answer for this mess is $var5
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
提示
在带有 bc 的脚本中,bc 中创建的变量只能在 bc 中使用,不能在 scirpts 中使用。 但是 bc 的运算中的运算结果可以赋值给 bc 外的变量。
# Exiting the script
shell 中每条命令都执行完成后都有 exit status. exit status 是 0 到 255 中值。
Linux 提供 $? 来记录上一条执行命令的 exit status.
一些参考的退出状态码:
| Code | Description |
|---|---|
| 0 | Successful completion of the command |
| 1 | General unknown error 2 Misuse of shell command |
| 126 | The command can't execute |
| 127 | Command not found |
| 128 | Invalid exit argument |
| 128+x | Fatal error with Linux signalx |
| 130 | Command terminated with Ctrl+C |
| 255 | Exit status out of range |
退出状态码是非常有用的,它可以用来检查一条命令的执行情况,下个 chapter 我们将介绍 if-then 语句来检查 shell script 中命令的状态码。
通常来说 shell script 的退出状态码默认是 script 中最后一条命令的退出状态码。也可以使用 exit 指定 scipt 的退出状态码:
$ cat test13
#!/bin/bash
# testing the exit status
var1=10
var2=30
var3=$[ $var1 + var2 ]
echo The answer is $var3
exit 5
2
3
4
5
6
7
8