Shell 函数的知识与实践

文章目录

    • Shell 函数的知识与实践
      • Shell 函数介绍
      • Shell 函数的语法
      • Shell 函数的执行
        • 1. 不带参数的函数执行
        • 2. 带参数的函数执行
      • Shell 函数的基础实践
        • 示例 1:简单的 hello 函数(验证 “先定义后调用”)
        • 示例 2:调用外部文件中的函数
        • 示例 3:带参数的函数(根据输入输出彩色文字)
        • 示例 4:函数参数与脚本参数的关系
      • 企业级 URL 检测脚本
      • 函数的递归调用(函数调用自身)
        • 示例 1:递归求 1+2+...+n 的和
        • 示例 2:递归求 n 的阶乘(1*2*...*n)
        • 示例 3:fork 炸弹(危险!仅作原理了解)
      • 总结

Shell 函数介绍

在学习 Shell 函数之前,我们先回忆一下 Linux 中的 alias(别名)功能。比如我们常用 ll 代替 ls -l --color=auto,就是通过别名实现的:

# 直接执行详细列表命令,显示/home目录内容(--color=auto自动为文件着色)
[bq@controller shell 14:15:42]$ ls -l --color=auto /home
总用量 0
drwx------. 5 bq bq 124 823 14:15 bq# 创建别名:用ll代替ls -l --color=auto
[bq@shell ~]$ alias ll='ls -l --color=auto'# 用别名执行,效果和原命令完全一致
[bq@controller shell 14:25:23]$ ll /home/
总用量 0
drwx------. 5 bq bq 124 823 14:15 bq

Shell 函数和别名类似,都能简化操作,但功能更强大。简单来说,函数就是把一段重复使用的代码 “打包”,起一个名字。之后想使用这段代码时,直接调用这个名字就行。如果需要修改这段代码,只改 “打包” 好的那一份,所有调用的地方都会同步更新。我们也可以把函数存到单独的文件里,需要时再加载使用。

使用 Shell 函数的好处:

  • 减少重复代码:一段代码多次用,定义成函数后不用反复写,提高开发效率。
  • 增强可读性:用有意义的函数名代替一堆命令,代码更易懂、易维护。
  • 实现模块化:把功能拆分成函数,让脚本更通用,方便移植到其他场景。

小贴士:Linux 系统中的近 2000 个命令,其实都可以理解为 Shell 的 “内置函数”,可见函数在 Shell 中的重要性。

Shell 函数的语法

Shell 函数有多种定义方式,核心都是 “函数名 + 代码块”,以下是常见格式:

标准写法

function 函数名 () {指令...  # 函数要执行的代码return n  # 可选,返回一个状态值(0-255,0表示成功)
}

简化写法 1:省略小括号 ()

function 函数名 {  # 去掉了函数名后的(),其他和标准写法一致指令...return n
}

简化写法 2:省略 function 关键字

函数名 () {  # 去掉了function,保留()和代码块指令...return n
}

三种写法功能完全一样,实际使用中可以根据习惯选择。

Shell 函数的执行

Shell 函数分为 “不带参数” 和 “带参数” 两种,执行方式略有不同,下面详细说明:

1. 不带参数的函数执行

直接输入函数名即可(注意:函数名后不要加小括号),格式:

函数名  # 直接调用函数

重要说明(必看)

  • 调用函数时,不要加 function 关键字,也不要加小括号(比如定义了 hello(),调用时直接写 hello)。
  • 函数必须 “先定义,后调用”:如果在调用之后才定义函数,Shell 会提示 “命令未找到”。
  • 执行优先级:Shell 执行程序的顺序是「系统别名 → 函数 → 系统命令 → 可执行文件」。比如如果有一个别名 ll、一个函数 ll 和系统命令 ll,执行 ll 时会先执行别名。
  • 变量共享:函数和调用它的脚本会共用变量,但可以用 local 定义 “局部变量”(仅在函数内有效,函数结束后消失)。
  • returnexit 的区别:return 是退出函数,返回一个状态值给调用者;exit 是直接退出整个脚本,返回状态值给当前 Shell。
  • 外部函数加载:如果函数存放在单独的文件中,需要用 source 文件名. 文件名 加载后才能调用(source. 作用相同)。
