Shell学习笔记,都是干货(坑)...

182 阅读4分钟

Shell命令无处不在;无论是开发cli工具、学习linux、尝试CI/CD等,都需要一定的Shell基础。但开始学习Shell时可谓一言难尽,它跟Js、Java等常见语言的语法差别太大了......

如何学习?

很多网站都能找到shell的详细教程,但是无论学什么语言都需要上手实际操作才能最快掌握。

第一次学习槽点可能有点多:各种括号,各种运算符,各种限制,各种不兼容 ... ...,文章最后会总结一下,建议经常复习一下。

前情提要

如果对shell一无所知,还是建议一下“菜鸟教程”。首先,必须掌握的几个知识点:echotest[][[]](())$?

    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 之间的内容作为输入。

文件描述符:

  1. 标准输入(Standard Input):用于接收输入的文件描述符为 0,通常表示为 stdin 或文件描述符符号 0。标准输入默认为终端(键盘)。

  2. 标准输出(Standard Output):用于输出结果的文件描述符为 1,通常表示为 stdout 或文件描述符符号 1。标准输出默认为终端(屏幕)。

  3. 标准错误输出(Standard Error):用于输出错误消息的文件描述符为 2,通常表示为 stderr 或文件描述符符号 2。标准错误输出默认为终端(屏幕)。

    例如:2>&1,指将标准输出、标准错误指定为同一输出路径;

输出重定向示例

输入重定向示例

自定义开始结束符号示例

<<符号右侧的字符串即为开始标记,该标记再次出现代表输入结束;期间回车只是换行,两次标记之间的内容作为标准输入流的输入内容。

7. 文件包含

类似于jsimport "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 > 22 > 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 - 历史命令记录的文件路径。
MAIL - 用户邮件存储的路径。
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的选项。
$_ - 上一个命令的最后一个参数。

5. 其他