Shell脚本--菜鸟教程笔记
Shell学习笔记
只针对菜鸟教程学习,后续再随着使用完善补充
shell环境:
Shell 编程跟 JavaScript、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。
Shell常见种类有:
- Bourne Shell(/usr/bin/sh或/bin/sh)
- Bourne Again Shell(/bin/bash) -- 本文记录的是Bash的学习笔记
- C Shell(/usr/bin/csh)
- K Shell(/usr/bin/ksh)
- Shell for Root(/sbin/sh)
在线编辑器:https://www.runoob.com/try/runcode.php?filename=helloworld&type=bash
编写及运行
- 第一行代码需要告诉系统这个脚本需要什么解释器来执行
#! /bin/bash
echo "hello world"
- 运行,以vscode运行test.sh为例(保证电脑里有git)
shell变量
变量命名
变量名的命名须遵循如下规则:
- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
- 中间不能有空格,可以使用下划线(_)。
- 不能使用标点符号。
- 不能使用bash里的关键字(可用help命令查看保留关键字)。
变量名和等号之间不能有空格
以下语句可以将文件xx下目录的文件名循环出来
for file in `ls /etc`
或
for file in $(ls /etc)
例如想打印文件dnk下的所有文件名:
for file in `ls ./dnk`;do
echo ${file}
done
使用变量
使用一个定义过的变量,只要在变量名前面加美元符号即可,如:
your_name="xiaohan"
echo $your_name
echo ${your_name}
花括号{}加不加都可以,但是不加的时候可能会出现和关键字冲突的情况,推荐写代码时加上花括号。
for skill in xian nv; do
echo "I am good at ${skill}Script"
done
以上述代码为例,上边展示的是skill没加花括号的结果,解释器就会把$skillScript当成一个变量(其值为空),下方的结果则是我们期待的运行结果。
只读变量
- 已定义的变量,可以被重新定义,如:
name="hhh"
echo ${name}
name="www"
echo ${name}
第二次赋值的时候不能写$your_name="alibaba",使用变量的时候才加美元符($)。
- 使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
my_name="xxn"
readonly my_name
my_name="dsz"
上面的例子尝试更改只读变量,结果报错:
./test.sh: line 15: my_name: readonly variable
删除变量
使用unset可以删除变量,但是只读变量不可以删除
my_name="hhh"
unset my_name
Shell字符串
定义字符串
字符串可以用单引号,也可以用双引号,也可以不用引号。当字符串有空格时,必须用引号
- 单引号
str='this is a string'
单引号字符串的限制:
- 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
- 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。
- 双引号
- 双引号里可以有变量
- 双引号里可以出现转义字符
- 双引号里可以直接打印出单引号,如果想打印双引号需要用转义符\
my_name="xxn"
str1="hello, I know you are \"${my_name}\""
str2="hello, I know you are '${my_name}'"
str3="hello, I know you are ${my_name}"
str4="hello, I know you are "${my_name}""
echo "str1:${str1}"
echo "str2:${str2}"
echo "str3:${str3}"
echo "str4:${str4}"
代码运行结果为
str1:hello, I know you are "xxn"
str2:hello, I know you are 'xxn'
str3:hello, I know you are xxn
str4:hello, I know you are xxn
拼接字符串
- 使用单引号拼接
str='不错'
pinjie1='今天天气'${str}''
pinjie2='今天天气${str}'
pinjie3='今天天气"${str}"'
结果为
今天天气不错
今天天气${str}
今天天气"${str}"
- 使用双引号拼接
pinjie4="今天天气'${str}'"
pinjie5="今天天气${str}"
pinjie6="今天天气"${str}""
结果为
今天天气'不错'
今天天气不错
今天天气不错
在单引号内进行拼接,只可以使用成对的单引号括住定义的变量并完成拼接;直接引用和使用双引号括住均无效,只会原样输出
在双引号内进行拼接,可以使用双引号括住变量,也可以直接使用变量;如果使用单引号括住,则单引号会被打印出来
获取字符长度
花括号内,在字符串变量名前加#即可
stt="ddwmkld12"
a=${#stt}
在使用变量的时候要加{}
提取子字符串
${字符串变量名:起始位置:截取长度}
stt="ddwmkld12"
a=${stt:2:4}
echo $a
结果
wmkl
起始位置从0开始
查找字符串
#!/bin/bash
string="runoob is a great site"
# 在string字符串中找r或者o,哪个先出现就输出谁的位置
echo `expr index "$string" ro`
结果是位置偏移,从1开始,例如r的位置是1;找不到的话才输出0
expr index STRING CHARS 命令并不是查找 CHARS 子字符串在 STRING 字符串中的位置。
它只能查找单个字符在 STRING 字符串中的位置。只是 CHARS 可以指定要查找哪几个字符,并以第一个查找到的字符为准。
当 STRING 字符串的内容包含空格时,要用双引号括起来,否则会报错。
其他
字符串还有很多操作,如替换、删除等,用到再看
Shell数组
bash只支持一维数组,初始化时不需要定义数组大小
定义数组
在 Shell 中,用括号来表示数组,数组元素用"空格"符号分割开。定义数组的一般形式为:
array_name=(value0 value1 value2 value3)
还可以单独定义数组的各个分量,可以不使用连续的下标,而且下标的范围没有限制。
arr[0]=50
arr[3]=30
此时输出数组的结果是 “50 30”,但是输出arr[1]的话显示空白,arr[3]才是50
读取数组
${数组名[下标]}
使用 @ 或者 * 符号可以获取数组中的所有元素
#! /bin/bash
arr=(1 2 3 4 'sds' df)
echo ${arr[@]}
输出的结果为
1 2 3 4 sds df
获取数组的长度
和字符串的方法相同
#! /bin/bash
arr=(1 2 3 4 'sds' df)
# 获取整个数组的长度,即数组中有几个元素。以下两种都可以,结果是6
echo ${#arr[*]}
echo ${#arr[@]}
# 获取数组中单个元素(第5个-sds)的长度,结果为3
echo ${#arr[4]}
Shell注释
- 单行注释:以#开头
- 多行注释:
:<
xxx可以用任意符号代替
Shell传递参数
我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。
$0代表执行的文件名称
#!/bin/bash
echo "Shell 传递参数实例!";
echo "执行的文件名:$0";
echo "第一个参数为:$1";
echo "第五个参数为:$5";
传入参数时,终端进行如下操作(上述代码保存在dw.sh)
./dw.sh 1 2 3 4
结果为:
Shell 传递参数实例!
执行的文件名:./dw.sh
第一个参数为:1
第5个参数为:
因为只传入了4个参数,故$5是空白
总结常用几个参数(还有不明白的之后解释)
| 参数处理 | 说明 |
|---|---|
| $0 | 执行的文件名 |
| $n | 第n个传入的参数 |
| $# | 传递到脚本的参数个数 |
| $* | 以一个单字符串显示所有向脚本传递的参数。 |
| $@ | 与$*相同,但是使用时返回的是每个参数。 |
| $$ | 脚本运行的当前进程ID号 |
| $! | 后台运行的最后一个进程的ID号 |
| $- | 显示Shell使用的当前选项,与set命令功能相同。 |
| $? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 |
$ 与 $@ 区别:*
- 相同点:都是引用所有参数。
- 不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 "1 2 3"(传递了一个参数),而 "@" 等价于 "1" "2" "3"(传递了三个参数)。
#!/bin/bash
echo "$*"
# 1 2 3 4
echo "$@"
# 1 2 3 4
for i in "$*";do
echo '$*' $i
done
# $* 1 2 3 4 -- 相当于传递了“1 2 3 4”这一个参数
for j in "$@";do
echo '$@' $j
done
# $@ 1
# $@ 2
# $@ 3
# $@ 4 --- 相当于传递了"1" "2" "3" "4" 四个参数
Shell运算符
- 算数运算符
- 关系运算符
- 布尔运算符
- 字符串运算符
- 文件测试运算符
算数运算符
加、减、乘、除、取余、赋值、相等、不等 和其他语言一样
原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。
expr 是一款表达式计算工具,使用它能完成表达式的求值操作。语法如下
`expr $a + $b`
# 外边的不是常用的单引号,是~下边那个
#!/bin/bash
a=100;
b=20;
c=`expr $a + $b`
echo $c
乘法的不能直接使用,要用转义符 /
在 MAC 中 shell 的 expr 语法是:$((表达式)),此处表达式中的 "*" 不需要转义符号 "" 。
也可以使用[]进行计算,在[]中的乘法不需要转义字符
a=10
b=20
c=$[a+b]
echo $c
//结果为30
关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
| 运算符 | 说明 |
|---|---|
| -eq | 两个数是否相等 |
| -ne | 两个数是否不相等 |
| -gt | 左边是否大于右边 |
| -lt | 左边是否小于右边 |
| -ge | 左边是否大于等于右边 |
| -le | 左边是否小于等于右边 |
布尔运算符
| 运算符 | 说明 |
|---|---|
| ! | 非 |
| -o | 或 |
| -a | 与 |
逻辑运算符
| 运算符 | 说明 |
|---|---|
| && | 逻辑中的AND |
| || | 逻辑中的OR |
字符串运算符
| 运算符 | 说明 |
|---|---|
| =(!=) | 两个字符串是否相等(不等) |
| -z | 字符串长度是否等于 0 |
| -n | 字符串长度是否不等于 0 |
| $ | 字符串是否不为 空 |
文件测试运算符
文件测试运算符用于检测 Unix 文件的各种属性。这部分只做简单记录,后续有应用的时候在介绍
| 操作符 | 说明 |
|---|---|
| -b file | 检测文件是否是块设备文件。 |
| -c file | 检测文件是否是字符设备文件。 |
| -d file | 检测文件是否是目录。 |
| -f file | 检测文件是否是普通文件(既不是目录,也不是设备文件)。 |
| -g file | 检测文件是否设置了 SGID 位。 |
| -k file | 检测文件是否设置了粘着位(Sticky Bit)。 |
| -p file | 检测文件是否是有名管道。 |
| -u file | 检测文件是否设置了 SUID 位。 |
| -r file | 检测文件是否可读。 |
| -w file | 检测文件是否可写。 |
| -x file | 检测文件是否可执行。 |
| -s file | 检测文件是否为不空(文件大小是否大于0)。 |
| -e file | 检测文件(包括目录)是否存在。 |
#!/bin/bash
file="/mnt/c/Users/taozhang/Desktop/WH_C++/Shell-/test.sh"
echo $0
if [ -e $file ]
then
echo "文件存在"
else
echo "文件不存在"
fi
file路径问题:
相对路径:当前文件夹下直接写文件名,上一级退出的话../xx.sh
绝对路径:直接看终端的复制前边部分,和文件夹下找到的路径格式不同
Shell常用命令
仅参考菜鸟教程,其余命令随时补充
echo命令
- 显示普通字符串:
echo "Hello World"
# 结果为 Hello World
- 显示转义字符串:
echo "\"Hello World\""
# 结果为 "Hello World"
补充一个read命令,从终端输入中读取一行,并指定给对应的变量
read name
终端运行后输入:wang xn
则再次调用name时,其内容就是"wang xn"
- 显示变量:
echo "${name} is a beautiful girl"
- 显示换行
echo -e "OK! \n"
echo "It is a test"
结果为:
OK!
It is a test
运行时发现的两点总结:
在转义字符后边接特定功能时,有两种方法,以 \n 举例:
1、空格 + \n :echo "wangxn \n"
2、-e:echo -e "wanghan\n"
如果直接echo "wangxn\n",\n会直接打印出来,没有效果
vscode终端中,如果使用 sh xxx.sh运行,就无法识别 -e
- 显示不换行
#!/bin/bash
echo -e "OK! \c"
echo -e "\"hello\""
结果为
OK! "hello"
- 显示结果至定向文件
echo "It is a test" > myfile
自动在当前文件路径下新建myfile文件,里边有 It is a test
如果需要在其他文件下创建文件并输出,则需要引号中写绝对(相对)路径
- 原样输出字符串,不进行转义或取变量(用单引号)
echo '$name\"'
# 结果为 $name\"
- 显示命令执行结果
echo `xx`
xx代表内部命令,如date就是显示当前日期的意思
printf命令
printf 使用引用文本或空格分隔的参数,外面可以在 printf 中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。默认的 printf 不会像 echo 自动添加换行符,我们可以手动添加 \n。
printf 命令的语法:
printf 格式控制字符串 参数列表
- 在格式控制字符串中,%s %c %d %f 都是格式替代符,%s 输出一个字符串,%d 整型输出,%c 输出一个字符,%f 输出实数,以小数形式输出。
- %-10s 指一个宽度为 10 个字符(- 表示左对齐,没有则表示右对齐),任何字符都会被显示在 10 个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
- %-4.2f 指格式化为小数,其中 .2 指保留2位小数。
printf "%-10s %-8.1f\n" abd 20.01
#结果为 abd 20.0
printf命令的转义序列(很多不明白,用到之后回头解释)
| 序列 | 说明 |
|---|---|
| \a | 警告字符,通常为ASCII的BEL字符 |
| \b | 后退 |
| \c | 抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略 |
| \f | 换页(formfeed) |
| \n | 换行 |
| \r | 回车(Carriage return) |
| \t | 水平制表符 |
| \v | 垂直制表符 |
| \ | 一个字面上的反斜杠字符 |
| \ddd | 表示1到3位数八进制值的字符。仅在格式字符串中有效 |
| \0ddd | 表示1到3位的八进制值字符 |
test命令
Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。
具体的参数查看Shell运算符-->关系运算符、字符串运算符、文件测试运算符、布尔运算符
#!/bin/bash
a=10
b=20
if test $a -lt $b
then
echo "OK"
else
echo "No"
fi
目前为止在if语句中test和[],在进行数值、字符和文件三个方面判断时的区别还没发现
test还支持与( -a )、或( -o )、非( ! )三个逻辑操作符(布尔运算符)用于将测试条件连接起来,其优先级为: ! 最高, -a 次之, -o 最低。
Shell流程控制
条件语句
if else
if condition
then
command1
commandN
else
command
fi
if else-if else
if condition1
then
command1
elif condition2
then
command2
else
command3
fi
condition应该用[]括起来,注意[]内前后留空格;或者与test结合
for循环
当变量值在列表里,for 循环即执行一次所有命令,使用变量名获取列表中的当前取值。命令可为任何有效的 shell 命令和语句。
in 列表可以包含替换、字符串和文件名。
for var in item1 item2 ... itemN
do
command
done
while语句
while 循环用于不断执行一系列命令,也用于从输入文件中读取数据。
while condition
do
command
done
补充:bash中的let命令
let 命令是 BASH 中用于计算的工具,用于执行一个或多个表达式,变量计算中不需要加上 $ 来表示变量。如果表达式中包含了空格或其他特殊字符,则必须引起来。
自加操作:let no++
自减操作:let no--
简写形式:let no+=10,let no-=20
a=10
b=20
let c=a+b
echo $c
目前为止,bash中的计算有: expr 、 [] 、 let 三种
while循环可用于读取键盘输入的信息
while read F
do
echo "您输入的是$F"
done
此时会进入循环中,运行后键盘输入什么,就会显示什么,并要求继续输入。如果想结束循环,终端 Ctrl+C
无限循环
无限循环有三种语法
# 第一种
while :
do
command
done
# 第二种
while true
do
command
done
第三种
for (( ; ; ))
do
command
done
until 循环
until 循环执行一系列命令直至条件为 true 时停止。
until 循环与 while 循环在处理方式上刚好相反。一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。
until condition
do
command
done
举例
a=1
until [ $a == 4 ]
do
echo -e "$a \c"
let a++
done
# 结果 1 2 3
case...esac语句
case ... esac 为多选择语句,与其他语言中的 switch ... case 语句类似,是一种多分枝选择结构,每个 case 分支用右圆括号开始,用两个分号 ;; 表示 break,即执行结束,跳出整个 case ... esac 语句,esac(就是 case 反过来)作为结束标记。
可以用 case 语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。
case 值 in
模式1)
command1
;;
模式2)
command1
;;
esac
举例子
read a
case $a in
1 | 2)
echo $a
;;
*) # 意思就是剩余的其他所有情况
echo no
;;
esac
跳出循环
break
break命令允许跳出所有循环(终止执行后面的所有循环)。语法就是;;
continue
continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。
语法 直接输入continue就可以了
补充:对于for循环,也可以 for((初始化参数;条件;命令)),就和其他语句一样了
for((a=1; a<=4; a++))
do
echo $a
done
while同理可以用(( ))存放条件
在shell中,(( ))有很多作用,只要括号中的运算符、表达式符合C语言运算规则,都可用在$((exp))中
这些作用以后见到代码再整理吧
Shell函数
function funname [()]
{
action;
[return int;]
}
function可以省略,如果加function可以省略名字后的()
函数返回值在调用该函数后通过 $? 来获得
在Shell中,函数的参数不是在定义时预先设计好的,而是调用函数时向其传递参数。
在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数...当n>=10时,需要使用${n}来获取参数。
#!/bin/bash
function fun()
{
echo "文件名称是$0"
echo "第一个参数是$1"
echo "共有$#个参数"
echo "脚本运行的当前进程ID号$$"
return `expr $1 + $2`
}
fun 1 2 3 4 5
echo $?
具体的函数参数 见上边的函数参数传递的表格
Shell 输入/输出重定向
| 命令 | 说明 |
|---|---|
| command > file | 将输出重定向到 file。 |
| command < file | 将输入重定向到 file。 |
| command >> file | 将输出以追加的方式重定向到 file。 |
| n > file | 将文件描述符为 n 的文件重定向到 file。 |
| n >> file | 将文件描述符为 n 的文件以追加的方式重定向到 file。 |
| n >& m | 将输出文件 m 和 n 合并。 |
| n <& m | 将输入文件 m 和 n 合并。 |
| << tag | 将开始标记 tag 和结束标记 tag 之间的内容作为输入。 |
补充:
- Here Document 是 Shell 中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell 脚本或程序。具体地,将 delimiter中的document作为输入传给command
command << delimiter
document
delimiter
- 如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:
$ command > /dev/null
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。
Shell文件包含
和其他语言一样,Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。
Shell 文件包含的语法格式如下:
. filename # 注意点号(.)和文件名中间有一空格,文件名输的是路径+文件名
或
source filename
举例说明:
test1.sh
#! /bin/bash
echo "hello world"
a=53
test2.sh
. ./test1.sh
echo $a
运行test2.sh后的 结果为:
hello world
53