2. 带参数的函数执行

调用时在函数名后直接跟参数,格式:

函数名 参数1 参数2  # 参数之间用空格分隔

参数说明

  • 函数内部用 “位置参数” 接收参数:$1 表示第 1 个参数,$2 表示第 2 个参数,$# 表示参数总数,$*$@ 表示所有参数,$? 表示上一条命令的返回值。
  • 临时覆盖父脚本参数:函数执行时,父脚本的参数会被函数参数临时 “掩盖”,函数执行完后,父脚本参数恢复正常。
  • $0 特殊:始终表示父脚本的文件名,不会被函数参数影响。

Shell 函数的基础实践

示例 1:简单的 hello 函数(验证 “先定义后调用”)

实验流程

  1. 编写脚本,先定义 hello 函数(输出 “Hello World !”),再调用函数,观察执行结果。
  2. 编写另一个脚本,先调用 hello 函数,再定义函数,观察错误结果。

详细步骤

# 脚本1:先定义函数,再调用
[bq@shell ~]$ cat fun1.sh 
#!/bin/bash
# 定义hello函数:输出Hello World !
function hello () {echo "Hello World !"
}
# 调用hello函数(直接写函数名)
hello# 执行脚本,成功输出结果
[bq@shell ~]$ bash fun1.sh 
Hello World !# 脚本2:先调用函数,再定义(错误示范)
[bq@shell ~]$ cat fun2.sh 
#!/bin/bash
# 先调用hello函数(此时函数还未定义)
hello
# 后定义hello函数
function hello () {echo "Hello World !"
}# 执行脚本,提示“hello: 命令未找到”(因为调用时函数还不存在)
[bq@shell ~]$ bash fun2.sh
fun2.sh: line 2: hello: command not found

结论:函数必须先定义,后调用,否则会报错。

示例 2:调用外部文件中的函数

实验流程

  1. 创建一个存放函数的文件 mylib(定义 hello 函数)。
  2. 编写脚本 fun3.sh,通过 source 加载 mylib 中的函数,然后调用。
  3. 执行脚本,验证能否成功调用外部函数。

详细步骤

# 1. 创建存放函数的文件mylib
[bq@shell ~]$ cat >> mylib << 'EOF'  # 用here document写入内容,'EOF'表示内容中的变量不解析
function hello () {echo "Hello World !"  # 函数功能:输出Hello World !
}
EOF# 2. 编写调用脚本fun3.sh
[bq@shell ~]$ cat fun3.sh 
#!/bin/bash
# 检查mylib文件是否存在且可读(-r选项:判断文件存在且可读)
if [ -r mylib ];thensource mylib  # 加载mylib文件中的函数(source等同于. mylib)
elseecho "mylib文件不存在或不可读"  # 如果文件不存在,提示错误exit 1  # 退出脚本,返回状态码1(表示执行失败)
fi
hello  # 调用加载的hello函数# 3. 执行脚本,成功调用外部函数
[bq@shell ~]$ bash fun3.sh 
Hello World !

结论:通过 source 可以加载外部文件中的函数,实现代码复用。

示例 3:带参数的函数(根据输入输出彩色文字)

实验流程

  1. 定义 print 函数,接收参数 PASS/FAIL/DONE,分别输出绿色、红色、紫色文字;其他参数提示用法。
  2. 脚本中通过 read 命令获取用户输入,传给 print 函数。
  3. 测试不同输入(PASS/FAIL/DONE/ 其他文字),观察输出结果。

详细步骤

