一、构建基本脚本
1.1 使用多个命令
- 两个命令一起运行,放在同一行,用
;
隔开,例如pwd; ls
1.2 创建shell脚本
- 在创建shell脚本文件时,必须在文件的第一行指定要使用的shell。其格式为:
#!/bin/bash
- 井号(#)用作注释行。shell并不会处理shell脚本中的注释行。然而,shell脚本文件的第一行是个例外,#后面的惊叹号会告诉shell用哪个shell来运行脚本。
1.3 显示消息
-
使用
echo 内容
-n
不换行-e
启用转义字符,echo默认不处理转义字符
-
防止字符串被Shell特殊字符解析
- 输出内容用
""
双引号包裹:允许变量和转义字符的解析 - 输出内容用
''
单引号包裹:完全禁止解析,包括变量
- 输出内容用
-
长文本输出,使用
\
反斜杠echo "This is a \ long line"
-
命令捕获:将命令传给echo,使用
$(命令)
-
echo "current pwd is $(pwd)"
-
1.4 使用变量
- 环境变量可以直接用使用,用
$环境变量
即可。 - 用户变量可以是任何由字母、数字或下划线组成的文本字符串,长度不超过20个。用户变量区分大小写。
- 在变量、等号和值之间不能出现空格。
- shell脚本会自动决定变量值的数据类型。
- 引用一个变量值时需要使用美元符,而引用变量来对其进行赋值时则不要使用美元符
- 为了避免变量与其他内容混淆,可以使用
{}
包裹变量,例如echo "your name is ${name}"
1.5 重定向输入和输出
-
输出重定向
命令 > 文件
,将命令内容保存到指定文件,这个会覆盖原文件命令 >> 文件
,这个不会覆盖原文件,是追加操作。
-
输入重定向
-
命令 < 文件
,将文件的内容重定向到命令。 -
内联输入重定向
命令 << 结束标记
,可以不使用文件进行重定向,在命令行中指定输入重定向的数据。[tom@Centos7 bash_learn]$ wc << EOF > hello > ouyang > 18 > EOF 3 3 17
-
1.6 管道
- 将一个命令的输出作为另一个命令的输入,就可以使用管道。
命令一 | 命令二
- Linux系统实际上会同时运行这两个命令,在系统内部将它们连接起来。在第一个命令产生输出的同时,输出会被立即送给第二个命令。数据传输不会用到任何中间文件或缓冲区。
1.7 数学计算
- 使用
$[运算]
,只能整数运算 - bc计算器
- 输入bc后,就可以进入计算器页面,可以定义变量,进行计算
- 在脚本中使用:
var1=$(echo "scale=4; 3.44 / 5" | bc)
1.8 退出脚本
- shell中运行的每个命令都使用退出状态码(exit status)告诉shell它已经运行完毕。退出状态码是一个0~255的整数值,在命令结束运行时由命令传给shell。可以捕获这个值并在脚本中使用。
$?
表示上一个命令的执行结果,0通常代表成功。- 默认情况下,shell脚本会以脚本中的最后一个命令的退出状态码退出。
- exit命令允许你在脚本结束时指定一个退出状态码。
- 直接终止整个shell脚本
1.9 运行脚本的不同方式
- 使用路径(绝对路径或相对路径)执行
./test.sh
- 脚本运行在一个新的子shell中,环境是独立的。也就是说,脚本中的变量、函数、工作目录的改变等,只在子 Shell 中生效,不会影响当前 Shell。
- 指定shell运行
bash test.sh
- 脚本运行在一个新的子shell中,环境是独立的。
- 使用
source test.sh
或者. test.sh
- 在本shell中执行,环境不是独立的。
二、结构化命令
2.1 if-then 语句
-
基本语法
if 命令 # 如果命令的退出状态码是0,则表示成功,就会执行then后面的部分,否则不执行 then 命令 fi
- 另一种形式
if 命令; then 命令 fi # 这种形式更像其他编程语言的if
- 另一种形式
2.2 if-then-else 语句
- 基本语法
if 命令; then 命令 else 命令 fi
2.3 if-then-elif-then-else 语句
- 基本语法
if 命令; then 命令 elif 命令; then 命令 else 命令 fi
2.4 test语句
- 上述的if后面,紧跟着必须是命令,并且通过命令的状态码,来决定是否执行then语句。
- test命令中列出的条件成立,则会返回0,这样一来,就和其他语言的if-else类似了。
- 基本语法:
test 条件
,如果没有条件,则直接返回0- 还有另一种方式
if [ 条件 ]
,注意,第一个方括号后和第二个方括号前必须有一个空格
- 还有另一种方式
- 数值比较
n1 -eq n2
检查n1是否与n2相等n1 -ge n2
检查n1是否大于或等于n2n1 -gt n2
检查n1是否大于n2n1 -le n2
检查n1是否小于或等于n2n1 -lt n2
检查n1是否小于n2n1 -ne n2
检查n1是否不等于n2- 只能使用整数
- 字符串比较
str1 = str2
检查str1是否和str2相同str1 != str2
检查str1是否和str2不同str1 < str2
检查str1是否比str2小str1 > str2
检查str1是否比str2大-n str1
检查str1的长度是否非0-z str1
检查str1的长度是否为0
- 文件比较
-d file
检查file是否存在并是一个目录-e file
检查file是否存在-f file
检查file是否存在并是一个文件-r file
检查file是否存在并可读-s file
检查file是否存在并非空-w file
检查file是否存在并可写-x file
检查file是否存在并可执行-O file
检查file是否存在并属当前用户所有-G file
检查file是否存在并且默认组与当前用户相同file1 -nt file2
检查file1是否比file2新file1 -ot file2
检查file1是否比file2旧
- 复合条件测试
- 并且
[ condition1 ] && [ condition2 ]
- 短路与,如果前者为假,后者不会执行
- 或
[ condition1 ] || [ condition2 ]
- 短路或,如果前者为真,后者不会执行
- 并且
2.5 if-then的高级特性
- 双括号:使用高级数学表达式
val++
后增val--
后减++val
先增--val
先减!
逻辑求反~
位求反**
幂运算<<
左位移>>
右位移&
按位与|
按位或&&
逻辑与||
逻辑或
- 双方括号:字符串比较的高级特性。
- 提供了模式匹配
2.6 case命令
-
基本语法
-
case 变量 in 模式1 | 模式2) 命令;; 模式三) 命令;; *) # 都没匹配上时 命令 esac
-
2.7 for命令
-
基本语法
for var in list do 命令 done
- for命令遍历list,每次都会赋给var,list中的分隔符,默认是空格
-
更改字段分隔符
- 有一个特殊的环境变量
IFS
,内部字段分隔符,定义了shell用作分隔符的一系列字符。 - 改成换行符
IFS='$\n'
- 如果要指定多个IFS字符,只要将它们在赋值行串起来就行。
IFS=$'\n':;"
- 使用换行符,冒号和分号
- 有一个特殊的环境变量
-
使用通配符读取目录
-
for file in /home/rich/test/* # 读取所有文件(夹) do if [ -d "$file" ] # 为啥需要用双引号包裹,因为文件名可能有空格 then echo "$file is a directory" elif [ -f "$file" ] then echo "$file is a file" fi done
-
-
C语言风格的for
-
# 感觉很奇怪。。。 for (( i=1; i <= 10; i++ )) do echo "The next number is $i" done $ ./test8 The next number is 1 The next number is 2 The next number is 3 The next number is 4 The next number is 5 The next number is 6 The next number is 7 The next number is 8 The next number is 9 The next number is 10
-
2.8 while命令
-
基本语法
while 测试命令 do 命令 done
-
多个测试命令
while 测试命令一 测试命令二 do 命令 done
- 多个测试命令都会执行,注意,只以最后一个的执行结果来判断是否进入do
2.9 until命令
-
基本语法
until 测试命令 do 命令 done
-
和while相反,只有返回不为0,才会执行
-
同样也可以有多个测试命令,只看最后一个的执行结果。
2.10 break命令
- 作用:跳出单个循环
- 跳出外部循环
break n
,指定跳出的循环层级,默认不加就是1.
2.11 continue命令
- 作用:中止某次循环。
- 和break类似,可以使用
continue n
,指定中止的循环层级
2.12 处理循环的输出
- 可以将循环的输出结果重定向到文件中
for file in /home/rich/* do if [ -d "$file" ] then echo "$file is a directory" elif echo "$file is a file" fi done > output.txt # 在这里重定向
2.13 使用循环的例子
-
查找可执行文件
-
如何模拟shell,查找
PATH
中的可执行文件? -
首先,
PATH
中定义了许多可执行文件的路径,使用:
进行分割,所以我们要修改IFS=:
-
然后就可以遍历
PATH
中的所有路径,然后要保证是文件,并且具有执行权限。 -
IFS=: for folder in $PATH do echo "$folder:" for file in $folder/* do if [ -f "$file" ] && [ -x "$file" ] then echo " $file" fi done done
-
-
批量创建用户
-
现在需要批量创建用户,有一个文件
users.csv
,里面的内容是id,名字 姓
,也就是说两个字段,一个id,一个name,那么是包含空格的。 -
input="users.csv" # 从文件中读取,分隔符改成, while IFS=',' read -r userid name do echo "adding $userid" useradd -c "$name" -m $userid done < "$input"
-
三、处理用户输入
3.1 命令行参数
- 执行脚本时
./test.sh 10 30
,其中的10
和30
就是传入的参数 $0
是程序名,$1
是第一个参数,$2
是第二个参数,以此类推,$9
是第九个。第十个之后的参数${10}
- 获取脚本名,可以使用函数
basename
,例如,name=$(basename $0)
3.2 特殊参数变量
- 统计命令行中输入了多少个参数
$#
,程序名$0
不被计算在内。 $*
会将命令行上的所有参数当作一个单词保存,不包括$0
。- 可以使用for循环分割,但是如果某个参数就包含空格时,就会出问题。
$@
保存命令行的所有参数,同时保留参数边界。- 如果有空格,可以
for var in "$@"
,用双引号包裹即可。
- 如果有空格,可以
3.3 移动变量
- 使用
shift
命令,每执行一次,可以将所有参数往左移一个,也就是说$2
变成$1
,$3
变成$2
,注意,不会改变$0
,也就是说$1
被移没了。 shift n
,移动n个- 有啥用?可以依次处理参数,处理完一个后,就可以移出了。
3.4 处理命令行选项和参数getopt
-
命令格式
getopt optstring parameters
- 在optstring中列出脚本中用到的每个命令行选项字母,然后如果某个选项需要参数,则加一个
:
,
- 在optstring中列出脚本中用到的每个命令行选项字母,然后如果某个选项需要参数,则加一个
-
例子
# 需要abcd选项,b选项需要一个参数 $ getopt ab:cd -a -b test1 -cd test2 test3 -a -b test1 -c -d -- test2 test3 # --是为了分隔开选项和参数 --后就是参数了 $
-
getopt是将空格当作分隔符,也就是说,如果某个参数有引号,即使用双引号包裹起来,也不起作用。
-
在脚本中使用
set -- $(getopt -q ab:cd "$@")
-q
是忽略错误- 将用户的参数,交给getopt,并且替换成
$1
、$2
等等。
3.5 获得用户输入
-
使用
read 变量1 变量2
,将用户输入到变量中。- 之后就会等待用户输入,按回车结束输入
- 用户输入按空格分割分别赋值给变量1、变量2
- 如果用户输入数目不够,则后面的变量,值为空
- 如果用户输入的数目过多,最后一个变量,获取剩下的所有值
- 也可以不指定变量,那么就会保存在
REPLY
环境变量中。
-
-p
输出提示信息 -
-t seconds
指定超时时间,如果超时都未输入,则会返回一个非0的状态码。 -
-n5
限制输入字符数 -
-s
使用户输入内容和shell背景色一样(输入密码时)。 -
通过文件读取
-
count=1 # 通过管道 然后传给while循环,不断读取 每次读取一行 cat test | while read line do echo "Line $count: $line" count=$[ $count + 1] done echo "Finished processing the file"
-