shell脚本基础
脚本声明
shell脚本文件通常以.sh作为文件扩展名,扩展名仅作为文件标识,并不影响脚本执行。shell程序的第一行,往往需要一个#!约定标记符,用于告诉系统这个脚本需要调用哪一种Shell解释器来执行
shell脚本的执行方式
1.通过文件名直接执行
首先需要为该脚本添加可执行权限,然后通过文件路径和文件名直接运行,操作系统会调用脚本文件第一行所指定的解释器来执行该脚本,如对于名为HelloWorld.sh脚本,执行方式为:
2.通过shell解释器调用
第二种执行方式为在调用shell解释器时,将脚本作为shell程序参数传入,该方法会忽略脚本第一行所指定解释器信息,直接使用用户唤起的shell程序作为脚本解释器
全局执行脚本方式
执行脚本一般需要在赋予脚本执行权限后,使用绝对路径调用,或者进入脚本所在目录,使用相对路径调用。那些全局可用的脚本和命令,Linux是根据PATH环境变量中所存储的脚本路径自动寻找执行实现的,可通过echo $PATH查看当前所配置的全局路径,手动安装的服务想实现全局执行脚本,有两种实现方法:
- 将脚本通过cp命令复制到PATH中的某个路径下,如:/usr/bin下
- 通过变量叠加的方式,将脚本路径添加到PATH变量中,格式为PATH=”$PATH”:路径
通过变量叠加的方式添加的全局变量,在系统重启后将失效,永久生效方法是将路径添加到/etc/profile文件的PATH变量后
注释
shell程序使用#开头作为注释,多行注释需要在每一行开头都添加#号,注意程序第一行的脚本声明并不是注释符
对于需要多行注释的内容,可以将其写入花括号中定义成一个函数,然后不调用即可
脚本格式转换
在Linux中,回车符识别为$
,而在windows中回车符为^M$,因此在windows中编写的sh脚本需要经过转换,才能在Linux中运行,转换命令为:
dos2unix 文件名
相反,将文件从Linux格式转为Windows格式为
unix2dos 文件名
脚本退出状态码
shell中运行的每个命令都使用退出状态码(exit status)告诉shell它已经运行完毕,这个状态码可以使用$?查看。同样,我们的脚本也可以使用退出状态码优雅地结束脚本执行,并返回脚本执行的状态。
退出状态码是一个0~255的整数值,默认情况下,命令成功执行时退出状态码是0,如果命令执行错误,则退出状态码就是一个正整数值。在脚本中,可通过exit 状态码命令指定脚本结束时的退出状态码,注意!exit命令会退出脚本,exit命令之后的任何代码都将不再执行。状态码应当设置在0-255区间内,否则会对其进行求模运算,如:指定状态码为300,则进行300%256运算,最后取值44,设置状态码时也可以使用变量
Linux中命令执行错误时的退出状态码无标准可循,但有一些可用参考:
状态码 | 描述 |
---|---|
0 | 命令成功执行 |
1 | 一般性未知错误 |
2 | 不适合的shell命令 |
126 | 命令不可执行 |
127 | 没找到命令 |
128 | 无效的退出参数 |
128+x | 与Linux信号相关的严重错误 |
130 | 通过Ctrl C终止的命令 |
255 | 正常范围之外的退出状态码 |
shell中的运算
各种括号使用预览
- ()用来创建子shell执行命令组,而$()则可以用来获取命令的输出,并将其赋值给变量,其作用和 ``命令调用一致,如:today=$(date “+%D”)。如果( )中指定了多个命令,则变量获取的是最后一个命令的输出
- [ ]用来进行条件判断,它是test命令的替代,可以结合if-then等条件判断语句进行数值比较、字符串比较、文件比较
- $(())和$[]用来进行整形数值运算,如:num=$[11+22]
- (())可以用来运算高级数学表达式,如:++、>>(右移位)、&&(逻辑与)等运算,也可以搭配if-then语句进行条件判断
- 双方括号[[]]命令用来进行高级字符串比较,允许使用*和?通配符,支持模式匹配,但部分shell可能不支持
- 大括号{ }用来进行变量引用,支持字符串拼接
$()与``命令调用
shell中最有用的特性之一是可以将命令的输出赋值给变量,然后通过变量来任意调用,有两种方法可以将命令输出赋值给变量:
- 通过反引号``
- 通过$( )
$(())与$[]整数运算
- 变量名=$((运算式))
- 变量名=$[运算式]
$[]是一种旧的Bash算术扩展方式,现代Shell更推荐使用$(())进行算术运算
expr表达式
expr是由Bourne shell提供的数学工具,bash shell为了保持跟Bourne shell的兼容而包含了expr命令。尽管现代脚本中大部分功能被更强大的 $(()) 算术求值或 ${} 字符串操作所取代,但 expr 在经典UNIX和早期shell脚本中仍然非常重要,expr表达式支持以下功能:
- 整数运算:加减乘除、取模运算等
- 字符串操作:获取字符串长度、子串提取、模式匹配等
- 条件判断:执行条件判断并返回 0 或 1 表示真假
expr通常搭配``和$()来获取expr表达式最后的计算值:
expr命令能够识别以下数学和字符串操作符,特殊字符的转义 expr 中的某些字符,如 *, &, <, >, | 等,在 shell 中具有特殊含义,需要用反斜杠 \ 进行转义处理
操作符 | 描述 |
---|---|
ARG1 + ARG2 | 返回ARG1和ARG2的算术运算和 |
ARG1 - ARG2 | 返回ARG1和ARG2的算术运算差 |
ARG1 \* ARG2 | 返回ARG1和ARG2的算术乘积,需要将*号转义 |
ARG1 / ARG2 | 返回ARG1被ARG2除的算术商 |
ARG1 % ARG2 | 返回ARG1被ARG2除的算术余数 |
ARG1 \< ARG2 | 如果ARG1小于ARG2,返回1;否则返回0 |
ARG1 <= ARG2 | 如果ARG1小于或等于ARG2,返回1;否则返回0 |
ARG1 = ARG2 | 如果ARG1等于ARG2,返回1;否则返回0 |
ARG1 != ARG2 | 如果ARG1不等于ARG2,返回1;否则返回0 |
ARG1 >= ARG2 | 如果ARG1大于或等于ARG2,返回1;否则返回0 |
ARG1 \> ARG2 | 如果ARG1大于ARG2,返回1;否则返回0 |
A | |
ARG1 \| ARG2 | 如果ARG1既不是null也不是零值,返回ARG1;否则返回ARG2 |
ARG1 \& ARG2 | 如果没有参数是null或零值,返回ARG1;否则返回0 |
STRING : REGEXP | 如果REGEXP匹配到了STRING中的某个模式,返回匹配的子字符串或长度 |
match STRING REGEXP | 如果REGEXP匹配到了STRING中的某个模式,返回该模式匹配 |
substr STRING POS LENGTH | 返回起始位置为POS(从1开始计数)、长度为LENGTH个字符的子字符串 |
index STRING CHARS | 返回在STRING中找到CHARS字符串的位置;否则,返回0 |
length STRING | 返回字符串STRING的数值长度 |
+ TOKEN |
将TOKEN解释成字符串,即使是个关键字 |
(EXPRESSION) | 返回EXPRESSION的值 |
变量
shell中的变量类型
shell程序的变量可以分为以下几种:
- 自定义变量,即程序员自定义的变量
- 预定义变量:bash预定义的变量
- 环境变量:由操作系统或用户设置的特殊变量,用于保存操作系统环境相关的数据,以及配置 Shell 的行为和执行环境
- 位置参数变量:用于向脚本中传递参数和数据,变量名不能自定义
自定义变量
- 变量名规则同C语言,允许由字母、数字、下划线组成,但不能以数字开头
- bash中变量默认类型都为字符串类型
- 变量赋值时,若值包含空格,则需要使用单引号或双引号包裹
- 变量值中可以使用\进行转义
- 变量名通常使用下划线分隔单词,如:user_name,server_ip
- 自定义的环境变量名、常量的变量名一般使用大写,如:readonly MAX_VLAUE=10
变量定义、赋值、调用
定义自定义变量不需要int、var等关键字,可以直接使用变量名=值形式定义并赋值,默认情况下,变量通常被视为字符串。对于定义过的变量,可以使用${变量名}调用,花括号用于识别变量边界,可以不加,但加上花括号是个好的编程习惯
只读变量
使用readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变,且只读变量不能使用unset删除
删除变量
可以通过unset 变量名命令删除变量,但无法删除只读变量
变量值检测与替换
类似于C语言三元表达式的一种功能,即检测变量y是否存在,或者其值是否为空/非空,然后根据y的状态赋予变量x不同值
语句 | 未定义变量y | 变量y为空 | 变量y非空 | 说明 |
---|---|---|---|---|
x=${y:-默认值} | x=默认值 | x=默认值 | x=$y | 如果变量y未定义或为空,则x使用指定的默认值 |
x=${y:=默认值} | x=y=默认值 | x=y=默认值 | x=$y,y值不变 | 如果变量y未定义或为空,则赋予y默认值,且x也使用默认值 |
x=${y:+替代值} | x为空 | x为空 | x=替代值 | 如果变量y已定义且非空,则x使用替代值 |
x=${y:?错误消息} | 输出消息到标准错误输出 | 输出消息到标准错误输出 | x=$y | 如果变量y未定义或为空,输出错误信息并退出 |
不带:的语法只检查变量是否已定义,不检查变量是否为空值(即使y为空也视为有效) | ||||
x=${y-默认值} | x=默认值 | x=$y(空) | x=$y | 如果y未定义,则x使用默认值 |
x=${y=默认值} | x=y=默认值 | x=$y(空) | x=$y,y值不变 | 如果变量y未定义,则x和y都赋值为默认值 |
x=${y+替代值} | x为空 | x=替代值 | x=替代值 | 如果变量y已定义(即使为空),则x使用替代值 |
x=${y?错误消息} | 输出消息到标准错误输出 | x=$y(空) | x=$y | 如果变量y未定义,则输出错误消息并退出 |
字符串
shell中的字符串可以使用单引号声明,也可以使用双引号声明,二者的特性:
- 单引号里的字符会原样输出,无法使用转义字符和变量,双引号里字符串的可以使用转义字符、引用变量
- 二者都可以用于字符串拼接
字符串拼接
可以直接在字符串前后使用${变量名}拼接字符串,不需要像其他语言一样添加+号等字符串拼接符
获取字符串长度
可以使用${#字符串变量名}来获取字符串长度
截取字符串
使用${字符串变量名:索引1:索引2}来截取从索引1到索引2的字符串,字符串索引从0开始
快速删除字符串
shell中可以使用#、##、%、%%符快速删除匹配的部分字符串
- ${字符串变量#pattern}删除pattern所匹配字符串之前的最短匹配部分
- ${字符串变量##pattern}删除pattern所匹配字符串之前的最长匹配部分
- ${字符串变量%pattern}删除pattern所匹配字符串之后的最短匹配部分
- ${字符串变量%%pattern}删除pattern所匹配字符串之后的最长匹配部分
查找子字符串
可转义的字符串
Bash支持使用$’字符串’语法来解析字符串中ANSI C 样式的转义序列,允许在字符串中插入特殊字符,如:换行符(\n)、制表符(\t)等,这可用于将转义字符赋值给IFS(字段分隔符变量)等
数组
数组定义
bash只支持一维数组,不支持多维数组,bash中的一维数组不限制数组大小,使用括号囊括数组元素,数组元素之间使用空格分隔:数组名=(值1 值2 值3)
数组元素
bash中数组使用${数组名[下标]}表示数组元素,与绝大多数语言一样,数组下标从0开始,可以用${数组名[@]}或${数组名[*]}表示数组中的所有元素
数组长度
获取数组长度的方法与获取字符串长度的方法类似,即在需要获取的对象前加上#号,该方法可以获得整个数组的长度,也可以用于获取单个数组元素的长度
关联数组
Bash 支持关联数组,可以使用任意的字符串、或者整数作为下标来访问数组元素,关联数组的键是唯一的
关联数组使用 declare 命令来声明,语法格式为:
declare -A 数组名
数组元素访问方式为:${数组名[“键名”]},通过${数组名[@]}或${数组名[*]}可以获取所有元素,在此基础上,在数组名前加上!可以获取数组所有键,在数组名前加上#可以获取数组长度
位置变量与参数传递
给脚本传递参数
在使用命令执行脚本时,可以向脚本中传递参数,在脚本中可以使用一些预定义变量获取到这些参数
变量 | 作用 |
---|---|
$n | n为数字,$0代表命令本身(包含文件路径),$1-$9代表命令第1-9个参数,10以上用{}包裹,如:${10} |
$* | 代表命令行所有参数,$*将命令行所有参数作为一个整体进行处理,在遍历时循环只执行一次 |
$@ | 代表命令行所有参数,$@会将参数依次拆开 |
$# | 代表传递到脚本的参数总个数 |
$? | 返回上一个命令的执行状态,如果返回0则说明执行正确,返回值非0则说明上一个命令执行不正确,用于检测上一个命令是否正确执行,方便后续处理 |
$$ | 脚本运行当前进程的进程号(PID) |
$! | 后台运行最后一个进程的进程号(PID) |
$- | 显示shell所使用的当前设置(如:是否使用监视模式,是否只读取指令,而不实际执行),与set命令功能相同 |
#! /bin/bash
echo $1 #输出10
echo $* #将参数 10 20作为一个整体输出
echo $@ #将参数依次处理为10和20两个参数输出
echo $# #输出参数个数
执行该脚本并传参
chmod 755 test.sh
./test.sh 10 20
for i in "$*";do
echo $i
done
for i in "$@";do
echo $i
done
执行 ./test.sh 1 2 3
第一个语句相当于传递了1个参数,输出1 2 3
第二个语句相当于传递了3个参数,输出
1
2
3
$0参数的使用
$0参数用于获取shell在命令行启动的脚本名,如果调用脚本时包含了路径,该参数会获取完整的脚本路径
如果不想获取文件路径,而只需要脚本名,则可以结合basename命令来剥离脚本名
基于该方法,可以写一个脚本,当用户输入不同的命令(不同脚本名),实现不同功能,但它们本质上是同一个脚本
移动参数shift
bash shell的shift命令能够用来操作命令行参数,在使用shift命令时,默认情况下它会将每个参数变量向左移动一个位置。所以,变量$3的值会移到$2中,变量$2的值会移到$1中,而变量$1的值则会被删除(注意,变量$0的值,也就是程序名,不会改变)
在不知道到底有多少参数时,可以先处理$1参数,然后用该命令依次移动参数,然后继续操作第一个参数,由此实现了对参数的遍历。但注意,如果某个参数被移出,它的值就被丢弃了,无法再恢复。shift也支持一次移动多个数据,只需要指定一个参数即可:shift 参数,因此,shift也常用来跳过不需要的参数
脚本选项
除了参数,绝大部分命令还会带有选项,如:ls -alh中的alh,同样,编写的shell脚本如果需要选项来加持命令的功能,则需要在脚本中对选项进行处理
选项与参数分割符
在Linux中,有些命令同时包含了参数和选项,为了方便将二者区分开来,Linux使用一个特殊标记符:双短线--表示选项列表结束,参数列表开始。该符号可以用于提醒命令和脚本,--符号后的都为命令(脚本)参数,该符号主要有两个使用场景:
- 选项输入结束,参数输入开始。如:test -a -b -c -- value1 data2;使用--可以很清晰的划分出命令选项和参数的界限
- 如果参数中包含-号,可能会被解析为选项,此时使用--分割可以防止参数被解析错误。如:创建名为-rTest的文件,如果使用touch -rTest会将-r解析为选项,因而返回错误,而执行 touch -- -rTest可成功创建该文件
简单选项处理
表面上看,命令行选项紧跟在脚本名之后,就跟命令行参数一样。因此对于简单的选项,可以像处理命令行参数一样处理命令行选项
如果选项中同时含有选项和参数,可以使用--符号分隔
当选项和参数混合时,需要对选项做进一步处理
getopt与getopts命令
上述方法不太方便处理选项合并输入等情况(如:-alh),事实上Linux提供了专门用于处理选项输入的命令——getopt,而bash也提供了更高级的内建命令——getopts
getopt命令
getopt命令用于解析命令行参数和选项,其命令格式为
getopt [选项] 解析的选项 需要解析的参数表列
getopt命令的选项有:
- -o 单个字符:指定命令短选项的字符串(如:-v),表示脚本支持的短选项(单个字符选项)
- --long 字符串1,字符串2…:指定命令长选项的字符串(如:--version),表示脚本支持的长选项(多字符选项)
- -n 替代字符串:指定一个名字来代替默认命令名称”getopt”
- -q:不生成错误信息(用户输入了指定选项之外的字符时)
- -s chars:指定分隔选项参数的字符集,通常用于指定选项和其参数之间的分隔符,默认是空格。
- -l:输出选项的字符串表示形式。
- -options:将选项和非选项分开,选项在参数列表的前面,非选项在参数列表的后面
指定命令解析的选项指该命令有效的选项字母,并指定选项需不需要参数值,如果需要则在选项后加上:号,随后getopt命令会根据指定的选项,解析getopt命令最后跟随的参数
getopt ab:cd -a -b test1 -cde test2 test3
该命令中,getopt命令定义了四个有效选项字母:a、b、c和d。字母b后有一个冒号(:),表示b选项需要一个参数值。当getopt命令运行时,它会检查提供的参数列表(-a -b test1 -cde test2 test3),并基于提供的optstring进行解析。注意,它会自动将-cde选项分成三个单独的选项,并插入双破折线(--)来分隔行中的选项和参数,因此,输入的参数会被解析为以下形式:
-a -b 'test1' -c -d -e -- 'test2' 'test3'
但由于getopt命令只指定了abcd四个有效选项,而用户输入了选项e,因此默认情况下,getopt命令会产生一条错误消息:
getopt: invalid option -- e
此时,可以添加为getopt命令添加-q选项来忽略错误消息:
getopt -q ab:cd -a -b test1 -cde test2 test3
shell脚本中使用getopt命令,可以搭配set命令使用,set命令可以将其接收的参数按空格分割,并赋值给位置参数,可以将getopt解析出来的值作为set命令的输入参数,此后就可以使用$位置值的方式调用getopt命令解析出来的值
#! /bin/bash
# 对用户输入的选项和参数进行处理
args=$(getopt -o a:b:cd --long aaa:,bbb:,ccc -- $@)
if [ $? -ne 0 ]
then
echo"解析错误"
exit 1
fi
# 查看getopt命令解析出来的选项和参数
echo "---------"
echo $args
echo "---------"
set -- $args
# 遍历所有选项,给与不同的功能
while true
do
case $1 in
-a|--aaa) echo "输入了-a或-aaa选项,参数值为$2" ; shift 2 ;;
-b|--bbb) echo "输入了-b或-bbb选项,参数值为$2" ; shift 2 ;;
-c) echo "输入了-c选项" ; shift ;;
-d) echo "输入了-d选项" ; shift ;;
--ccc) echo "输入了--ccc长选项" ; shift; break ;;
--) shift; break ;;
esac
done
# 选项外的参数另作处理
echo "额外的输入参数有:$@"
getopts命令
getopts命令是bash shell的内建命令,为了弥补getopt命令的不足(如:不好处理带空格和引号的参数),它提供了一些高级功能。
getopts命令与getopt命令不同,每次调用getopts命令,它只处理命令行上检测到的一个参数,因此往往需要循环调用,处理完所有的参数后,它会退出并返回一个大于0的退出状态码。getopts命令的格式与getopt命令相似:
同样,解析选项中可以指定命令需要识别的有效字母选项,如果该选项需要参数,就加一个冒号。如果需要getopts命令忽略错误信息,可以在整个解析选项字符串前加个冒号,相当于getopt命令的-q选项。getopts命令会将输入的参数表列保存在指定的变量中,方便遍历。
getopts命令还预定义了两个环境变量:如果选项需要跟随一个参数值,OPTARG环境变量会保存该值,而变量OPTIND变量保存了参数列表中getopts正在处理的参数位置。
getopts命令有以下特点:
- 该命令不直接支持长选项,但处理单字母选项很方便
- 解析命令行选项时该命令会移除开头的单破折线,所以在case定义中不用单破折线
- 该命令支持在参数值中包含空格,只需要将带空格的参数值用双引号包裹即可
- 可以将选项字母和参数值放在一起使用,而不用加空格,如:-abvalue,命令可以根据case定义的字符正确解析出-a和-b选项,以及value参数值
选项标准化
部分选项在Linux中有一些墨守成规的用途,遵守这些规定会让脚本看起来更友好一些
选项 | 说明 | 选项 | 说明 |
---|---|---|---|
-a | 显示所有对象 | -n | 使用非交互模式(批处理) |
-c | 生成一个计数 | -o | 将所有输出重定向到的指定的输出文件 |
-d | 指定一个目录 | -q | 以安静模式运行 |
-e | 扩展一个对象 | -r | 递归地处理目录和文件 |
-f | 指定读入数据的文件 | -s | 以安静模式运行 |
-h | 显示命令的帮助信息 | -v | 生成详细输出 |
-i | 忽略文本大小写 | -x | 排除某个对象 |
-l | 产生输出的长格式版本 | -y | 对所有问题回答yes |
数值声明与数值运算
默认情况下,Bash中的变量会被声明为字符串类型,如:num=22,Bash会将22作为字符串处理,诸如11+22的式子也会被识别为字符串,想要进行数值运算,需要使用特殊声明语句和运算方式
数值声明
该语句用于特殊声明变量类型,使变量不再只限于字符串类型
declare [+或-] [选项] 变量名
- - 给变量设置类型属性
- + 取消变量的类型属性
- -i 将变量声明为整形(integer)
- -x 将变量声明为环境变量
- -p 查看变量的类型
数值运算
进行数值运算可以使用以下几种方式:
1.通过”$(())”或”$[]”运算式
- 变量名=$((运算式))
- 变量名=$[运算式]
2.通过declare -i声明
declare -i 变量名
浮点数运算的解决方案
z shell提供了完整的浮点数运算操作,但在bash shell中,需要借助内建的bash运算器——bc
输入输出
用户输入read
read [选项] [变量名] 从键盘(默认)读取一行数据,然后将输入的数据分解成字段,并将字段分别赋值给这些变量
- -p “提示信息” 输出提示信息(prompt 提示)
- -t 秒数 read命令只等待指定时间(timeout 超时),如果超时未输入,read命令会以非零状态码退出
- -n 字符数 read命令只接收指定数量字符
- -s 隐藏输入的内容(silent 沉默)
- -i 文本 将指定文本作为默认输入值
- -a 数组名 将输入的数据赋值给数组变量
- -d 字符 指定一个定界符,当入户输入该字符时,read命令将停止读取输入(delimit 划定界限)
- -e 允许用户使用Readline功能,该功能允许用户使用快捷键进行快速操作(如快速跳转到本行开头)
- -r 禁用转义,\不再会被解析为转义字符
- -u 文件描述符 从指定的文件描述符中读取,如标准输入设备的文件描述符为0
- -c 在读取输入时不需要按回车确认,即实时输入
- 如果在命令中不指定变量,则read读入的数据会保存在环境变量REPLY中
read也支持一次输入多个数据,数据之间用空格隔开,然后赋值给多个变量。如果输入的数据个数多于变量个数,则多出来的数据连同空格会被赋值给最后一个变量。read命令也支持将多个数据直接存入数组中,如果数据间的分隔符是,或者其他符号,也可以通过IFS变量修改分隔符(Linux中分隔符默认为空格、制表符、换行符),方便从文件中读入数据并作处理
终端输出
echo [选项] [输出内容] 用于向终端输出文本信息
- -e 启用转义字符\,可以输出如换行符(\n)、制表符(\t)等,也可以修改输出文字的样式
- -E 禁用转义字符,原样输出文本
- -n 不换行输出文本,输出完毕后光标停留在同一行
输入输出重定向
文件描述符与标准输入输出设备
Linux系统将每个对象当作文件处理,并使用文件描述符(file descriptor)来标识每个文件对象。文件描述符是一个非负整数,可以唯一标识会话中打开的文件,bash shell保留了前三个文件描述符(0、1和2)作为标准输入、标准输出、标准错误输出的文件描述符,每个进程一次最多可以有九个文件描述符。
设备 | Linux文件名 | 文件描述符 | 类型 |
---|---|---|---|
键盘 | /dev/stdin | 0 | 标准输入 |
显示器 | /dev/stdout | 1 | 标准输出 |
显示器 | /dev/stderr | 2 | 标准错误输出 |
输出重定向
一般情况下,Linux输出信息时默认的输出位置为屏幕,可以用重定向符将命令的输出重定向到文件等地方,而不再显示到屏幕上。如果重定向位置为文件而该文件不存在,则会新建文件
类型 | 符号 | 作用 |
---|---|---|
标准输出重定向 | > | 以覆盖方式将正确命令返回内容输出到指定文件或设备中 |
>> | 以追加方式将正确命令返回内容输出到指定文件或设备中 | |
标准错误输出重定向 | 2> | 以覆盖方式将错误命令返回内容输出到指定文件或设备中 |
2>> | 以追加方式将错误命令返回内容输出到指定文件或设备中 | |
同时输出正确和错误命令返回信息 | &> | 以覆盖方式,把正确和错误返回信息保存到同一文件中(Bash特有简写,也可以写为>&) |
&>> | 以追加方式,把正确和错误返回信息保存到同一文件中(Bash特有简写) | |
cmd>file 2>&1 | 以覆盖方式,把正确和错误返回信息保存到同一文件中(POSIX标准写法,兼容性强) | |
cmd>>file 2>&1 | 以追加方式,把正确和错误返回信息保存到同一文件中(POSIX标准写法,兼容性强) | |
cmd>>file1 2>>file2 | 把正确命令返回信息追加到文件1中,错误命令返回信息追加到文件2中 | |
在脚本中输出错误消息(输出到stderr) | >&2 | 在脚本中自定义一个消息作为错误输出 |
批量重定向 | exec 1> | 将脚本执行期间所有标准输出重定向到指定位置 |
exec 2> | 将脚本执行期间所有错误输出重定向到指定位置 |
1.标准输出与标准错误输出的重定向
标准输出是指程序执行正确时,会向用户输出普通信息流(如:程序的执行结果、信息或数据等)。标准错误输出则用来显示错误信息,当程序遇到错误或异常时,它会将错误信息发送到标准错误输出流。默认情况下,标准输出和标准错误输出的输出位置都为屏幕,但可以使用重定向命令将程序的正确执行结果或错误执行结果输出到屏幕以外的地方。
单独输出正确信息时,可以在重定向符中省略文件描述符1,但如果同时重定向了标准输出和标准错误输出,则需要加上文件描述符
2.输出错误信息
如果需要在脚本中输出错误信息,则可以将输出信息重定向到STDERR文件描述符,语法为:输出信息 >&2。在终端中输出语句时看起来和正常echo语句无区别,但对系统而言明确了这是一个错误输出信息
输入重定向(不常用)
将文件等内容重定向到命令,下列命令省略了文件描述符0
类型 | 符号 | 作用 |
---|---|---|
输入重定向 | n< | 从文件描述符n输入,n默认为0,n为0时可以省略 |
标准输入重定向 | < | 将文件或设备内容重定向到命令 |
内联输入重定向 | << | 在命令行中指定多个文本输入内容 |
如:使用wc命令统计文件中的字符
内联输入重定向(inline input redirection)用于从命令行输入多行数据。在使用时,必须指定一个文本用于标记输入数据的开始和结尾,完整的语法为:
批量重定向
当脚本中有大量信息需要重定向,如果为每个命令都进行一次重定向过于繁琐,此时可以搭配exec命令指定shell将脚本执行期间的信息都重定向到某个位置。exec会替换当前shell的执行上下文环境,将标准输出输入和错误输出重定向到指定位置
丢弃命令输出
如果需要某个输出既不显示到屏幕上,也不保存到文件中,可以将其重定向到一个名为null的特殊文件中。Linux中该文件的位于/dev/null路径下,所有重定向到该文件中的数据都会被丢弃,该文件中无任何内容。因此也可以在输入重定向中将其作为输入文件,可以用来快速清空文件内容,将该文件变为空白文件,常用于日志文件中清除内容(也可以删除再创建)
自定义重定向
在shell中最多可以有9个打开的文件描述符。除了标准输入0,标准输出1和标准错误输出2,其他6个从3~8的文件描述符均可用作输入或输出重定向。可以将这些文件描述符中的任意一个分配给文件,然后在脚本中使用它们
创建文件描述符
可以直接使用exec命令直接给输入、输出分配文件描述符。和标准的文件描述符一样,一旦将另一个文件
描述符分配给一个文件,这个重定向就会一直有效,直到其被重新分配。
重定向文件描述符
语法 | 说明 |
---|---|
n>file | 以覆盖方式,将文件描述符n重定向到文件file |
n>>file | 以追加方式,将文件描述符n重定向到文件file |
n>&- | 关闭文件描述符n |
n>&m | 将n的输出合并到m |
n>&m- | 移动文件描述符,将n重定向到m,然后关闭m |
<n | 从n输入 |
m<&n | 将n的输入与m合并 |
n<&m | 复制文件描述符,将文件描述符m复制到n,对n读取等同于对m读取 |
n<&m- | 移动文件描述符,将文件描述符m移动到n,随后关闭m |
n<>file | 允许通过n同时对文件进行读操作和写操作 |
恢复文件描述符的指向
通过exec命令重定向的文件描述符会在整个脚本运行期间指向指定的位置,如果需要将其恢复到原来的指向位置,可以创建一个文件描述符作为一个缓存变量,并在代码功能执行完后将需要恢复的文件描述符重定向到新建的文件描述符
同时读写的文件描述符(了解)
shell也支持打开单个文件描述符来作为输入和输出,即可以用同
一个文件描述符对同一个文件进行读写。shell会维护一个内部指针,指明在文件中的当前位置,任何读或写都会从文件指针上次的位置开始,因此可能出现内容被错误读写的问题,不太好用
关闭文件描述符
shell会在脚本退出时自动关闭文件描述符,若要手动关闭文件描述符,则需要将该文件描述符重定向到特殊符号&-,文件描述符关闭后,如果输出数据shell将抛出错误
列出打开的文件描述符
lsof命令会列出整个Linux系统打开的所有文件描述符。由于该命令会向非系统管理员用户提供Linux系统的信息,因此在很多Linux发行版中隐藏了该命令。普通用户要想运行该命令,必须使用全路径来引用该命令:/usr/sbin/lsof
管理员用户可以直接使用:
- -p PID:指定对应进程PID所打开的文件描述符
- -d 值:指定需要显示的文件描述符编号
- -a 对上述两个选项结果进行布尔AND运算
该命令返回信息包括:
参数 | 说明 |
---|---|
COMMAND | 正在运行的命令名的前9个字符 |
PID | 进程的PID |
USER | 进程属主的登录名 |
FD | 文件描述符号以及访问类型(r代表读,w代表写,u代表读写) |
TYPE | 文件的类型(CHR代表字符型,BLK代表块型,DIR代表目录,REG代表常规文件) |
DEVICE | 设备的设备号(主设备号和从设备号) |
SIZE | 如果有的话,表示文件的大小 |
NODE | 本地文件的节点号 |
NAME | 文件名 |
返回值结果举例
由于STDIN、STDOUT和STDERR文件描述符都指向终端,因此其文件类型是字符型,输出文件的名称就是终端的设备名,且所有3种标准文件都支持读和写。
tee同时输出到显示器和文件
Linux提供了一个特殊命令tee,用于将输出同时发送到标准输出(显示器)和文件,而不用重定向多次:
- 默认情况下,如果指定的文件不存在,则会新建该文件,如果文件存在,tee命令会覆盖原文件内容
- -a 将数据追加到文件中
- -i 忽略中断信号,例如通过 Ctrl+C 触发的信号
tee命令会重定向STDIN输入的数据,并同时发往两处:STDOUT和tee命令指定的文件,因此可以搭配管道符使用。
条件判断
在bash shell中,if 语句的判断依据为命令的退出状态码,不能像其他语言一样直接用 if 语句判断条件语句,但bash shell提供了两个用于条件判断的方法:
- test 命令
- [ ] 使用中括号测试条件
test命令与[ ]
test命令可用于做数值比较、字符串比较和文件比较,当test命令中列出的条件成立,test命令就会正常退出并返回退出状态码0,这样if等语句就可以正常工作了。此外,test命令有一种替代形式,可以直接将条件语句写于中括号[ ]中,注意,第一个中括号后和第二个中括号前都必须加上一个空格,否则将报错。如果test命令后或者中括号中不写条件语句,则它们都将视为条件不成立,二者的语法类似:
- test 条件语句
- [ 条件语句 ]
str=""
if test $str
then
echo "该变量存在"
else
echo "该变量不存在"
fi
或
if [ $str ] #取变量值$str前后均有空格
then
echo "该变量存在"
else
echo "该变量不存在"
fi
文件相关
上述两个方法用于测试文件或目录是否满足相关要求时的语法为:
- test 选项 文件/目录名
- [ 选项 文件/目录名 ]
文件类型
测试选项 | 作用 |
---|---|
-e file | 判断file是否存在 |
-d file | 判断file是否存在,且是否为目录 |
-f file | 判断file是否存在,且是否为为文件 |
-b file | 判断file是否存在,且是否为块设备文件(如:硬盘设备文件) |
-c file | 判断file是否存在,且是否为字符设备文件(如:鼠标、键盘设备文件) |
-O file | 判断file是否存在并属于当前用户所有 |
-G file | 判断file是否存在并默认组与当前用户相同 |
-L file | 判断file是否存在,且是否为链接文件 |
-p file | 判断file是否存在,且是否为管道文件 |
-s file | 判断file是否存在,且文件大小是否大于0(非空),非空为true |
-S file | 判断是否为套接字文件 |
-N file | 判断file是否存在,且文件在上一次读取之后被修改过(mtime比atime新),则为true |
location=$HOME
file_name="myFile"
if [ -e $location ]
then
if [ -e $location/$file_name ]
then
echo "文件存在"
echo "写入数据"
date >> $location/$file_name
else
echo "文件不存在"
fi
else
echo "目录不存在"
fi
文件权限
检测读(写/执行)权限时,文件所有者、所属组、其他任意拥有读(写/执行)权限都将返回true
测试选项 | 作用 |
---|---|
-r file | 判断file是否存在,且是否拥有读权限 |
-w file | 判断file是否存在,且是否拥有写权限 |
-x file | 判断file是否存在,且是否拥有执行权限 |
-u file | 判断file是否存在,且是否拥有SUID权限 |
-g file | 判断file是否存在,且是否拥有SGID权限 |
-k file | 判断file是否存在,且是否拥有SBit权限 |
两个文件比较
在比较文件之前,应该确认两个文件确实存在,或用脚本验证文件是否存在并作错误处理,否则比较操作可能会返回错误的结果。
测试选项 | 作用 |
---|---|
文件1 -nt 文件2 | 判断文件1的修改时间是否比文件2新 |
文件1 -ot 文件2 | 判断文件1的修改时间是否比文件2旧 |
文件1 -ef 文件2 | 判断文件1和文件2的i节点(Inode)是否一致,即判断是否为同一文件,常用于判断是否为硬链接 |
整数比较
bash shell只支持整数进行比较,这里的整数可以是变量,也可以是正值或负值常量
测试选项 | 作用 |
---|---|
整数1 -eq 整数2 | 判断整数1与整数2是否相等 |
整数1 -ne 整数2 | 判断整数1与整数2是否不相等 |
整数1 -gt 整数2 | 判断整数1是否大于整数2 |
整数1 -lt 整数2 | 判断整数1是否小于整数2 |
整数1 -ge 整数2 | 判断整数1是否大于等于整数2 |
整数1 -le 整数2 | 判断整数1是否小于等于整数2 |
字符串判断
注意!以下比较符号=、!=、\<、\> 前后都需要有一个空格
测试选项 | 作用 |
---|---|
-z str | 字符串str长度为0,则为true(字符串为空字符串,未定义的变量也视为空) |
-n str | 判断字符串str长度是否为非0(非空) |
str1 = str2 | 判断str1和str2是否相等 |
str1 !=s tr2 | 判断str1和str2是否不相等 |
str1 \< str2 | 判断str1是否比str2小(根据ASCII码) |
str1 \> str2 | 判断str1是否比str2大(根据ASCII码) |
在比较字符串是否比另外一个字符串小/大时,大于号和小于号需要进行转义,否则会当做重定向符号处理。在比较时,会依次判断字母在ASCII中的出现位置,因此大写字母会小于小写字母(sort命令使用的是系统的本地化语言设置中定义的排序顺序,小写字母出现顺序位于大写字母前,与之相反)
str1="Test"
str2="test"
if [ str1 \\< str2 ]
then
echo "Test小于test" #由于ASCII中T小于t,所以输出该语句
else
echo "Test大于test"
fi
多重判断
测试选项 | 作用 |
---|---|
判断1 -a 判断2 | 逻辑与,判断1和判断2都成立,结果为真 |
判断1 -o 判断2 | 逻辑或,判断1和判断2任意一个成立,结果为真 |
! 判断 | 结果取反 |
[ 判断1 ] && [ 判断2 ] | 判断1和判断2都成立,结果为真 |
[ 判断1 ] || [ 判断2 ] | 判断1和判断2任意一个成立,结果为真 |
a=10
[ -n "$a" -a "$a" -gt 20] && echo "true" || echo "false"
判断语句
if语句
其他语言中的if语句,会判断if语句后式子的求值结果,并根据结果的true或false值进行对应处理。但bash shell中的if语句有所不同,bash会执行if语句后的命令,如果该命令的退出状态码是0,则会执行then部分的代码体,如果该命令的退出状态码是其他值,则执行else语句(没有else则不执行),if语句的语法为:
userName=shiwivi
if grep $userName /etc/passwd
then
echo "找到该用户:"
ls -a /home/$userName/.b*
fi
此外,bash shell也支持if-then-else语句和if嵌套语句,但注意,嵌套的if语句中的else-if被简化为了elif
case命令
case语句类似于其他语言中的switch…case语句,用于匹配多个同类型的条件,其语法为:
case中的条件可以使用或符号同时设置多个条件,最后的星号会捕获所有与已有条件不匹配的值,相当于switch语句中的default
case $USER in
JayChou | EasonChan)
echo "歌手";;
KenThompson)
echo "程序员";;
*)
echo "未知用户";;
esac
循环语句
for命令
for语句遍历时,默认使用空格分割需要遍历的值,如果值本身包含空格,则需要使用双引号将值包裹,此时for语句不会将双引号作为值的一部分
此外,bash shell还支持一种C语言风格的for语句,但需要注意:
- 使用双括号
- 变量不以美元符$
- 迭代过程的算式不需要用expr命令格式for((初始值;条件;变量变化)) do 程序 done
while命令
until命令
until命令和while命令工作的方式相反,until命令要求你指定一个通常返回非零退出状态码的测试命令。只有测试命令的退出状态码不为0,bash shell才会执行循环中列出的命令。一旦测试命令返回了退出状态码0,循环就结束了。即:while命令在条件满足时执行,until命令在条件满足时退出。同样,until也像while一样允许多个测试命令,但只有最后一个命令有效
break终止循环
break的用法与其他语言类似,即可以终止当前的循环,但bash shell提供了一个更高级功能,可以通过break 值的方式跳出多重循环(如果不指定值,默认为1),如:在两层for语句嵌套中,break 2 可以直接停止外层for语句的循环
continue跳过循环
continue可以跳过本次循环,continue之后的命令将不再执行而开始下一次循环,与break类似,在多层循环中,bash shell中的continue提供跳过多层循环的功能,其语法为continue 值,值默认为1
处理循环的输出
在shell脚本中,可以在done命令后添加管道符或重定向符来处理循环的输出
字段分隔符
环境变量IFS,称为内部字段分隔符(internal field separator),用于定义bash shell内用作字段分隔的一系列字符。默认情况下,bash shell会将下列字符当作字段分隔符:
- 空格
- 制表符
- 换行符
如果bash shell在数据中看到了这些字符中的任意一个,它就会假定这表明了列表中一个新数据字段的开始。在处理可能含有空格的数据(比如文件名)时,这会非常麻烦
要解决这个问题,可以在shell脚本中临时更改IFS环境变量的值来限制被bash shell当作字段分隔符的字符。一般会在修改IFS值前保存原来的默认值,使用完后再恢复它
#!/bin/bash
# 提取/etc/passwd文件每个字段的内容
IFS.OLD=$IFS
IFS=$'\n'
for entry in $(cat /etc/passwd)
do
echo "当前用户整体信息:$entry"
IFS=:
for value in $entry
do
echo " $value"
done
done
(())双括号命令
双括号命令(( ))在shell中用于进行数学运算和条件判断,并提供了更灵活和方便的方式来处理数值计算和逻辑操作
数学运算
双括号命令除了基本的数学运算外,还支持以下运算,且双括号中还支持使用变量
符号 | 描述 |
---|---|
num++、++num | 前置/后置的自增 |
num–、–num | 前置/后置的自减 |
! | 逻辑取反 |
~ | 按位取反 |
** | 冥运算 |
<< | 左位移 |
>> | 右位移 |
& | 位布尔和 |
| | 位布尔或 |
&& | 逻辑和 |
|| | 逻辑或 |
逻辑判断
双括号命令也常用于if-then等语句的逻辑判断,双括号命令支持<、>、<=、>=、==、!=运算符
[[]]双方括号命令
双方括号命令提供了针对字符串比较的高级特性,双方括号命令里的表达式支持模式匹配,可以定义正则表达式来匹配字符串,因此该命令在比较字符串时功能很强大:
- 支持<、>、==、!=、-gt等比较符
- 支持!、&&、||等逻辑组合
- 支持-f等文件测试,如:检测文件是否存在,是否为目录等
- 支持使用*、?等通配符,并使用=~结合正则表达式进行匹配
脚本控制
脚本在执行时,终端可以通过kill命令或Ctrl+C等方式发起信号进行进程的暂停、终止操作,这些信号中有一些是可以通过脚本捕获并作相应处理的(如:在被终止时将数据写入文件做好保存,或者无视终止命令继续执行脚本)
捕获信号
- 信号可以是信号值或信号名,但需要是可被捕获的Linux信号
- trap命令也会捕获以下特殊的伪信号
信号名(信号值) | 说明 |
---|---|
EXIT(0) | 会在脚本退出时执行 |
DEBUG | 会在每个命令后都执行一次 |
ERR | 当某个命令以非零状态退出时,执行一次(非零状态来自if,while,until语句时不会执行) |
修改信号捕获
如果需要在脚本某个阶段修改前面设置的信号捕获语句,只需要重新使用一次trap命令即可
删除信号捕获
如果脚本在某个阶段不再需要进行信号捕获了,则可以移除信号捕获,语法为trap -- 信号
函数
创建与调用函数
shell脚本中创建函数的方法有两种:
注意,使用第一种方式定义函数时,函数名和{之间必须有一个空格,否则{可能会被当作函数名的一部分从而报错。调用函数与其他语言类似,只是不需要括号,直接执行函数名即可,同样,shell中的函数需要在调用前的代码中创建,否则抛出command not found错误。函数可以同名,后面的函数会覆盖前面的函数,函数调用方法为:
函数名 参数1 参数2 …
变量的作用域
在shell中,变量也分为全局变量和局部变量
全局变量
与其他语言有所不同,shell中的全局变量作用域为定义之后的整个脚本代码,即使是函数内部定义的变量,在函数外依旧有效。若无特殊声明,脚本中定义的变量默认为全局变量。
局部变量
shell中的局部变量需要使用local关键字声明。局部变量如果在函数内部声明,则其作用域只在函数中,如果函数外部存在一个同名变量,二者互不干扰
函数返回值与输出
默认退出状态码
bash shell会把函数当作一个小型脚本,运行结束时会返回一个退出状态码。与其他命令一样,在函数执行结束后,可以用shell中的预定义变量$?来确定函数的退出状态码。默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码。注意,函数的默认退出状态码只看最后一条命令的退出状态,其他命令执行正确与否无法获悉,因此该方法应用场景有限。此外,就像脚本的返回值可以使用exit修改一样,函数的返回值可以使用return语句修改
return命令修改状态码
shell支持使用return命令来返回指定的退出状态码,状态码支持0-255之间的整数值,该状态码同样可以使用$?获得。与exit命令指定的脚本退出状态码类似,如果return命令指定的退出状态码大于255,会输出该值被256取模后的结果
函数输出
shell支持将命令的输出赋值给变量,同样也支持将函数的输出值赋值给变量,通过该方法我们可以将任何类型的数据作为函数输出,然后通过反引号``或$( )调用函数,并将获取的函数输出值保存到变量中,该方法相比于return语句功能更加强大。但注意,通过该方法从函数中输出的值并不会作为函数的退出状态码,也无法被$?捕获
函数传参
由于bash shell会将函数当作小型脚本来对待,因此可以像命令传参一样给函数传入参数,并使用$1、$2等位置变量来读取传入函数的参数。
由于函数内部也使用$1、$2等变量来引用函数的参数,因此脚本中的$1、$2等位置变量无法在函数内部使用,即函数中无法直接通过位置变量来读取脚本传入的参数。要在函数中使用脚本传入的参数,则需要在调用函数时手动将它们传过去
传递数组参数
shell中,给函数传递数组,不能像C语言一样直接传递数组名(数组首地址),这样只会传递第一个值。需要使用${数组名[@]}将所有数组元素传递过去,然后在函数中使用$@获取所有的数组元素
理论上通过${数组名[*]}也可以传递数组元素,或用$*接收,但$*会将所有数组元素视为一个整体,在使用for-in等语句遍历时可能出现问题
返回数组
从函数返回数组时,也不能直接返回数组地址,而是需要通过echo语句输出${数组名[@]}(数组元素),然后通过命令替换等方式在脚本中调用函数,并在命令替换时在外层加上( )将其转换为一个数组
#! /bin/bash
function test {
#echo "函数开始执行----------"
local newArray
local index=$[$#-1] #获取函数接收的参数个数并-1
for value in $@
do
newArray[$index]=$value
index=$[${index}-1]
done
echo ${newArray[@]}
}
array=(10 20 30 40 50)
result=($(test ${array[@]}))
echo "返回值result $result"
注意:
1.bash在使用命令替换时,会将捕获的结果作为一个字符串返回。上述代码中,命令替换外层加了个小括号(),在没有括号的情况下,result存储的是一个字符串,添加小括号后,result存储的结果将被转换为数组
2. bash在使用命令替换时会捕获所有命令的标准输出,如果在test函数中有除了输出数组元素以外的语句,也会被捕获,并被赋值给result。由于shell支持不同类型的数据都作为同一数组的元素,因此所有输出信息都将作为数组元素,这可能会对之后使用该数组造成影响,因此,返回数组的函数,尽量不要输出多余的信息。如:如果test函数第一行被注释的echo语句正常输出,则该语句信息将作为result的数组元素
3. 命令替换会捕获所有标准输出语句,且不显示到屏幕上,因此执行result=($(test ${array[@]}))语句时,虽然函数被执行了,但不会有任何屏幕输出,这些输出信息会在使用echo输出result的结果时,才会被输出到屏幕上
创建库
如果需要在多个脚本中频繁使用同一段代码,可以将这段代码封装为函数库,然后在多个脚本中调用该库文件。但在shell中,如果直接调用或执行该文件,shell会创建一个新的shell并在其中执行该库文件中的代码,而函数的作用域仅限于定义它的shell会话,因此在其他脚本中将无法调用这些函数。
shell提供了source命令用于解决该问题,source命令会在当前shell上下文中执行命令,这样脚本就可以使用库中的函数了。source命令有个快捷别名,称为点操作符,命令也可以用.号代替,语法格式为:
. 库文件路径
#!/bin/bash
function add {
if [ $# -ne 2 ]
then
echo "参数输入错误"
else
echo $[$1+$2]
fi
}
function sub {
if [ $# -ne 2 ]
then
echo "参数输入错误"
else
echo $[$1-$2]
fi
}
文件路径:/myShell/test.sh
#!/bin/bash
#注意:无论使用.号还是source,命令后都需要添加一个空格,路径用相对路径或绝对路径均可
. ./myfunc.sh
source /myShell/myfunc.sh
echo "求和运算:$(add 10 20)"
echo "求差运算:$(sub 20 10)"
在命令行中使用函数
函数也可以在命令行中创建和调用,命令行中创建的函数可以在整个系统中被调用,直到当前shell退出。注意,如果命名时函数名和shell的内建命令同名,则内建命令的功能会被函数功能覆盖!
在命令行中定义函数的方法有两种:
以单行方式定义,需要在每个命令后都加上分号;,以便shell区分命令的起止
定义: function test { pwd; ls; echo $[ $1 / $2 ]; } 调用: test 100 50以多行方式定义,定义时,bash会使用次提示符提示输入更多命令,该方式不需要在命令后添加分号,直接回车即可,最后输入花括号}告知shell函数输入完毕
定义: function add { > echo $[ $1 + $2 ] > } 调用: add 2 5
在.bashrc文件中定义函数
在命令行中定义的函数,在shell退出时函数也会随之失效,如果希望某个函数在shell启动时也随之被加载进内存,方便在全局调用,则可以将该函数写入$HOME/.bashrc文件,一般情况下,bash以登录交互式shell启动,或以非登录交互式shell启动都会载入该文件。绝大部分Linux发行版都已经在.bashrc文件中定义了一些函数,注意小心修改,将新添加的函数写于文件末尾即可。也可以将新写的函数写于一个单独的文件中,然后在.bashrc文件中使用source命令载入。
通过该方式从.bashrc文件载入的函数,可以在命令行中全局调用,但无法在脚本中使用,这是由于脚本执行时启动的非交互式shell不会读取.bashrc文件,函数也不会从父shell中继承。如果需要某个函数在脚本中也可以被直接调用,可以用以下方法:
- 在脚本中使用source命令引入.bashrc文件
- 如果不想引入.bashrc文件,则在.bashrc文件中定义完函数后,可以用export -f命令将函数导出为环境变量,这样执行脚本时创建的非交互式shell将能从父shell继承该函数,然后在脚本中直接调用
function myTest1 {
echo "函数myTest1被调用"
}
function myTest2 {
echo "函数test2被调用"
}
export -f myTest1 #导出其中一个函数
在文件/root/.bashrc末尾引入myfunc文件
if [ -f /root/myfunc ];then
. /root/myfunc
fi
/myShell/test.sh
#!/bin/bash
echo "脚本开始执行"
myTest1 #被导出的函数可以在非交互式shell执行的脚本中直接使用
myTest2 #未被导出为环境变量的函数则提示command not found
脚本合集
判断发行版
#!/bin/bash
os_name="undefined"
os_version="0.0"
# 检查 /etc/os-release 文件是否存在
if [ -f /etc/os-release ]; then
. /etc/os-release
os_name=$NAME
os_version=$VERSION
echo "发行版: $NAME"
echo "版本: $VERSION"
else
# 作为替代,检查其他可能的文件
if [ -f /etc/lsb-release ]; then
. /etc/lsb-release
os_name=$DISTRIB_ID
os_version=$DISTRIB_RELEASE
echo "发行版: $DISTRIB_ID"
echo "版本: $DISTRIB_RELEASE"
elif [ -f /etc/debian_version ]; then
os_name="Debian"
echo "发行版:Debian"
echo "版本未知,以下为/etc/debian_version文件内容:"
cat /etc/debian_version
elif [ -f /etc/centos-release ]; then
os_name="CentOS"
echo "发行版:CentOS"
cat /etc/centos-release
elif [ -f /etc/redhat-release ]; then
os_name="redhat"
echo "发行版:redhat或centOS"
else
echo "未知发行版"
fi
fi
判断系统架构
#!/bin/bash
if [[ "$(uname)" == "Linux" ]];then
case "$(uname -m)" in
'i386'|'i686')
machine='32';;
'amd64'|'x86_64')
machine='64';;
'armv5tel')
machine='arm32-v5';;
'armv6l')
machine='arm32-v6'
grep Features /proc/cpuinfo | grep -qw 'vfp' || machine='arm32-v5';;
'armv7' | 'armv7l')
machine='arm32-v7a'
grep Features /proc/cpuinfo | grep -qw 'vfp' || machine='arm32-v5';;
'armv8' | 'aarch64')
machine='arm64-v8a';;
'mips')
machine='mips32';;
'mipsle')
machine='mips32le';;
'mips64')
machine='mips64';;
'mips64le')
machine='mips64le';;
'ppc64')
machine='ppc64';;
'ppc64le')
machine='ppc64le';;
'riscv64')
machine='riscv64';;
's390x')
machine='s390x';;
*)
echo "error: The architecture is not supported."
exit 1;;
esac
fi
echo $machine