《Linux命令行大全》重点笔记4
第四部分 编写shell脚本
第24章 编写第一个shell脚本
24.1 shell脚本
-
shell脚本:就是一个包含一系列命令的文件。
-
shell:一个强大的命令行接口,也是脚本语言解释器。
24.2 编写shell脚本
三个步骤:
- 编写脚本:常用vim。
- 使脚本可执行:权限设置。
- 放在shell能发现的位置:默认shell会自动寻找某些目录找可执行文件。
1、脚本文件格式
#!:shebang,使用脚本解释器名字。
#! /bin/bash
#:注释。
2、可执行权限
chmod 755 shellfile
:每个人都能执行。rwx | r-x | r-x
chmod 700 shellfile
:只要脚本所有者才能执行。rwx | — | —
3、脚本文件位置
两种执行方式:
- 显示指定脚本文件路径:
./shellfile
当前目录下的shellfile。 - 把文本添加进PATH环境变量中任一目录:
shellfile
可在任意位置运行。
PATH环境变量:用于保存可以直接运行可执行文件的路径,用:隔开。
24.3 编写shell脚本格式技巧
1、尽量写长选项名
可读性高。
ls -ad
等价于ls --all --directory
2、缩进和行连接
命令很长可以写成几行,在行尾使用行连接符\
。
第25章 启动一个项目
编写报告生成器:
vim ~/bin/sys_info_page # 编辑shell脚本,详情看下文
chmod 755 ~/bin/sys_info_page # 设置可执行权限
sys_info_page > sys_info_page.html # 输出重定向到一个html文件
firefox sys_info_page.html # 用firefox打开html文件
sys_info_page:
#! /bin/bash
# Program to output a system information page
echo "<HTML>
<HEAD>
...
</HEAD>
<BODY>
...
</BODY>
</HTML>"
1、定义变量/常量
ABC="hahaha"
:一般大写表示常量;
abc="xixixi"
:一般小写表示变量。
通过$ABC
/$abc
进行调用。
2、给变量/常量赋值
shell通过字符串给变量/常量赋值。
3、here文档
可以代替echo输出文本,工作原理是把文本输入到一个命令的stdin中。
command << token # '<<'换成'<<-',则可以忽略text中的Tab,方便对齐缩进。
text
token
所以sys_info_page改写为:
#! /bin/bash
# Program to output a system information page
cat << _EOF_
<HTML>
<HEAD>
...
</HEAD>
<BODY>
...
</BODY>
</HTML>
_EOF_
第26章 自顶向下设计
26.1 shell函数
shell函数定义必须在调用之前。
两种语法形式:
-
# 定义 function funcname { commands return } # 调用 funcname
-
# 定义 funcname () { commands return } # 调用 $(funcname)
26.2 局部变量
local varname
:通过变量名前面加上local关键字。
第27章 流控制:IF分支语句
27.1 使用if
if commands; then # if条件若有多条命令,根据最后一条执行结果进行评估
commands
elif commands; then
commands
else
commands
fi
27.2 退出状态
$?
:查看退出状态。0表示成功,其他数字表示失败。
true/fasle
:true命令表示成功(退出状态为0),false命令表示失败(退出状态为1)。
27.3 test命令
if和test命令一起使用。
[expression]
:expression是表达式,结果是true(test命令返回退出状态0)/false(test命令返回退出状态1)。
1、文件表达式
评估文件状态。
[-e "$FILE"]
:-e判断文件存在。
2、字符串表达式
测试字符串操作。
[-z "$ABC"]
:-z判断长度,引号包起变量防止参数为空。
3、整数表达式
27.4 [[]]——新test命令
[[expression]]
新增特性:
string=~regex
:与正则表达式regex匹配。$FILE == foo.*
:模式匹配。
27.5 (())——为整数设计
用于算术真值测试。
27.6 组合表达式
不同命令下用不同符号:
operation | test | [[]]或者(()) |
---|---|---|
AND | -a | && |
OR | -o | || |
NOT | ! | ! |
27.7 控制运算符
comm1 && comm2
:comm1成功了,comm2才能执行。
comm1 || comm2
:comm1失败了,comm2才能执行。
第28章 读取键盘输入
28.1 read——从stdin读取输入值
read [-options] [variable...]
例子🌰:
echo -n "please enter an integer -> " # -n设置不换行输出
read num # 接下来可以使用$num
read var1 var2 var3 # 输入参数不够时,使用默认值(空值)
read # 无变量存储,则存到$REPLY
-------------------------------------------------------------------
read -p "please enter an integer -> " num # -p显示提示
第29章 流控制:WHILE和UNTIL循环
1、while
退出状态不为0时终止。
while commands; do
commands
# 可在循环内使用continue/break.
done
2、until
退出状态为0时终止。
until commands; do
commands
# 可在循环内使用continue/break.
done
3、循环读取文件
while read a b c; do
printf "a: %s\t b: %s\t c: %s\n"\
$a \
$b \
$c
done < ts.txt
第30章 故障诊断
略。
第31章 流控制:case分支
case var in
value1) commands
;;
value2) commands
;;
value3 | value4)
commands
;;
...
*) commands
;;
sesac
第32章 位置参数
命令行默认存有10个参数,用$0/$1/.../$9
表示。
第一个$0
总是可执行程序所在路径名,其他的根据传参赋值。
1、确定实参数量
$#
:得到传入参数数量,不包括$0
。
2、shift——处理大量实参
每次只需要处理一个变量即可如$1
,每次执行一次shift,所有参数都往上移。
$1 | $2 | $3 |
---|---|---|
a | b | c |
shift:
$1 | $2 | $3 |
---|---|---|
b | c | d |
3、处理多个位置参数
$*
:所有参数按单词拆分。
$@
:所有参数按单词拆分。
"$*"
:所有参数合并成1个参数。
"$@"
:所以参数按引号区分。(最佳!)
第33章 流控制:for循环
1、for:传统shell形式
for var in words; do
commands
done
2、for:C语言形式
for ((expression1; expression2; expression3)); do
commands
done
第34章 字符串和数字
34.1 字符串参数扩展
1、基本参数扩展
${var}
:加上{}比较稳健。
2、空变量扩展
${var:-word}
:如果变量var未定义或为空,则赋值为word;否则还是原值。
${var:?word}
:如果变量var未定义或为空,则把word输出到stderr;否则还是原值。
${var:+word}
:如果变量var未定义或为空,无操作;否则赋值为word。
3、返回变量名扩展
${!string*}
:返回string开头的变量名,等价于${!string@}
。
4、字符串操作
${#string}
:字符串string长度。
${string:offset}
:从第offset字符开始提取剩余字符串。
${string:offset:length}
:从第offset字符开始提取长度为length的字符串。
${string#pattern}
:从头删除最短匹配串。
${string##pattern}
:从头删除最长匹配串。
${string%pattern}
:从尾删除最短匹配串。
${string%%pattern}
:从尾删除最长匹配串。
${string/pattern/string}
:以string替换第一个匹配串。
${string//pattern/string}
:以string替换所有匹配串。
${string/#pattern/string}
:以string替换出现在开头的匹配串。
${string/%pattern/string}
:以string替换出现在末尾的匹配串。
34.2 算术计算和扩展
$((expression))
:基本形式。expression是算术表达式。
$((n#number))
:n进制。特别的,默认是十进制,0开头是八进制,0x开头是十六进制。
$((a + b))
:a+b。支持各种操作如赋值、加减乘除、位运算、逻辑运算等。
34.3 bc——计算器程序
bc foo.bc
第35章 数组
1、创建数组
a[1]=foo
:直接给数组a的元素1赋值进行创建。declare -a a
:declare命令创建数组a。
2、数组赋值
name[index]=value
:单个赋值。name=(value1 value2...)
:多个同时赋值。name=([0]=value1 [1]=value2...)
:指定元素赋值。
3、访问元素
name[index]
:某个元素;
4、数组元素集合
"${name[@]}"
:所有元素集合。用法:for i in "${name[@]}"; do ... done
。
5、元素个数
${#name[@]}
:返回数组中元素个数。若只有对name[100]=foo创建赋值,name数组元素只有1个。
6、数组元素下标集合
"${!name[@]}"
:所有元素下标集合。用法:for i in "${!name[@]}"; do ... done
。
7、尾部添加元素
name+={var1 var2 var3}
8、数组排序
a_sorted = ($(for i in "${a[@]}"; do echo $i; done | sort))
:数组a排序后赋值给a_sorted。
9、删除数组
unset name
:删除整个数组;
unset 'name[index]'
:删除单个元素。
第36章 其他命令
36.1 命令组合使用
两种方式:
- 组命令:
{command1; command2; command3...; }
- 子shell:
(command1; command2; command3...; )
注意⚠️:每条命令都要分号结尾;并且最后要加空格!
组命令与子shell的区别:
- 组命令在一个shell里执行所有命令,子shell创建一个新的shell实例执行命令。
- 组命令更快,内存占用更少。
1、用于重定向
ls -l > output.txt
echo "helloworld" >> output.txt
cat foo.txt >> output.txt
------------------------------------------
# 一行搞定
{ls -l; echo "helloworld"; cat foo.txt; } > output.txt
(ls -l; echo "helloworld"; cat foo.txt; ) > output.txt
更常用于管道!{ls -l; echo "helloworld"; cat foo.txt; } | lpr
2、进程替换
由于!**管道中的命令是在子shell内执行!**所以无法对父shell变量进行赋值!
如:
echo "foo" | read
echo $REPLAY # 空值,因为read在子shell执行,"foo"保存在子shell的$REPLAY里,退出则销毁子shell
解决方法:
-
产生stdout:
<(commands)
子shell的输出以文件形式保存。 -
吸纳stdin:
>(commands)
例子🌰:
read < <(echo "foo")
echo $REPLAY # 成功读取"foo"
# 常用于read按行读取文本
while a b c; do
...
done < <(foo.txt)
36.2 trap——响应信号
trap string signal
:对信号signal作出响应string。
注意⚠️:string是命令加引号构成的字符串。
目的:处理程序不正常终止,对临时文件等的销毁以及提示报错。
36.3 wait——管理异步执行
何谓同步、异步:
- 同步:阻塞模式。进程A需等待B执行完后才可执行。
- 异步:非阻塞模式。无需等待,各自执行。
子脚本可以在父脚本执行时执行额外任务,但是父脚本如果需要子脚本的内容时,
可以用wait命令——父脚本暂停,直到指定进程/子脚本结束。
wait $pid
:阻塞至进程号为pid的进程结束运行。
36.4 命名管道——建立两个进程间通信的文件
命名管道,使用两块FIFO缓冲保存通信信息。
# 跨终端/跨进程
process1 > named_pipe
process2 < named_pipe
# 等价于同个终端/同个进程内
process1 | process2
1、mkfifo——设置命名管道
mkfifo pipe1
2、>/<——使用命令管道
终端1:
ls -l > pipe1
:数据传至命名管道pipe1并阻塞,直至管道中数据被读取。
终端2:
cat < pipe1
:读取命名管道pipe2。