# 编写脚本fun4.sh
[bq@shell ~]$ cat fun4.sh 
#!/bin/bash
# 定义print函数:根据参数输出彩色文字
function print () {# 判断参数是否为PASS:输出绿色文字(\033[1;32m是绿色高亮,\033[0;39m恢复默认颜色)if [ "$1" == "PASS" ];thenecho -e '\033[1;32mPASS\033[0;39m'  # -e选项:解析转义字符(如颜色控制符)# 判断参数是否为FAIL:输出红色文字elif [ "$1" == "FAIL" ];thenecho -e '\033[1;31mFAIL\033[0;39m'# 判断参数是否为DONE:输出紫色文字elif [ "$1" == "DONE" ];thenecho -e '\033[1;35mDONE\033[0;39m'# 其他参数:提示用法elseecho "Usage: print PASS|FAIL|DONE"fi
}
# 交互式读取用户输入,-p选项:显示提示文字
read -p "请输入你想要打印的内容:" str
# 调用print函数,传入用户输入的参数
print $str# 测试1:输入PASS(输出绿色PASS)
[bq@shell ~]$ bash fun4.sh 
请输入你想要打印的内容:PASS
PASS  # 实际显示为绿色高亮# 测试2:输入FAIL(输出红色FAIL)
[bq@shell ~]$ bash fun4.sh 
请输入你想要打印的内容:FAIL
FAIL  # 实际显示为红色高亮# 测试3:输入DONE(输出紫色DONE)
[bq@shell ~]$ bash fun4.sh 
请输入你想要打印的内容:DONE
DONE  # 实际显示为紫色高亮# 测试4:输入其他文字(提示用法)
[bq@shell ~]$ bash fun4.sh 
请输入你想要打印的内容:hello
Usage: print PASS|FAIL|DONE

