由于在自己的工作和学习过程中,只查看某个大佬的教程或文章无法满足自己的学习需求和解决遇到的问题,所以自己在追赶大佬们步伐的基础上,又自己总结、整理、汇总了一些资料,方便自己理解和后续回顾,同时也希望给大家带来帮助,所以才写下该篇文章。在本文中,所有参考或引用大佬们文章内容的位置,都附上了原文章链接,您可以直接前往查阅观看。在原文章内容的基础上,若无任何补充内容,同时避免直接大段摘抄大佬们的文章,该情况下也只附上了原文章链接供大家学习。本文旨在总结归纳,并希望给大家提供帮助,未用作任何商用用途。文章内容如有错误之处,望各位大佬指出。如果涉及侵权行为,将会第一时间对文章进行删除。
👉 个人博客主页 👈
📝 一个努力学习的程序猿
🧑👉 本文所在专栏: Linux 专栏,欢迎大家前往查看更多内容
Shell 脚本的基础使用
前言
在前文了解了一些 Linux 基础知识后,不得不提的就是 Shell。虽然笔者没有用 Shell 脚本的需求,不过了解一些未必是坏事。本文将仅作学习笔记用。
在网上来看,Shell 是一个应用程序,它连接了用户和 Linux 内核,让用户能够更加高效、安全、低成本地使用 Linux 内核。Shell 既是一种命令语言,又是一种程序设计语言,而后者就是平时说的 Shell 脚本。如果您想对 Shell 有更多的了解,为了避免大篇幅引用,您可以参考以下文章:
接下来笔者关于 Shell 的总结,完全参考于菜鸟教程。如果您想全面的学习,可以直接跳转网页学习。本文将仅对重要内容进行总结:
Shell 教程
第一个Shell脚本
第一步: 找一个合适的位置创建一个文件:touch test.sh
其中,它的扩展名为 sh(sh代表shell),但扩展名并不影响脚本执行。比如用 php 写 shell 脚本,扩展名就用 php 好了,主要是为了自己区分。
第二步: 打开文本编辑器:vi test.sh
,并输入一些代码(具体操作可回看常用 Linux 命令中,关于编辑文件的操作):
#!/bin/bash
echo "Hello World !"
要说明的是,Shell 编程跟其他编程语言一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。而在Linux中,有很多 Shell 的种类:
Bourne Shell(/usr/bin/sh或/bin/sh)
Bourne Again Shell(/bin/bash)
C Shell(/usr/bin/csh)
K Shell(/usr/bin/ksh)
Shell for Root(/sbin/sh)
所以,在编写脚本的时候,需要在脚本文件的最前面加上 #!
,来告知系统要用哪个Shell程序来解释此脚本。
虽然笔者对不知道它们之间的差异,不过从网上来看,Bash易用且免费,在日常工作中被广泛使用,所以,在脚本文件的开头,我们使用 #!/bin/bash 。后续的代码示例,将不再复写这行代码。
第三步: 运行 Shell 脚本有两种方法:
1、作为可执行程序
首先我们会发现,我们刚创建的 test.sh 并不是一个可执行程序(如果不会看,可回看常用 Linux 命令中,关于权限的说明):
所以,我们先给它权限:
chmod +x ./test.sh
此时可以看到,它已经有了可执行权限。随后执行脚本:
./test.sh #执行脚本
需要注意的是,这里一定要写成 ./test.sh,而不是 test.sh,运行其它二进制的程序也一样。如果没有正斜杠,那么 Linux 系统就会去根目录里寻找。所以要用./test.sh 告诉系统在当前目录找。
2、作为解释器参数
这种运行方式比较简单,就是直接运行解释器,其参数就是 shell 脚本的文件名,
如:/bin/bash test.sh
如果选用这种方式运行脚本,那么也就不需要在第一行指定解释器信息。
也就是 #!/bin/bash
不需要再去写。同时也不需要调整它为可执行程序。
之后请随意选用一种方式执行,后续代码将不再赘述执行方式。
变量
变量的使用
在Shell脚本中定义变量的方式为:
my_name="csdn"
需要注意的是,在Shell脚本中,变量名和等号之间不能有空格。同时,变量名的命名需要遵循如下规则:
(1)命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
(2)不能使用bash里的关键字(可用help命令查看保留关键字)。
比如有效的变量名:
name
my_name
_name
name2
无效的变量名:
?name
*name
如果要使用一个已经定义过的变量,只需要在变量名前加美元符号即可:
name="csdn"
echo $name
echo ${name}
上面使用了两种方式。其中第二种方式中使用的花括号不是必须要加的,加花括号只是为了帮助解释器识别变量的边界,比如下面的情况下就必须要加:
name="csdn"
echo "I am a ${name}web developer"
如果不给 name 变量加花括号,写成了 $nameweb developer
,解释器显然会把 $nameweb
当成一个变量,从而找不到这个值。
如果想给已定义的变量重新赋值:
name="csdn"
echo $name
name="csdnNew"
echo $name
需要注意的是,刚才说使用一个已经定义过的变量需要使用 $
,但是在赋值的时候,它不算是使用变量,因此不需要这么写: $name="csdnNew"
。
如果想避免已定义的变量被重新赋值,可以使用 readonly
将其定义为只读变量(只读变量的值无法被修改,尝试修改将会报错):
name="csdn"
readonly name
如果想删除变量,需要使用 unset
命令:
name="csdn"
unset name
需要注意的是,unset
不能删除只读变量。
字符串
在声明一个字符串数据类型的变量时,字符串可以用单引号、双引号声明,也可以不用引号。但是不同的使用,会有着不同的限制:
单引号使用:
str='this is a string'
(1)单引号字串中不能出现单独一个的单引号(对单引号使用转义符也不行)。但可成对出现,作为字符串拼接使用,比如:
str='test''csdn'
(2)单引号里的任何字符都会原样输出(包括转义符),且单引号字符串中使用变量是无效的。比如:
name="csdn"
str='Hello, I know you are \"${name}\"! \n'
echo $str
输出结果为:
但是同样的代码,双引号就可以这样用:
name="csdn"
str="Hello, I know you are \"${name}\"! \n"
echo $str
输出结果为:
所以双引号的特性为:
(1)双引号里可以有变量
(2)双引号里可以出现转义字符
【表格转自菜鸟教程】
能否引用变量 | 能否引用转移符 | 能否引用文本格式符 (如:换行符、制表符) | |
---|---|---|---|
单引号 | 否 | 否 | 否 |
双引号 | 能 | 能 | 能 |
无引号 | 能 | 能 | 否 |
在上述用法里,还有一个 \n
。\n
其实是用来换行的,还有一个 \c
是用来不换行的。但从输出结果来看,在这里并没有生效。如果想要其生效需要这么做:
echo -e "测试!\n"
echo "测试!"
echo -e "测试!\c"
echo "测试!"
输出结果为:
其他用法:
获取字符串长度:
string="abcd"
echo ${#string} # 输出 4
提取子字符串:
string="csdn"
echo ${string:1:2} # 输出 sd
注意:和其他语言一样,第一个字符的索引值为 0。
查找子字符串:(下方为查找字符s或d,其中哪个字母先出现就计算哪个)
string="csdn"
echo `expr index "${string}" sd` # 输出2
注意:查找子字符串的脚本中 ` 是反引号,而不是单引号 '。
数组
通过了解得知:在 bash 中,仅支持一维数组,同时并没有限定数组的大小,初始化时也不需要定义数组大小。和其他语言一样,数组元素的下标由 0 开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于 0。
不过和其他语言不同的是,在 Shell 中的数组要用括号来表示,数组元素之间要用空格分开。比如:
name="cdsn"
array=(1 2 3 ${name})
echo ${array[0]} ${array[3]}
也可以单独再定义数组的各个分量:
array[0]=4
array[1]=$name
array[2]="name"
除此以外,使用 @
或 *
符号可以获取数组中的所有元素,比如:
echo ${array[@]} ${array[*]}
获取数组长度的方法与获取字符串长度的方法相同:
# 取得数组元素的个数
length=${#array[@]}
# 或者
length=${#array[*]}
# 取得数组单个元素的长度
length=${#array[3]}
注释
在前文中已经有了一些注释的使用。如果要单行注释,只需要以 #
开头即可:
# 这是一个注释
如果要多行注释,可以这么用:
:<<EOF
注释内容...
注释内容...
注释内容...
EOF
EOF 可以替换成其他符号,比如:
:<<'
注释内容...
注释内容...
注释内容...
'
:<<!
注释内容...
注释内容...
注释内容...
!
传递参数
$ 获取
在执行 Shell 脚本时,我们可以向脚本传递参数。脚本内获取参数的方式为:$n
,其中的 n 代表一个数字。如果数字为 0 代表着当前执行的文件名(包含文件路径),如果使用其他数字就代表着传递进脚本的参数,比如 1 为执行脚本的第一个参数,2 为执行脚本的第二个参数……
比如:
echo "执行的文件名:$0"
echo "第一个参数为:$1"
echo "第二个参数为:$2"
输出结果如下所示:
除了 $n
外,还有几个特殊字符可以用来处理参数:
【表格转自菜鸟教程】
用法 | 作用 |
---|---|
$# | 传递到脚本的参数个数 |
$* 或 $@ | 显示所有向脚本传递的参数 |
$$ | 脚本运行的当前进程ID号 |
$! | 后台运行的最后一个进程的ID号 |
$- | 显示Shell使用的当前选项 |
$? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误 |
其中的 $*
和 $@
这两种用法稍稍有些区别:
【图片转自菜鸟教程】
只是这么看可能有点懵,说白了就是:假设在脚本运行时写了三个参数 1、2、3,则使用 $*
等价于 “1 2 3”(传递了一个参数),而 $@
等价于 “1” “2” “3”(传递了三个参数)。
示例:
echo "参数个数为:$#"
for i in "$*"; do
echo "传递的参数作为一个字符串显示:"$i
done
for i in "$@"; do
echo "传递的参数作为多个字符串显示:"$i
done
输出结果为:
关于这里使用的for循环,将在下文说明。
read 获取
除了上述在脚本调用时传递参数外,还可以在过程中传递参数。比如:
echo "测试!"
read name
echo "$name 测试!"
输出结果:
不过需要注意的是,如果传递多参数,输入的参数之间需要用空格来分开。比如:
read firstName secondName
echo "第一个参数:${firstName}; 第二个参数:${secondName}"
输出结果:
此时可以发现 read 命令会一个一个地接收输入的参数,如果输入的参数个数大于需要的参数个数,则多出的参数将都会被作为整体被最后一个参数接收。
在使用 read 时,还可以设置一些其他参数:
-p 输入提示文字
-n 输入字符长度限制(达到6位,自动结束)
-t 输入限时
-s 隐藏输入内容
比如:
read -p "请输入密码:" -n 6 -t 5 -s password
echo -e "\npassword is ${password}"
输出结果:
流程控制 + 运算符
更详细的运算符说明和示例,为了避免大篇幅引用,建议您前往以下文章查阅:
https://www.runoob.com/linux/linux-shell-basic-operators.html
更详细的流程控制说明和示例,为了避免大篇幅引用,建议您前往以下文章查阅:
https://www.runoob.com/linux/linux-shell-process-control.html
在文中将对主要用法做一些总结和相关补充说明。
运算符
运算符使用
通过了解得知:原生 bash 不支持简单的数学运算,比如这样简单的使用 val=3+4
是不行的。
想要使用运算符,可以采用以下几种方式:
① expr
expr 是一款表达式计算工具,使用它能完成表达式的求值操作。比如:
val=`expr 2 + 2`
echo "两数之和为:$val"
需要注意:
(1)此时使用的是反引号 ` 而不是单引号 ’
(2)表达式和运算符之间要有空格,例如 2+2
是不对的,必须写成 2 + 2
。下面的几种方式也是同理,不再赘述。
②
val=$[ `expr 2 + 2` ]
echo "两数之和为:$val"
需要注意:通过查阅相关资料得知,有的文章中明确提到,使用方括号也需要有空格,例如像文章中表示下面第一行这样写是错误的,需要像下面第二行那样写:
$[`expr 2 + 2`]
$[ `expr 2 + 2` ]
但是在我自己的测试中发现,方括号和表达式之间的空格不写也没有问题,也就是使用上面第一行那样写。所以不知道是不是可能涉及版本问题、工具问题 或是 其他问题。虽然对该问题无法证实,但笔者这里还是建议先用上空格,其他的括号、方括号使用也是同理。如果真有类似的报错,就可以来确认是否是因为空格的问题导致。下文类似涉及到空格的问题将不再赘述。
③
val=$(expr 2 + 2)
echo "两数之和为:$val"
④
val=$[ 2 + 2 ]
echo "两数之和为:$val"
a=2
b=3
val2=$[ $a + $b ]
val3=$[ a + b ]
上文关于运算符的使用示例如下:
a=2
b=3
val1=`expr $a + $b`
val2=$[ `expr $a + $b` ]
val3=$(expr $a + $b)
val4=$[ a + b ]
echo "两数之和为:$val1 $val2 $val3 $val4"
输出结果:
之后您可以选择其中任意一种方式去使用。
算术运算符
【表格转自菜鸟教程】
需要注意的是:
(1)乘号 *
前,必须加反斜杠 \
才能实现乘法运算;
(2)通过了解得知:在 MAC 中 shell 的 expr 语法是:$((表达式))
,此时表达式中使用 *
不需要反斜杠 \
;
(3)使用 ==
和 !=
时和其他运算符的多种使用方式不同,它们必须要这么用:[ $a == $b ]
、[ $a != $b ]
,且只用于流程控制。比如这样用是不行的:
val=[ $a == $b ]
val=`expr $a == $b`
示例如下:
a=2
b=3
if [ $a == $b ]
then
echo "a == b"
else
echo "a != b"
fi
输出结果为:
关于其中的 if else 的使用,将在下文介绍。
关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
【表格转自菜鸟教程】
上述关系运算符和使用 ==
和 !=
时的说明一样,它们必须要这么用且只用于流程控制。不再赘述。
当然,这样记忆起来确实有些复杂,为了简化使用,还可以这样用:
(( $a > $b ))
能这样用的运算符是:>、<、>=、<=、==、!=
布尔运算符
【表格转自菜鸟教程】
上述布尔运算符和使用 ==
和 !=
时的说明一样,它们必须要这么用且只用于流程控制。不再赘述。
其中的 或运算 和 与运算 可能和大家熟知的不太相同,也不太好记忆,不过您依旧可以使用逻辑运算符。👇
逻辑运算符
【表格转自菜鸟教程】
上述逻辑运算符和使用 ==
和 !=
时的说明一样,它们必须要这么用且只用于流程控制。不再赘述。
使用的时候需要注意,此处是 双方括号。
除此以外,逻辑运算符还有另一种用法。在这种用法下,它可以不在流程控制中使用,(这种用法在其他语言,至少是前端中还是很常见的)比如:
a=2
b=2
c=2
d=3
[ $a -eq $b ] && echo "a等于b"
[ $a -eq $b ] || echo "c不等于d"
输出结果为:
简单总结来说,使用 &&
时,因为它是逻辑与,只有左侧命令执行成功,右侧命令才会执行。使用 ||
时,因为它是逻辑或,只有左侧命令执行失败,右侧命令才会执行。利用这个特性就可以做出上面的操作,从而省去使用 if else
。
字符串运算符
【表格转自菜鸟教程】
上述字符串运算符和使用 ==
和 !=
时一样,它们必须要这么用且只用于流程控制。
在这里需要注意: -n
在使用时一定需要有双引号,比如使用时如果你想把双引号去掉:
a=""
if [ -n $a ]
then
echo "字符串长度不为 0"
else
echo "字符串长度为 0"
fi
理论上来说,目前 a 的字符串长度确实为 0,但是输出结果将会是:
所以,在使用 -n
的时候一定要加上双引号,否则 [ -n $a ]
的结果永远为 true。
test 命令
更详细的 test 命令说明和示例,为了避免大篇幅引用,建议您前往以下文章查阅:
https://www.runoob.com/linux/linux-shell-test.html
Shell 中的 test 命令用于检查某个条件是否成立。比如,我们可以使用 test 来替换上述关系运算符的使用:
a=2
b=2
if test $a -eq $b
then
echo "两个数相等!"
else
echo "两个数不相等!"
fi
其他运算符同理。
if else
熟知的各语言使用:
if (判断条件) {
// 操作
}
else if (判断条件) {
// 操作
}
else {
// 不做任何事情
}
在 sh/bash 需要注意的是:如果其中的某一项没有做任何操作,比如上面的 else 中不写任何内容是不行的,即:如果某一项没有任何语句执行,就不能写这一项。具体的在 shell 脚本中的语法格式为:
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi
具体的使用在运算符中已经有过示例,比如:
a=2
b=3
if [ $a == $b ]
then
echo "a == b"
else
echo "a != b"
fi
需要注意的是,if
和 elif
使用时,下一行需要有 then
。但是使用到 else
时,下一行是没有 then
的。除此以外,在使用完 if-else
后的最末尾一行需要写上 fi
。
除了上述用法外,还可以把 then
放在和 if elif
同一行上,需要注意分号:
a=2
b=3
if [ $a == $b ]; then
echo "a == b"
else
echo "a != b"
fi
for 循环
使用示例:
a=(1 2 3)
for ((i=0;i<3;i++))
do
echo "数组值为:${a[$i]}"
done
输出结果为:
同理的,也可以把 do
放在上一行上:(后续使用中将不再对这样的用法进行赘述)
a=(1 2 3)
for ((i=0;i<3;i++)); do
echo "数组值为:${a[$i]}"
done
需要注意的是,和其他语言不同的是,在上例中使用 i
时,一定要加上 $
,不然程序会变成死循环。
和其他语言一样,如果要跳出所有循环(终止执行后面的所有循环),可以使用 break
,比如:(其他循环也可以这么用)
a=(1 2 3)
for ((i=0;i<3;i++)); do
echo "数组值为:${a[$i]}"
break
done
如果要跳出当前循环,可以使用 continue
,比如:(其他循环也可以这么用)
a=(1 2 3)
for ((i=0;i<3;i++)); do
echo "数组值为:${a[$i]}"
continue
echo "测试!"
done
while 语句
使用示例:
a=1
while (( $a<=5 )); do
echo $a
let "a++"
done
输出结果:
以上示例中使用了 Bash let 命令,它用于执行一个或多个表达式,变量计算中不需要加上 $
来表示变量,为了避免大篇幅引用,具体可查阅:https://www.runoob.com/linux/linux-comm-let.html
let "a++"
就相当于
a=`expr $a + 1`
case … esac
case … esac 为多选择语句,与其他语言中的 switch … case 语句类似,即:可以用 case 语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。
使用说明:每个 case 分支用两个分号 ;;
表示 break
,即执行结束,跳出整个 case … esac 语句。使用 esac(就是 case 反过来)作为结束标记。case 取值后面必须为单词 in
,每一模式必须以右括号结束。取值可以为变量或常数,匹配发现取值符合某一模式后,命令将一直执行直至遇到 ;;
。取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号 *
捕获该值,再执行后面的命令。
使用示例:
echo '请输入 1 到 4 之间的数字:'
echo '您输入的数字为:'
read num
case $num in
1) echo '您输入了 1'
;;
2) echo '您输入了 2'
;;
3) echo '您输入了 3'
;;
4) echo '您输入了 4'
;;
*) echo '您没有输入 1 到 4 之间的数字'
;;
esac
输出结果为:
函数
使用示例如下:
demo1(){
echo "这是demo1!"
}
demo1
demo2(){
echo "输入第一个数字:"
read a
echo "输入第二个数字:"
read b
return $(($a+$b))
}
demo2
echo "demo2 输入的两个数字之和为 $?"
输出结果如下:
说明:
(1)函数如果有返回值可以加 return。如果没有返回值可以不加 return。
(2)函数返回值在调用该函数后通过 $?
来获得。
(3)所有函数在使用前必须定义。即,函数必须写在调用函数前。在调用函数时,仅使用其函数名即可。
调用函数时也可以向其中传递参数。在函数体内部,需要通过 $n
的形式来获取参数的值,例如,$1
表示第一个参数,$2
表示第二个参数…
需要注意的是,当 n >= 10,也就是超过了十个参数时,获取参数需要使用 ${n}
,例如:${10}
表示第十个参数。
示例如下:
demo(){
echo "第一个参数为 $1 !"
echo "第十个参数为 ${10} !"
}
demo 1 2 3 4 5 6 7 8 9 10 11
输出结果:
至此关于 Shell 脚本的一些基础使用总结就完成了。文章主要为自己的学习笔记,也希望能给大家带来帮助。
由于在自己的工作和学习过程中,只查看某个大佬的教程或文章无法满足自己的学习需求和解决遇到的问题,所以自己在追赶大佬们步伐的基础上,又自己总结、整理、汇总了一些资料,方便自己理解和后续回顾,同时也希望给大家带来帮助,所以才写下该篇文章。在本文中,所有参考或引用大佬们文章内容的位置,都附上了原文章链接,您可以直接前往查阅观看。在原文章内容的基础上,若无任何补充内容,同时避免直接大段摘抄大佬们的文章,该情况下也只附上了原文章链接供大家学习。本文旨在总结归纳,并希望给大家提供帮助,未用作任何商用用途。文章内容如有错误之处,望各位大佬指出。如果涉及侵权行为,将会第一时间对文章进行删除。
👉 个人博客主页 👈
📝 一个努力学习的程序猿
🧑👉 本文所在专栏: Linux 专栏,欢迎大家前往查看更多内容