Lesson1 - shell 脚本简介
Shell script 是解释型语言,而不是编译型语言 shell 分好多种,如果想看你的操作系统中支持哪几种,可以执行如下命令:
cat /etc/shells
输出如下(每个人的不一样):
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.
/bin/bash
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh
新建一个 hello.sh 文件
touch hello.sh
.sh
后缀名不是必要的,它不影响 shell 脚本的运行 执行 ls -al
命令可以看到更多的有关 hello.sh 的信息:
total 0drwxr-xr-x 3 liusilong staff 96 Jan 30 15:03 .drwxr-xr-x@ 37 liusilong staff 1184 Jan 30 15:03 ..-rw-r--r-- 1 liusilong staff 0 Jan 30 15:03 hello.sh
-rw-r--r--
表示对我自己是 rw(可读可写);对 group 是 r(只读);对 user 也是 r(只读) shell script 的第一行是指定 bash 的位置,可以通过 which bash
命令还获取操作系统中 bash 的位置。所以shell 脚本中第一行一般为:
#! /bin/bash
接着我们来输出一个 Hello world
#! /bin/bash
echo "Hello world"
接着我们使用 ./hello.sh
命令来执行这个 shell 脚本。会出现如下:
zsh: permission denied: ./hello.sh
因为 hello.sh
是仅仅只有读写权限的:-rw-r--r--
,并没有可执行权限,我们使用命令 chmod +x hello.sh
来让 hello.sh 拥有可执行权限,执行之后,hello.sh 的权限信息就会为:-rwxr-xr-x
Lesson2 - 使用变量及注释注释
注释是以 #
开头
#! /bin/bash# 这是一行注释echo "hello world" # 这里也是注释
变量分为两种,一种是系统变量,一种是用户定义的变量 一般的,系统变量都是大写,用户自定义变量都是小驼峰
系统变量:
echo $BASHecho $BASH_VERSIONecho $HOMEecho $PWD
用户自定义变量:
name=Mark
val=10
echo The name is $name
echo value is $val
Lesson3 - 读取用户输入
我们可以使用 read
命令来接受用户的输入,如下:
read name
意思就是读取用户的输入,然后将用户输入保存在变量 name
中 完整例子:
#! /bin/bash
# Read User Input
echo "Enter name :"
read name
echo "Entered name : $name"
接收多个用户输入如下:
#! /bin/bash
# Read User Input
echo "Enter name :"
read name1 name2 name3
echo "Entered name : $name1, $name2, $name3"
执行如下(假设输入为: a b c):
Enter name :
jack rose tom
Entered name : jack, rose, tom
上面的 read 命令是换行输入,我们也可以单行输入
read -p "username: " user_name
echo "username is $user_name"
输出:
username: liusilong
username is liusilong
我们可以使用 -sp 来输入密码,就是让输入的密码不显示
read -p "username: " user_name
read -sp "password: " pass_word # 密码输入时不显示
echo # 换行
echo "username is $user_name"
echo "password is $pass_word"
输出:
username: liusilong
password:
username is liusilong
password is 12345
将用户输入的多个值保存到一个数组中
echo "Enter names:"
# -a 表示让 shell 读取一个数组(array),names 为数组变量
read -a names
echo "names : ${names[0]}, ${names[1]}"
输出:
Enter names:
jack rose
names : jack, rose
如果我们在 read
后面不写接收用户输入的变量
echo "Enter name : "
# read 后面并没有有用于接受用户输入的变量
read
# 使用内置变量 REPLAY 来获取用户输入
echo "name is $REPLY"
输出:
Enter name :
jack
name is jack
Lesson4 - shell 脚本传参
shell 脚本中定义参数是 $1, $2, $3
等等,不能写 $0
,因为 $0
模式是该 shell 脚本的名称。例子如下:
#! /bin/bash
echo $1 $2 $3
这样我们在执行上述 shell 脚本的时候,后面就可以带上参数:
./hello.sh jack rose tom
输出如下:
jack rose tom
还有一种就是将接收到的参数保存到数组里面:
echo "Enter name: " $1 $2 $3
# 定义一个变量 args,为一个数组
args=("$@")
echo "输入的参数为: $@"
echo "参数的个数为: $#"
执行 ./hello.sh jack rose tom
输出:
Enter name: jack rose tom
输入的参数为: jack rose tom
参数的个数为: 3
Lesson5 - if 语句(if..then, if..then..else, if..elif..else)
shell 中 if 的基本句法如下:
if [ condition ]
then
statement
fi
还有一种写法是将 condition 放在双小括号里面:
if ((condition))
then
statement
fi
说明:如果 condition == true 则执行 then 下面的 statement 语句块,statement 语句块执行完成之后,需要一个 fi 来结尾表示这个 if 语句的结尾。 shell 中的整型比较:
-eq 相等 if [ "$a" -eq "$b" ] (a == b)
-ne 不等 if [ "$a" -ne "$b" ] ( a != b)
-gt 大于 if [ "$a" -gt "$b" ]
-lt 小于 if [ "$a" -lt "$b" ]
-ge 大于等于 if [ "$a" -ge "$b" ]
-le 小于等于 if [ "$a" -le "$b" ]
< 小于 if (($counter < 20))
...
注意:如果我们使用 -eq 这样的比较两个整数的大小,则条件可以直接放到方括号里面;如果我们使用普通的运算符来比较,条件就需要放到双小括号里面。
切记不能写成 if (( $counter -gt 9 ))
案例:
count=10
if [ $count -ge 9 ]
then
echo "condition is true"
fi
if (($count > 9))
then
echo "right"
fi
字符串比较:
= - is equal to - if [ "$a" = "$b" ]
== - is equal to - if [ "$a" == "$b" ]
!= - is not equal to - if [ "$a" != "$b" ]
< - is less than, in ASCII alphabetical order - if [[ "$a" < "$b" ]]
> - is greater than, in ASCII alphabetical order - if [[ "$a" > "$b"]]
-z - string is null, that is, has zero length
案例:
word="abc"
if [ $word == "abc" ]
then
echo "condition is true"
fi
letter="a"
if [[ $letter < "b" ]]
then
echo "a less than b"
fi
if..then..else 案例
letter="a"
if [[ $letter == "b" ]]
then
echo "condition is true"
else
echo "condition is false"
fi
if..elif..else 案例
grade=90
if (($grade < 60))
then
echo "not good"
elif (($grade > 60 && $grade < 80))
then
echo "good"
else
echo "very good"
fi
Lesson6 - File test operators
下面的例子中,编写一个程序用来检测文件是否存在:
# -e 和结尾出的 \c 是为了让光标不换行
echo -e "Enter the name of the file: \c"
# 将用户输入的文件名称保存在 file_name 变量中
read file_name
# -e 用来判断该文件是否存在
if [ -e $file_name ]
then
echo "$file_name found"
else
echo "$file_name not found"
fi
测试:
➜ shell ./hello.sh
Enter the name of the file: test
test not found
➜ shell touch test
➜ shell ./hello.sh
Enter the name of the file: test
test found
File 相关的操作符:
- -e 判断文件是否存在
- -f 判断该文件是否存在并且是否是一个常规文件
- -d 判断文件是否是目录文件
- -c 判断文件是否是文本文件,如 text 文件
- -b 判断文件是否是 block file,如图片,音频,视频
- -s 判断文件是否为空文件
- -r 判断文件是否有可读权限
- -w 判断文件是否有可写权限
- -x 判断文件是否有可执行权限
向 test 中写入内容
cat > test 我是写入的内容 接着按 control + d 保存退出 注意:cat > test 是 test 文件中的内容被完全覆盖 cat >> test 是向 test 文件中继续写入内容,不覆盖
Lesson7 - 向文件中写内容
下面的案例是需求如下: 用户输入文件名称,检查文件是否存在,如果存在再检查该文件是否有写入权限,如果有写入权限就写入内容。
# -e 和结尾出的 \c 是为了让光标不换行
echo -e "Enter the name of the file: \c"
# 将用户输入的文件名称保存在 file_name 变量中
read file_name
if [ -f $file_name ] # ①
then
if [ -w $file_name ] # ②
then
echo "输入要写入的内容,按 ctrl+d 退出"
cat >> $file_name # ③
else
echo "该文件没有可写权限"
fi
else
echo "$file_name not exists"
fi
说明:
①:检查用户输入的文件是否存在
②:检查该文件是否有可写权限
③:向该文件中写入内容
一般的,我们如果直接使用命令如:touch demo
创建一个 demo 文件的情况下,该文件是有可写权限的,如下:
那么我们为了测试 ② 处的代码,可以手动移除 demo 文件的可写权限,执行命令 chmod -w demo
即可。
然后我们可以执行命令 chmod +w demo
即可重新设置 demo 文件的可写权限。
Lesson8 - 逻辑运算符 AND
age=20
if [ "$age" -gt 18 ] && [ "$age" -lt 30 ]
then
echo "valid age"
else
echo "age not valid"
fi
另外一种写法是:if [ "$age" -gt 18 -a "$age" -lt 30 ]
还有一种写法是:if (( $age >18 && $age < 30 ))
或者: if [[ "$age" -gt 18 && "$age" -lt 30 ]]
Lesson9 - 逻辑运算符 OR
逻辑或 和 逻辑与 运算符的语法一样,就是表达的意思不一样 逻辑与 需要 condition 中的所有条件都满足,当前的 condition 才为 true 逻辑或 只需要 condition 中的有一个条件满足,当前的 condition 就为 true
Lesson10 - 算术运算符
如果我们直接在 shell 脚本中执行如下命令:
#! /bin/bash
echo 1+1
执行之后并不会输出 2,而是会直接输出 1+1,因为 echo 会把之后的字符都当成字符串,并不会去执行算术运算符
我们使用如下的方式去执行两个数之间的运算
num1=20
num2=5
echo $(( num1 + num2 ))
echo $(( num1 - num2 ))
echo $(( num1 * num2 ))
echo $(( num1 / num2 ))
echo $(( num1 % num2 ))
输出:
25
15
100
4
0
注意:需要使用双括号开包裹,并且前面需要有一个 $ 符号
Lesson11 - 浮点型运算
我们将上一节中的 num1 的值由 20 改成 20.5,就会计算出错,如下:
num1=20.5
num2=5
echo $(( num1 + num2 ))
echo $(( num1 - num2 ))
echo $(( num1 * num2 ))
echo $(( num1 / num2 ))
echo $(( num1 % num2 ))
执行输入结果如下:
./hello.sh: line 8: 20.5: syntax error: invalid arithmetic operator (error token is ".5")
./hello.sh: line 9: 20.5: syntax error: invalid arithmetic operator (error token is ".5")
./hello.sh: line 10: 20.5: syntax error: invalid arithmetic operator (error token is ".5")
./hello.sh: line 11: 20.5: syntax error: invalid arithmetic operator (error token is ".5")
./hello.sh: line 12: 20.5: syntax error: invalid arithmetic operator (error token is ".5")
很明显,使用整型运算符的语法计算浮点型是行不通的。
要计算浮点型数据,我们需要借助系统内置的一个工具 bc,我们可以使用命令 man bc 来查看 bc 的相关文档。案例如下:
num1=20.5
num2=5
echo "$num1 + $num2" | bc
echo "$num1 - $num2" | bc
echo "$num1 * $num2" | bc
echo "$num1 / $num2" | bc
echo "$num1 % $num2" | bc
输出:
25.5
15.5
102.5
4
.5
注意:20.5 / 5 应该等于 4.1 的,但是上面计算你的结果是 4。这是个问题,我们可以通过 scale 来指定保留几位小数。
num1=20.5
num2=5
# 结果保留两位小数,输出的结果为 4.10
echo "scale=2;$num1 / $num2" | bc
还可以求平方根,立方等:
num3=4
# 求 4 的开平方根;-l 表示使用标准的 mathlib;具体用法可以 man bc
echo "scale=2;sqrt($num3)" | bc -l
# 求 3 的立方
echo "scale=2;3^3" | bc -l
有关 bc 的更多用法,可以通过 man bc
查看文档。
Lesson12 - case 语句
case 的语法如下:
case expression in
pattern1 )
statements ;;
pattern2 )
statements ;;
...
esac
案例:
vehicle=$1
case $vehicle in
"car" )
echo "Rent of $vehicle is 100 dollar" ;;
"van" )
echo "Rent of $vehicle is 80 dollar" ;;
"bicycle" )
echo "Rent of $vehicle is 5 dollar" ;;
"truck" )
echo "Rent of $vehicle is 150 dollar" ;;
* )
echo "Unknow vehicle"
esac
里面的 * 代表默认条件,类似 Java 中的 switch-case 语句中的 default 分支
Lesson13 - case 例子
判断用户输入的字符:
echo -e "Enter some character: \c"
read value
case $value in
[a-z] )
echo "User entered $value a to z"
;;
[A-Z] )
echo "User entered $value A to Z"
;;
[0-9] )
echo "User entered $value 0 to 9"
;;
? )
echo "User entered $value special character"
;;
* )
echo "Unknow input"
;;
esac
Lesson14 - 数组
# 定义一个数组
os=('ubuntu' 'windows' 'kali')
# 通过下标来添加或者更新元素
os[3]='mac'
# 通过下标删除数组中的元素
unset os[2]
# 输出数组中的所有数据
echo "${os[@]}"
# 输出数组中的第二个元素
echo "${os[1]}"
# 输出数组中所有的下标
echo "${!os[@]}"
# 输出数组的长度
echo "${#os[@]}"
string=afadsf
# 输出 afadsf
echo "${string[@]}"
# 输出 afadsf
echo "${string[0]}"
# 没有输出
echo "${string[1]}"
# 输出 1
echo "${#string[@]}"
Lesson15 - while 循环
while 的语法如下:
while [ condition ]
do
command1
command2
command3
done
从 1 到 10 循环输出:
n=1
while [ $n -le 10 ] # 或者 while (( $n <= 10 ))
do
echo "$n"
n=$(( n+1 )) # 或者直接 (( n++ ))
done
Lesson16 - while 循环中的 sleep
我们下面的例子是每间隔一秒输出一个数,从 1 到 10
n=1
while [ $n -le 10 ]
do
echo "$n"
n=$(( n+1 ))
sleep 1 # 单位是 秒
done
Lesson17 - 读取文件内容
使用 while 循环来读取文件内容
#! /bin/bash
while read p
do
echo $p
done < hello.sh
上述代码中,我们就是读取的 hello.sh 脚本中的内容,如下:
#! /bin/bash
while read p
do
echo $p
使用 cat 读取 hello.sh 文件内容
#! /bin/bash
cat hello.sh | while read p
do
echo $p
done
使用 IFS 读取 hello.sh 文件内容
#! /bin/bash
while IFS= read -r line
do
echo $line
done < hello.sh
Lesson18 - until loop
until loop 的语法如下:
until [ condition ]
do
command1
command2
command3
done
也就是说知道 condition 满足,do..done 中间的语句就不执行了,否则会一直执行 do..done 中的语句
下面写一个例子用来输入 1..9
n=1
until [ $n -ge 10 ]
do
echo $n
n=$(( n+1 )) # 或者直接这样 (( n++ ))
done
上述代码中,condition 是 n >= 10,所以会输出 1..9,如果需要输出 1..10 则只需要将 condition 改成 $n -gt 10 即可。
Lesson19 - For 循环
For 循环的几种语法如下:
for VARIABLE in 1 2 3 4 5 .. N
do
command1
command2
commandN
done
# OR ------------
for VARIABLE in file1 file2 file3
do
command1 on $VARIABLE
command2
commandN
done
# OR ------------
for OUTPUT in $(Linux-Or-Unix-Command-Hrer)
do
command1 on $OUTPUT
command2
commandN
done
#OR ------------
# 类似 Java 中的 for 循环
for ((Exp1; Exp2; Exp3))
do
command1
command2
command3
done
案例:输出 1 2 3 4 5
for i in 1 2 3 4 5
do
echo $i
done
如果你的 bash 的版本是 3.x ,还可以这样写:
for i in {1..5}
do
echo $i
done
使用类似 Java 中的 for 循环写法如下: 输出:0 1 2 3 4
for (( i=0; i<5; i++ ))
do
echo $i
done
Lesson20 - 使用 for 循环执行命令
下面的例子中,我们使用 for 循环分别执行 ls pwd date 这三个命令,如下:
for command in ls pwd date
do
echo "---$command-----" # 输出命令名称
$command # 执行命令
done
输出如下:
---ls-----
demo hello.sh test
---pwd-----
/Users/liusilong/Dev/shell
---date-----
Sat Feb 1 20:59:22 CST 2020
输出当前目录下所有是文件夹的文件
# * 表示当前文件夹下的所有文件
for item in *
do
if [ -d $item ] # -d 用来判断该文件是否为文件夹
then
echo $item
fi
done
Lesson21 - Select 循环
select 循环为我们提供了一个列表供我们选择,下面例子中我们提供一个名字的列表,然后让用户选择:
select name in mark john tom ben
do
echo "$name selected"
done
运行上述脚本,会给我们提供代码中的 4 个名字的选项列表:
接着我们选择第二个名称,输入 2,如下:
select 循环也可以和 case 语句结合使用,如下:
select name in mark john tom ben
do
case $name in
mark )
echo mark selected
;;
john )
echo john selected
;;
tom )
echo tom selected
;;
ben )
echo ben selected
;;
* ) # default
echo "Error"
;;
esac
done
输出如下:
Lesson22 - Break and Continue
break 用来退出当前循环。 下面的例子中,我们写一个从 1 到 10 的 for 循环,但是只输出前五个数字:
for (( i=1; i<=10; i++ ))
do
if [ $i -gt 5 ] # 如果 i > 5
then
break # 退出当前循环
fi
echo $i
done
输出:
1
2
3
4
5
continue 就是跳过本次循环,继续执行下一次。
我们同样使用 for 循环来输出 1 到 10,但是不输出 3 和 6,即,遇到 3 或者 6 就跳过本次循环。
for (( i=1; i<=10; i++ ))
do
if [ $i -eq 3 -o $i -eq 6 ] # 类似 Java 中的 if ( i == 3 || i == 6 )
then
continue
fi
echo $i
done
输出:
1
2
4
5
7
8
9
10
Lesson23 - Functions
shell 中方法的基本语法如下:
function name() {
statements
}
当然,function 关键字也可以省略:
name() {
statements
}
例子:
# 定义一个 hello 方法
function hello(){
echo "Hello"
}
# 定义一个 quit 方法
quit() {
exit
}
# 调用 hello 方法
hello
echo "foo"
# 调用 quit 方法
quit
输出:
Hello
foo
接受参数的方法:
# 定义一个 print 方法,并接受一个参数
function print(){
echo $1
}
# 调用 print 方法,并传递参数
print jack
print rose
输出:
jack
rose
Lesson24 - 局部变量(Local variables)
下面是一个带有局部变量的方法的例子:
function print(){
# 定义一个局部变量 name
name=$1
echo "the name is $name"
}
# 调动 print 方法,并传入参数
print Shell
输出: the name is Shell
我们把上述代码改成如下:
function print(){
# 定义一个局部变量 name
name=$1
echo "the name is $name"
}
# 定义一个全局变量 name
name="Tom"
echo "the name is $name : Before"
# 调动 print 方法,并传入参数
print Shell
echo "the name is $name : After"
这个输出如下:
the name is Tom : Before
the name is Shell
the name is Shell : After
看到了吗?最后一行输出中,明明调用的是 全局变量 name,为什么输出的是 局部变量 name 的值呢?
出现这种情况,我们需要在声明局部变量的时候,在其之前加上 local 关键字,否则局部变量的改变会影响到全局变量。
只需将上述代码中的 name=$1
改为 local name=$1
,这样,输出就正常了,局部变量和全局变量之间互不影响了。