Shell命令无处不在;无论是开发cli工具、学习linux、尝试CI/CD等,都需要一定的Shell基础。但开始学习Shell时可谓一言难尽,它跟Js、Java等常见语言的语法差别太大了......
如何学习?
很多网站都能找到shell的详细教程,但是无论学什么语言都需要上手实际操作才能最快掌握。
第一次学习槽点可能有点多:各种括号,各种运算符,各种限制,各种不兼容 ... ...,文章最后会总结一下,建议经常复习一下。
前情提要
如果对shell一无所知,还是建议一下“菜鸟教程”。首先,必须掌握的几个知识点:echo、test、[]、[[]]、(())、$?
1. echo 类似js中的console.log,用于输出字符串到终端。
echo后面的字符串可以不加单、双引号,但是需要当心特殊字符的转义。例如,重定向符号>。
这里> 被解析成重定向符号,加""、''可以避免这种不必要的错误发生。
2. test 用于检查某个条件是否成立,但是它在终端不会输出任何东西;test 条件句是否通过的标准是能否正常结束(命令执行的状态码:$?为0为正常结束,否则为异常)。
注意:
$?代表上一条命令的状态码,也就是执行状况:0代表正常执行,其他值代表异常退出。test中,条件句的$? == 0代表真,其他数值均为假。
**test**的等价语法:**[]**, 上面的例子可以简化为下面的形式:
[]与test相同,只支持基本的条件判断和文件属性测试,例如字符串比较、数值比较、文件存在判断等。以下是部分重要区分点:
=或==是字符串相等比较符号,两侧要保留空格(=用作赋值变量时不能保留空格);- 这里的
>与<为重定向符号,不是比较大小; -eq,-lt,-gt...用于数值比较;- 不支持
&&与||,而是要用-a与-o。
test的增强语法:**[[]]**,是Bash Shell的扩展语法,不是所有的Shell都支持。
较[]来说功能更强,[[]]增加了模式匹配、逻辑运算符和高级的字符串操作,例如:正则表达式、逻辑与 &&、逻辑或 ||、字符串替换,大于>,小于<等。
**test**的兼职语法:**(())**:(())不是专门用于test的语法,而是为方便数学运算的语法,能顺便用于数值比较(比较结果:1为真,0为假;与$?的要区分开)。**注意:**``**不能用于字符串比较以及文件测试**``。
3. shell设计之初是为了文件管理、文本处理和系统管理;算数运算不是其主要用途,因此,凡是涉及数值运算的都有专用的语法或命令;
Shell的版本
shell有很多不同的版本(不能保证同一个语法在不同的环境下表现一致):
Bash
Zsh
Fish
Ksh
PowerShell
Ash
... ...
不同版本的 shell 在功能和语法上可能会有一些差异,但它们通常都提供了基本的命令解析和脚本编程功能。可以根据自己的需求选择合适的 shell(例如:CentOs默认bash)。
语法
1. 变量
1.1 变量的定义与使用
命名规则跟js基本一样
(只包含字母、数字和下划线,不能以数字开头,避免关键字......)。
变量定义无需特别声明;引用时只需加$符,不加$可能会当做普通字符串处理,也可能会被当做函数调用。
# 定义变量(增)
> var=123 # 直接赋值,不需要var/let等类似声明语法
# 删除变量(删)
> unset var
# 可以直接覆盖(改)
> var=456
# 使用变量(查)
> echo $var; # 变量名前加$即为引用,输出“123”; 也可以是echo ${var},效果相同;
> echo $variable; # 未定义的变量默认值为空字符串,输出空字符串 ""
> echo ${var}456; # 输出"123456";这里必须要加{},不然就相当于取值${var456};
与js变量的不同点
# 1. 变量赋值时,等号两侧不能有空格;
> var="123" # 正确
> variable = "123" # 错误,等号两侧不能有空格;有空格的是后面字符串比较的语法;
# 2. 单双引号
> echo "$var"; # 这里双引号可以不加,输出“123”;
> echo '$var'; # 单引号为保留当前字符串的字面量,直接输出"$var";
$var
1.2 基础变量类型
shell是弱类型语言,变量的类型在赋值时自动推断,可以根据需要在不同类型之间进行转换。
在shell中,变量分为以下几种类型:
- 字符串类型:
Shell中的默认变量类型。可以不加引号(注意:空格会默认为分隔符),也可以加单、双引号。
-
整数类型:默认是字符串,但是进行算术运算时,会根据上下文将其视为数值。
echo $((1+2)) # 这里进行数值运算,1、2会推断为数字; 3
**
注意:shell没有浮点类型,布尔类型变量,整数运算得到还是整数(向下取整);
**
- 数组类型:使用圆括号来定义数组,空格为分隔符。数组下标一般从0开始
(我的macOS上zsh从1开始)
-
关联数组(
类似js中的对象)这里需要特别声明,-A 选项就是用于声明一个关联数组。
declare -A map=(["a"]="A" ["b"]="B" ["c"]="C")
声明与赋值分开
declare -A map; map[a]='A' map[b]='B' map[c]='C'
**
**
- 环境变量类型:由操作系统或用户设置的特殊变量,用于存储环境相关的信息,如路径、配置等(后面列举)。
2. 函数
函数跟js的函数结构很像,不同点:
-
返回值必须是整数,或者不返回;返回值会被当做命令执行状态码
$?(0为执行成功,其他为异常) -
function关键字不是必需的; -
没有形参,入参要用
$@等特殊符号获取;[ function ] funname () # function 关键字可以不要 { 代码;
[return int;] # 返回值必须是整数;0代表函数执行状态正常;不返回默认0; }
2.1 参数传递
函数或者.sh文件执行时,命令后面可以输入参数,例如:执行test.sh文件;获取传参则需要通过一些特殊符号。
# 执行test.sh
> ./test.sh
# 执行并传参,参数以空格分隔
> ./test.sh 1 2 3
3. 运算符
3.1 算术运算符(没啥特别)
+加法-减法*乘法/除法%取余=赋值==相等。用于比较两个数字!=不等。用于比较两个数字
shell默认都是字符串,直接写echo 1+2是不行的,如果想要进行算术运算需要特定的语法,例如:(()),``或者$()等。
3.2 关系运算符
-eq相等(equal)-ne不相等(not equal)-gt大于(greater than)-lt小于(less than)-ge大于等于(greater equal)-le小于等于(less equal)
>、<在shell中分别为输出、输入重定向符号,所以1 > 2的意思是命令1的输出值重定向到文件“2”中;而1 < 2则表示文件“2”的内容输入到指令“1”中。
在 “
if [ 1 > 2 ]...”中,[ 1 > 2 ]为真。这是因为这里[]实际判断的是:“将指令1的输出重定向到文件2中”。
因此,比较大小时> 、<只能用于特定的语法中,例如:if [[ 1 > 2 ]]。
[]与[[]]中 == 与 != 是以字符串形式进行比较的;-eq与-ne则是以数字形式进行比较,如果内容不是数字则报错。
3.3 布尔运算符
!非-o或-a与
注意:用于[]中,不能用在[[]]。
3.4 逻辑运算符
&&逻辑与,不能用于[]中,但可以用于[[]]中;也可以用来连接两个命令顺序执行,例如:1==1&&echo true,跟js中用法相同;||逻辑或,不能用于[]中,但可以用于[[]]中;
3.5 字符串运算
=或==相等判断!=不等-z字符串长度是否为0-n字符串长度是否不为0=~用于正则匹配,只能用于[[]]中
3.6 文件测试运算符
-b检测文件是否是块设备文件,如果是,则为真。-c检测文件是否是字符设备文件,如果是,则为真。-d检测文件是否是目录,如果是,则为真。-f检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则为真。-g检测文件是否设置了 SGID 位,如果是,则为真。-k检测文件是否设置了粘着位(Sticky Bit),如果是,则为真。-p检测文件是否是有名管道,如果是,则为真。-u检测文件是否设置了 SUID 位,如果是,则为真。-r检测文件是否可读,如果是,则为真。-w检测文件是否可写,如果是,则为真。-x检测文件是否可执行,如果是,则为真。-s检测文件是否为空(文件大小是否大于0),不为空为真。-e检测文件(包括目录)是否存在,如果是,则为真。
可以用于[]与[[]]。
4. Shell 流程控制
4.1 if
> if 条件句1 # 条件句通常为test、[]、(())、[[]]
> then
> 命令1
> elif 条件句2
> then
> 命令2
> else
> 命令n
> fi
4.2 for
# 形式1:for var in {0..n}; 其中:n为整数,
# 形式2:for ((i=0;i<n;i++)); 其中:n为整数
# 形式3:for var in item1 item2 itemN;
> for var in item1 item2 ... itemN
> do # 开始标识
> commands;
> done # 结束标识
4.3 while
> while condition
> do
> command;
> done
4.4 无限循环
> while :
> do
> command;
> done
# 或者
> for (( ; ; ))
> do
> commands;
> done
4.5 until
# condition 跟 while 相反;为假时循环,为真时停止。
> until condition
> do
> command;
> done
4.6 case ... esac
两个分号 ;; 表示 break;
4.7 跳出循环
- break;
- continue;
跟js中的一样,不再赘述。
6. 输入输出重定向
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 之间的内容作为输入。
文件描述符:
-
标准输入(Standard Input):用于接收输入的文件描述符为 0,通常表示为
stdin或文件描述符符号0。标准输入默认为终端(键盘)。 -
标准输出(Standard Output):用于输出结果的文件描述符为 1,通常表示为
stdout或文件描述符符号1。标准输出默认为终端(屏幕)。 -
标准错误输出(Standard Error):用于输出错误消息的文件描述符为 2,通常表示为
stderr或文件描述符符号2。标准错误输出默认为终端(屏幕)。例如:2>&1,指将标准输出、标准错误指定为同一输出路径;
输出重定向示例
输入重定向示例
自定义开始结束符号示例
<<符号右侧的字符串即为开始标记,该标记再次出现代表输入结束;期间回车只是换行,两次标记之间的内容作为标准输入流的输入内容。
7. 文件包含
类似于js的import "file/path"语法;导入其他文件中定义的全局变量和函数等。
语法:.或者source,注意保留空格。
总结
1. shell中各种括号(),[],(()),[[]],{}等的作用
1.1 ()的常用方式
1. 子shell ——
()中的shell处于独立的环境,不会影响父shell
2. 命令替换 ——
$()执行括号内的命令,并将标准输出作为字符串返回。
从例子中可以看到,$()中有多个输出时,会合为一行一起输出。
3. 数组
> arr=(1 2 3)
1.2 []的常用方式
1. test命令 —— 参考文章开头“特别注意”
常见错误
- 比较运算符两侧要有空格, 条件句与
[]之间也要有空格。
if的条件句是否真,是根据条件句的退出状态:$?来判断的(0:真,1:假)。
这个例子里,1 > 2与2 > 1都是真;因为> 和 <是**重定向输入和输出的特殊符号。**而不是“大于“、”小于”号!
例句1中,1 > 2的含义是:“将命令**1**的输出重定向到名为 “2” 的文件中。”
例句2中,1 < 2则是代表将**文件“2”**的内容输入到命令1中。
2. 基本算数运算 —— 已过时,现在可以使用
$(())
> var=$[1+2]
> echo $var
3. 正则
4. 数组下标
例如:> $arr[0] # 数组取值
1.3 **(())**的常用方式
1. 算数运算 —— 支持
>,<,>=,<=,+=,++...等常见的运算符,以及赋值操作
(())中的变量引用无需加$符号:
注意:(())中多语句用,而不是;分隔,最后一条语句决定整个 (()) 构造的退出状态$?以及返回值。
$(())会将最后一条指令的值“return”出来:
**2. 条件判断 —— 只能数值比较。**不能进行字符串比较,以及文件校验等(不能取代
test命令);
1.4 **[[]]**的常用方式
1. 条件判断 —— 参考本文开头对test的介绍
[[]]与[]或者test的区别:
-
>、<等字符在[]中会被认为成重定向,而在[[]]中是比较符号; -
&&、||、<和>能够被[[]]用作判断符号,但不能用在[]中,会报错。例如:if [[ a != 1 && a != 2 ]]
如果使用[]则为:
if [ $a != 1] && [ $a != 2 ]
或者
if [ $a != 1 -a $a != 2 ]
1.5 **{}**的常用方式
1. 字符扩展
echo {a,b}.txt输出a.txt b.txt。与echo [a-b].txt区别在于,后者只输出存在的文件名(参考上面[]用法中的例子),而前者不管是不是存在此文件,永远都会输出a.txt b.txt,因为{}的字符扩展和是不是文件名没有关系。-
echo {a..c}.txt输出a.txt b.txt c.txt。 - 也可用于
for循环
2. 特殊替换结构 —— 4种特殊的替换结构
-
${var:-string}表示若变量var不为空,则等于var的值,否则等于string的值 -
${var:=string}表示若变量var不为空,则等于var的值,否则等于string的值,并将"string"赋值给var -
${var:+string}的规则和上面的相反,表示若变量var不为空,则等于string的值,否则等于var的值(即空值)。 -
${var:?string}表示若变量var不为空,则等于var的值,否则把string输出到标准错误中,并从脚本中退出。
3. 模式匹配截断
${var%pattern}表示看var是否以模式pattern结尾,如果是,就把var中的内容去掉右边最短的匹配模式。${var%%pattern}表示看var是否以模式pattern结尾,如果是,就把var中的内容去掉右边最长的匹配模式。${var#pattern}表示看var是否以模式pattern开始,如果是,就把var中的内容去掉左边最短的匹配模式。${var##pattern}表示看var是否以模式pattern开始,如果是,就把var中的内容去掉左边最长的匹配模式。
4. 字符串****截取
${var:num}表示提取var中第num个字符到末尾的所有字符。若num为正数,从左边0处开始;若num为负数,从右边开始提取字串,但必须使用在冒号后面加空格或一个数字或整个num加上括号,如${var: -2}、${var:1-3}或${var:(-2)}。${var:num1:num2}表示从var中第num1个位置开始提取长度为num2的子串。num1从0开始。
2. '',"",``的用途
''中所有的特殊字符都会被禁用,包括变量扩展和命令替换。单引号中的内容会被视为纯文本。例如,echo 'Hello $NAME'会直接输出Hello $NAME,而不会替换变量。""中大部分特殊字符会被保留,但是变量扩展和命令替换会被执行。例如,echo "Hello $NAME"会替换变量$NAME的值,并输出Hello加上该变量的值。``等同于$()用于执行命令替换,它会将命令的输出结果插入到命令替换处;例如,echo `date`会执行date命令,并将其输出结果作为参数传递给echo命令
3. 以下是一些常见的环境变量:
HOME- 当前用户的主目录的路径。
PATH- 一个冒号分隔的目录列表。
PWD- 当前工作目录的完整路径。
USER- 当前登录的用户名。
SHELL- 当前用户登录Shell的完整路径。
LANG- 当前设置的语言和地区名称。
TERM- 当前使用的终端类型。
EDITOR- 用户指定的默认文本编辑器。
PS1- 主提示符字符串,控制Shell提示符的外观。
PS2- 次提示符字符串,用于命令行续行。
HOSTNAME- 系统的主机名。
UID- 当前用户的用户ID。
EUID- 当前用户的有效用户ID。
GROUPS- 当前用户所属组的数组。
HISTSIZE- 历史命令记录的大小。
HISTFILE- 历史命令记录的文件路径。
LOGNAME- 当前用户的登录名。
OLDPWD- 上一次工作目录的路径。
PPID- 父进程的进程ID。
RANDOM- 返回一个随机数。
SECONDS- 脚本运行的秒数。
OSTYPE- 操作系统类型。
MACHTYPE- 机器类型,通常表示CPU和操作系统的组合。
DISPLAY- X Window System显示的名称。
LD_LIBRARY_PATH- 在此路径列表中搜索动态链接库的目录。
TZ- 时区设置。
IFS- 内部字段分隔符,默认空格、制表符和换行符。
4. 下面是一些常用的特殊变量:
$0- 当前脚本的名称或Shell中执行的命令。
$1到$9- 对应于脚本或函数的第1到第9个位置参数。
$#- 传递给脚本或函数的位置参数的个数。
$@- 所有位置参数的列表,当被双引号包围时,每个参数都是一个独立的字符串。
$*- 所有位置参数的列表,当被双引号包围时,所有参数被视为一个单一的字符串。
$?- 上一个执行的命令的退出状态码。
$$- 当前Shell进程的进程ID(PID)。
$!- 上一个在后台执行的命令的PID。
$-- 当前Shell的选项。
$_- 上一个命令的最后一个参数。