补充说明

  • read -p "提示信息" 变量名:用于交互式获取用户输入,-p 显示提示文字。
  • 颜色控制符:\033[1;32m 中,1 表示高亮,32 表示绿色(31 = 红,35 = 紫),\033[0;39m 用于恢复默认颜色,避免后续文字也变色。
示例 4:函数参数与脚本参数的关系

实验流程

  1. 编写脚本 fun5.sh,定义 print 函数(逻辑同示例 3),脚本中通过 $2 获取第二个参数传给函数。
  2. 测试脚本传入不同参数,观察函数是否使用脚本的参数,以及 $0 的值(脚本名还是函数名)。

详细步骤

# 编写脚本fun5.sh
[bq@shell ~]$ cat fun5.sh 
#!/bin/bash
function print () {if [ "$1" == "PASS" ];thenecho -e '\033[1;32mPASS\033[0;39m'elif [ "$1" == "FAIL" ];thenecho -e '\033[1;31mFAIL\033[0;39m'elif [ "$1" == "DONE" ];thenecho -e '\033[1;35mDONE\033[0;39m'else# $0始终表示脚本名(而非函数名)echo "Usage: $0 PASS|FAIL|DONE"fi
}
str=$2  # 脚本的第二个参数赋值给变量str
print $str  # 函数使用脚本的第二个参数# 测试1:脚本传入两个参数(PASS和FAIL)
[bq@shell ~]$ bash fun5.sh PASS FAIL
FAIL  # 函数接收的是脚本的第二个参数(FAIL),输出红色FAIL# 测试2:脚本不传入参数(触发else分支)
[bq@shell ~]$ bash fun5.sh 
Usage: fun5.sh PASS|FAIL|DONE  # $0显示为脚本名fun5.sh,而非函数名print

结论

  • 函数参数需要显式从脚本参数中传递(如脚本的 $2 传给函数的 $1)。
  • $0 始终表示当前脚本的文件名,和函数名无关。

企业级 URL 检测脚本

实验流程

  1. 定义 usage 函数:提示脚本用法(如参数错误时调用)。
  2. 定义 check_url 函数:用 wget 检测 URL 是否可访问。
  3. 定义 main 函数:检查参数数量,调用 check_url 函数。
  4. 执行脚本,测试有效 URL(如百度)和无效 URL,观察结果。

详细步骤

# 编写检测脚本check_url.sh
[bq@shell ~]$ cat check_url.sh 
#!/bin/bash
# 用法提示函数:当参数错误时调用
function usage () {echo "usage: $0 url"  # 提示正确用法:脚本名 + URLexit 1  # 退出脚本,状态码1表示错误
}# URL检测函数:接收URL参数,判断是否可访问
function check_url () {# wget选项说明:# --spider:模拟爬虫(只检查URL是否存在,不下载内容)# -q:安静模式(不输出日志)# -o /dev/null:将日志输出到“黑洞”(彻底不显示)# --tries=1:尝试1次(失败不重试)# -T 5:超时时间5秒wget --spider -q -o /dev/null --tries=1 -T 5 $1# 判断上一条命令的返回值($?):0表示成功,非0表示失败[ $? -eq 0 ] && echo "$1 is accessable" || echo "$1 is not accessable"
}# 主函数:处理参数,调用检测函数
function main () {[ $# -ne 1 ] && usage  # 如果参数数量不是1,调用usage函数提示用法check_url $1  # 调用检测函数,传入URL参数
}# 执行主函数,$*表示所有参数(传给main函数)
main $*# 测试1:检测有效URL(百度)
[bq@shell ~]$ bash check_url.sh www.baidu.com
www.baidu.com is accessable  # 可访问# 测试2:检测无效URL(不存在的域名)
[bq@shell ~]$ bash check_url.sh www.bq.com
www.bq.com is not accessable  # 不可访问# 测试3:参数错误(不传参数)
[bq@shell ~]$ bash check_url.sh 
usage: check_url.sh url  # 调用usage函数提示用法

实战价值:可批量检测网站可用性,结合定时任务(crontab)实现自动监控。

函数的递归调用(函数调用自身)

递归是指函数自己调用自己,适用于有明确终止条件的问题(如求和、阶乘)。

示例 1:递归求 1+2+…+n 的和

实验流程

  1. 定义 sum_out 函数:如果 n=1,返回 1;否则返回 n + sum_out(n-1)(自身调用)。
  2. 脚本接收用户输入的整数 n,调用函数计算和并输出。
  3. 测试输入 10(预期结果 55),验证正确性。

详细步骤

# 编写求和脚本sum.sh
[bq@shell ~]$ cat sum.sh 
#!/bin/bash
# 递归求和函数:计算1+2+...+$1的和
function sum_out() {# 终止条件:当参数为1时,和为1if [ $1 -eq 1 ];thensum=1else# 递归调用:n的和 = n + (n-1)的和(通过$(sum_out $[ $1 - 1 ])获取n-1的和)sum=$[ $1 + $(sum_out $[ $1 - 1 ]) ]fiecho $sum  # 输出计算结果
}
# 读取用户输入的整数
read -p "输入一个你想计算和的整数:" num
# 调用递归函数,传入用户输入的数字
sum_out $num# 测试:输入10(1+2+...+10=55)
[bq@shell ~]$ bash sum.sh 
输入一个你想计算和的整数:10
55  # 结果正确
示例 2:递归求 n 的阶乘(12…*n)

实验流程

  1. 定义 fact_out 函数:如果 n=1,返回 1;否则返回 n * fact_out(n-1)
  2. 脚本接收用户输入的整数 n,调用函数计算阶乘并输出。
  3. 测试输入 10(预期结果 3628800),验证正确性。

详细步骤

# 编写阶乘脚本fact.sh
[bq@shell ~]$ cat fact.sh 
#!/bin/bash
# 递归阶乘函数:计算1*2*...*$1的积
function fact_out() {# 终止条件:当参数为1时,阶乘为1if [ $1 -eq 1 ];thensum=1else# 递归调用:n的阶乘 = n * (n-1)的阶乘sum=$[ $1 * $(fact_out $[ $1 - 1 ]) ]fiecho $sum  # 输出计算结果
}
# 读取用户输入的整数
read -p "输入一个你想计算阶乘的整数:" num
# 调用递归函数,传入用户输入的数字
fact_out $num# 测试:输入10(10! = 3628800)
[bq@shell ~]$ bash fact.sh 
输入一个你想计算阶乘的整数:10
3628800  # 结果正确
示例 3:fork 炸弹(危险!仅作原理了解)

fork 炸弹是一种通过递归创建大量进程耗尽系统资源的恶意代码,原理是函数不断自我复制并后台运行。

代码解析

:(){ :|:& };:  # fork炸弹核心代码

逐部分解释:

  • :():定义一个函数,函数名是 :(冒号)。

  • { :|:& }
    

    :函数体内容:

    • ::调用函数自身(递归)。
    • |:管道符,将左边的输出作为右边的输入,同时触发两次函数调用。
    • &:将进程放入后台运行,允许同时创建更多子进程。
  • ;:结束函数定义。

  • ::调用函数,触发 “爆炸”。

危害:函数会无限制创建子进程,很快耗尽 CPU、内存等资源,导致系统卡死。

防御措施:限制用户可创建的最大进程数(临时生效):

# 限制当前用户最多创建100个进程(ulimit -u 限制用户最大进程数)
[bq@shell ~]$ ulimit -u 100

警告:切勿在生产环境中执行 fork 炸弹代码!

总结

Shell 函数通过 “封装重复代码” 提升了脚本的简洁性和可维护性,掌握函数的定义、参数传递、递归调用等技巧,能大幅提高 Shell 脚本开发效率。实际使用中,建议将通用函数整理到单独的文件中,通过 source 加载复用,形成自己的 “函数库”。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/94220.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/94220.shtml
英文地址,请注明出处:http://en.pswp.cn/web/94220.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序设计的请求封装方案(request.js)

以下是为微信小程序设计的请求封装方案&#xff0c;包含代码示例和最佳实践建议&#xff1a; 基础请求封装&#xff08;request.js&#xff09; // 基础配置 const BASE_URL https://api.yourdomain.com; const TIMEOUT 10000;// 请求封装函数 const request (options) >…

【Linux系统】进程信号:信号的处理

上一篇文章在介绍完信号的产生和保存后&#xff0c;我们现在对信号有了一个基本的认识&#xff0c;信号由键盘、系统调用、硬件异常、软件条件等方式产生&#xff0c;然后被保存在三张表中&#xff0c;再将信号递达&#xff0c;操作系统有三种处理方式&#xff1a;默认处理、忽…

权限管理模块

登录相关权限管理模块(基础版)模块设计与实现优化点&#xff1a;前后端用户验证实现方式常见的攻击手段及防御手段权限管理模块(基础版) RBAC(Role-Base Access Control&#xff0c;基于角色的访问控制)&#xff1a;是权限管理的常用方案。 核心&#xff1a;通过用户 - 角色 -…

征服与守护:从拉里·埃里森看八号人格的职场王者之道

真正的强者&#xff0c;从不遵守别人的规则2010年&#xff0c;加利福尼亚州的圣何塞机场迎来了一架不速之客——一架意大利产的马基战斗机以一种极其霸道的姿态降落在跑道上。舱盖打开&#xff0c;走下来的不是空军飞行员&#xff0c;而是一位身穿飞行员服、戴着墨镜的企业家&a…

【Linux系统】命名管道与共享内存

前言&#xff1a; 上文我们讲到了匿名管道【Linux系统】匿名管道以及进程池的简单实现-CSDN博客 本文我们来讲一讲命名管道与共享内存 命名管道 上面我们讲到&#xff0c;匿名管道只能用于有血缘关系&#xff08;尤其父子&#xff09;的进程进行通信&#xff01;但如果…

搜索体验优化:ABP vNext 的查询改写(Query Rewrite)与同义词治理

&#x1f50e; 搜索体验优化&#xff1a;ABP vNext 的查询改写&#xff08;Query Rewrite&#xff09;与同义词治理 &#x1f4da; 目录&#x1f50e; 搜索体验优化&#xff1a;ABP vNext 的查询改写&#xff08;Query Rewrite&#xff09;与同义词治理1. 背景与问题界定 &…

Text2API与Text2SQL深度对比:自然语言驱动的数据交互革命

在数字化浪潮中&#xff0c;如何让人机交互更加自然流畅&#xff1f;Text2API与Text2SQL技术应运而生&#xff0c;它们如同魔法般将自然语言转化为机器可执行的指令&#xff0c;让数据交互不再高不可攀。本文将深入剖析这两项技术的原理、优劣势及应用场景&#xff0c;带您领略…

数据可视化与分析平台设计与实现案例

数据可视化与分析平台设计与实现案例(python) 下面分享一个完整的 Flask 数据可视化与分析平台代码,包含所有必要的组件和功能。这个平台允许用户上传数据文件、进行基本的数据清洗、生成各种可视化图表以及查看基础统计分析结果。 产品设计 核心功能 数据上传与管理(支…

Kotlin-基础语法练习二

接上一篇博客 每个 Kotlin 程序都是由两种部分组成的&#xff1a; 1、表达式&#xff08;Expressions&#xff09;&#xff1a;用于计算值的部分&#xff0c;比如 2 3、函数调用、变量赋值等&#xff0c;它们通常会返回一个结果。2、语句&#xff08;Statements&#xff09;…

与Deepseek对话了解单片机基础知识

keil5里的c语言编程的程序烧录到单片机里具体过程是啥&#xff1f;如何能把机器语言转换为电路控制&#xff1f; 步骤 所在位置 核心工具 输入->输出 比喻 1. 编译 Keil5 (PC) 编译…

利用背景图片定位套打档案封面

某些表单设计起来比较复杂&#xff0c;或只有表单的空白图片资料。Nhdeep档案目录套打工具&#xff08;nhdeep官网www.nhdeep.com&#xff09;支持将已有的表单图片作为模版背景图片&#xff0c;然后使用文本框进行精准的位置定位&#xff0c;再进行文本替换。 背景图片定位套…

微信HOOK 实现自动下载视频

1、前言 在收发消息的接口中&#xff0c;图片和文件这类接口是相对容易自动下载&#xff0c;但是视频的下载是需要手动点击的&#xff0c;并且只有这一种下载方式&#xff0c;实现自动化也比较困难&#xff0c;一些项目的开发中&#xff0c;需要自动下载收到的视频并保存&#…

【GPT入门】第57课 详解 LLamaFactory 与 XTuner 实现大模型多卡分布式训练的方案与实践

【GPT入门】第57课 大模型多卡计算1. 理论2.LLamaFacotory实践3. xtuner3.1 介绍3.1 安装3.2 xtuner训练3.4 训练后格式转换3.5 合并基础模型与lora模型3.6 参数说明3.7 训练过程主观检验1. 理论 deepspeed的三种训练方式 zero-1&#xff0c;优化器状态分片。的优势体现在多卡…

部队多媒体信息发布系统:赋能 IPTV 与电教化,加速军营信息化变革

在科技飞速发展的当下&#xff0c;部队的信息化建设也在不断推进。多媒体信息发布系统作为一种创新的技术手段&#xff0c;正逐步融入部队的各个领域&#xff0c;为部队的现代化建设注入强大动力。​在部队 IPTV 方面&#xff0c;多媒体信息发布系统展现出卓越的性能。它打破了…

FTP/TCP上传下载文件

封装C风格地ftplib为ftp.c和ftp.h文件&#xff1a;cftplient类&#xff08;主要成员变量&#xff1a;文件大小、文件修改时间、主要成员函数&#xff1a;get函数&#xff08;远程文件名、本地文件名、核对文件时间&#xff09;、put函数&#xff08;本地文件名、服务端文件名、…

DeepSeek V3.1深度解析:一个模型两种思维,迈向Agent时代的第一步!

名人说&#xff1a;博观而约取&#xff0c;厚积而薄发。——苏轼《稼说送张琥》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录一、什么是DeepSeek V3.1&#xff1f;为什么这么火&#x1f680;1. 发布时间线回顾2.…

VsCode 便携版(绿色版)下载及配置

下载 VsCode 便携版&#xff0c;并确保所有配置和扩展都保存在一起&#xff0c;实现真正的“绿色版”效果 核心步骤概览 核心原理是在 VSCode 的主程序目录下创建一个名为 data 的文件夹&#xff0c;VSCode 启动时如果检测到这个文件夹&#xff0c;就会自动切换到便携模式&am…

使用VLLM部署大模型embedding/chat 的API

模型下载&#xff1a;一般通过modelscope提供的方式进行下载&#xff0c;速度更快&#xff0c;huggingface下模型即便开启了魔法也还是很慢&#xff0c;对于9B以上的模型都是至少15G的。 比如需要下载qwen3-embedding-8b的模型&#xff0c;可以通过提供的一段代码自动进行下载到…

Blender模型动画导入到UE5

UE5支持直接导入FBX文件&#xff0c;但在实际应用中笔者发现&#xff1a;刚开始使用的是UE5.3&#xff0c;在UE5.3中直接将.fbx文件拖入UE中导入后是一个个的零件&#xff0c;后来使用了datasmith插件等其他办法&#xff0c;怎么都没有达到想要的效果。后面升级UE5.4以后&#…