shell语法 函数

Posted 我的紫霞辣辣

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了shell语法 函数相关的知识,希望对你有一定的参考价值。

函数介绍

函数就是用来盛放一组代码的容器,函数内的一组代码完成一个特定的功能,称之为一组代码块,调用函数便可触发函数内代码块的运行,这可以实现代码的复用,所以函数又可以称之为一个工具。

为何要用函数

1、减少代码冗余
2、提升代码的组织结构性、可读性
3、增强扩展性

函数的基本使用

具备某一功能的工具=>函数
事先准备好哦工具=>函数的定义
遇到应用场景,拿来就用=>函数的调用

所以函数的使用原则:先定义,后调用

定义函数

# 语法:
[ function ] funname [()]
{
    命令1;
    命令2;
    命令3;
    ...
    [return int;]
}

# 示例1:完整写法
function 函数名() {
	函数要实现的功能代码
}

# 示例2:省略关键字(),注意此时不能省略关键字function
function 函数名 {
	函数要实现的功能代码
}

# 示例3:省略关键字function
函数名() {
	函数要实现的功能代码
}

调用函数

# 语法:
函数名  # 无参调用
函数名 参数1 参数2  # 有参调用

#/bin/bash

# 示例
function test1() {
        echo "执行第一个函数"
}

function test2 {				
        echo "执行第二个函数"
}

test3() {
        echo "执行第三个函数"
}

# 调用函数:直接引用函数名字即调用函数,会触发函数内代码的运行
test1
test2
test3

[root@m01 ~]# bash 1.sh
执行第一个函数
执行第二个函数
执行第三个函数

函数参数

如果把函数当成一座工厂,函数的参数就是为工厂运送的原材料

  • 调用函数时可以向其传递参数
# 调用函数test1,在其后以空格为分隔符依次罗列参数 
test1 111 222 333 444 555
  • 在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数, 2 表 示 第 二 个 参 数 . . . 当 n > = 10 时 , 需 要 使 用 2表示第二个参数...当n>=10时,需要使用 2...n>=10使{n}来获取参数
#!/bin/bash

read -p "输入参数一:" A
read -p "输入参数二:" B
read -p "输入参数三:" C

# 定义函数
function test01() {
        echo "start.."
        echo "$1"
        echo "$2"
        echo "$3"
        echo "end.."
}

# 调用函数
test01 $A $B $C


# 执行脚本
[root@m01 ~]# bash k.sh 
输入参数一:nana
输入参数二:lala
输入参数三:dada
start..
nana
lala
dada
end..

函数传参

参数处理说明
$#传递到脚本或函数的参数个数
$*所有参数
$@所有参数,与$*类似
$$当前脚本进程的ID号
$?获取上一条命令执行完毕后的退出状态,0表示正确,非0代表错误。如果执行的是函数那么$?取的是函数体内return后的值
- $*与$@的差别
1、当$*和$@没有被引号引用起来的时候,它们确实没有什么区别,都会把位置参数当成一个个体。
2、"$*" 会把所有位置参数当成一个整体(或者说当成一个单词),如果没有位置参数,则"$*"为空,
如果有两个位置参数并且分隔符为空格时,"$*"相当于"$1 $2"
3、"$@"  会把所有位置参数当成一个单独的字段,如果没有位置参数,则"$@"展开为空(不是空字符串,而是空列表),
如果存在一个位置参数,则"$@"相当于"$1",如果有两个参数,则"$@"相当于"$1"  "$2"等等
echo "======函数test1========"
# 定义函数test1
function test1() {
	echo "$*"			# 所有参数
	echo "$@"			# 所有参数
	echo $#				# 传递到脚本或函数的参数个数	
	echo $$				# 当前进程的pid号
	echo $?		# 获取上一条命令执行完毕后的退出状态,0表示正确,非0代表错误。如果执行的是函数那么$?取的是函数体内return后的值
}

# 调用函数test1
test1 111 222 333 444 555

# test1的函数执行结果
======函数test1========
111 222 333 444 555
111 222 333 444 555
5
18337
0

=======================================================================================================================

echo "======函数test2========"
# 定义函数test2
function test2() {
	for i in "$*" 	 	# 注意:$*不加引号结果与$@一模一样
	do
		echo $i
	done
}

# 调用函数test2
test2 111 222 333 "444 555"			 # 注意"444 555"被引号引成了一个参数

# test2函数执行结果
======函数test2========
111 222 333 444 555

=======================================================================================================================

echo "======函数test3========"
# 定义函数test3
function test3() {
	for i in "$@"		 # 注意:$*不加引号结果与$@一模一样
	do
		echo $i
	done
}

# 调用函数test3
test3 111 222 333 "444 555"			 # 注意"444 555"被引号引成了一个参数

# test2函数执行结果
======函数test3========
111
222
333
444 555

函数的返回值

如果把函数当成一座工厂,函数的返回值就是工厂的产品,在函数内使用return关键字返回值,函数内可以有多个return,但只要执行一个,整个函数就会立刻结束

#!/bin/bash

function test01() {
        echo 111
        return
        echo 222
        return
        echo 333
}

test01

[root@m01 ~]# bash D.sh
111            

需要注意的是shell语言的函数中,通常用return返回函数运行是否成功的状态,0代表成功,非零代表失败,需要用$?获取函数的返回值

#!/bin/bash

function test01() {
        echo 111
        echo 222
        echo 333
        xxx				# 运行该命令会报错
}		
	
test01					# 调用函数
echo $?				# 查看函数的运行结果

[root@m01 ~]# bash 2.sh
111
222
333
2.sh: line 7: xxx: command not found
127

如果函数内有return,那么return后跟的只能是整型值并且范围为0-255,用于标识函数的运行结果是否正确, 与C 语言不同,shell 语言中 0 代表 true,0 以外的值代表 false

#!/bin/bash

function test01() {
        echo 111
        echo 222
        return 0
}

test01
echo $?				  # 用$?获取函数的返回值

[root@m01 ~]# bash 3.sh 
111
222
0

变量的作用域

Shell 变量的作用域(Scope),就是 Shell 变量的有效范围(可以使用的范围)。

1、局部变量:只能在函数内访问

  • 使用local关键字定义在函数内的变量属于局部范围,只能在函数内使用,如下所示
#!/bin/bash

# 定义函数
function test(){
        local x=111
        echo "函数内访问x:$x"
}

# 调用函数
test
echo "在全局访问x:$x"			 # 无法访问函数内的局部变量

执行结果
[root@m01 ~]# bash 123.sh
函数内访问x:111
在全局访问x:

2、全局变量:可以在当前shell进程中使用

  • 所谓全局变量,就是指变量在当前的整个 Shell 进程中都有效。每个 Shell 进程都有自己的作用域,彼此之间互不影响。在 Shell 中定义的变量,默认就都是全局变量。
#!/bin/bash

x=666

function test() {
        echo "函数内访问x:$x"
}

test
echo "全局访问x:$x"

执行结果
[root@m01 ~]# bash ABC.sh
函数内访问x:666
全局访问x:666

注意:

  1. 在函数内定义的变量,如果没有用local声明,那么默认也是全局变量,shell变量语法的该特性与js的变量是类似的(在js函数内部定义的变量,默认也是全局变量,除非加上关键字var)。
#!/bin/bash

function test() {
        x=222
}

test

echo "在全局访问x:$x"

执行结果
[root@m01 ~]# bash aaa.sh
在全局访问x:222
  1. 每执行一个解释器,都会开启一个解释的shell进程,每个shell进程都有自己的作用域彼此互不干扰
[root@m01 ~]# x=111
[root@m01 ~]# echo $x
111
[root@m01 ~]# bash
[root@m01 ~]# echo $x

[root@m01 ~]# 
  1. 需要强调的是:
    全局变量的作用范围是当前的 Shell 进程,而不是当前的 Shell 脚本文件,它们是不同的概念。打开一个 Shell 窗口就创建了一个 Shell 进程,打开多个 Shell 窗口就创建了多个 Shell 进程,每个 Shell 进程都是独立的,拥有不同的进程 ID。在一个 Shell 进程中可以使用 source 命令执行多个 Shell 脚本文件,此时全局变量在这些脚本文件中都有效。
[root@m01 ~]# echo $x

[root@m01 ~]# vim a.sh
#!/bin/bash

function test(){
        x=2222		  # 全局变量
}

test

[root@m01 ~]# source a.sh  		 # 在当前shell进程中执行,产生一个全局变量
[root@m01 ~]# echo $x		 	 # 在当前shell进程中访问全局变量x,可以看到
2222
[root@m01 ~]# vim b.sh
#!/bin/bash

echo $x
[root@m01 ~]# source b.sh 		 # 在当前shell进程中访问全局变量x,同样可以看到
2222

结论:函数test内的全局变量x早已超越了文件,即全局变量是超越文件的,作用范围是整个当前bash进程

3、环境变量:在当前进程的子进程中都可以使用

全局变量只在当前 Shell 进程中有效,对其它 Shell 进程和子进程都无效。如果使用export命令将全局变量导出,那么它就在所有的子进程中也有效了,这称为“环境变量”。

环境变量被创建时所处的 Shell 进程称为父进程,如果在父进程中再创建一个新的进程来执行 Shell 命令,那么这个新的进程被称作 Shell 子进程。当 Shell 子进程产生时,它会继承父进程的环境变量为自己所用,所以说环境变量可从父进程传给子进程。不难理解,环境变量还可以传递给孙进程。

[root@m01 ~]# export x=666
[root@m01 ~]# bash
[root@m01 ~]# echo $x
666
[root@m01 ~]# bash
[root@m01 ~]# echo $x
666

ps:通过exit命令可以一层一层地退出 Shell。

命令set和env

set:显示所有变量
env:环境变量

注意

  • 1、环境变量只能向下传递而不能向上传递,即“传子不传父”。
  • 2、两个没有父子关系的 Shell 进程是不能传递环境变量的

我们一直强调的是环境变量在 Shell 子进程中有效,并没有说它在所有的 Shell 进程中都有效;如果你通过终端创建了一个新的 Shell窗口,那它就不是当前 Shell 的子进程,环境变量对这个新的 Shell 进程仍然是无效的。

  • 3、环境变量也是临时的
[root@m01 ~]# ps aux | grep bash$ | grep -v grep
root      18443  0.0  0.3 116468  3188 pts/0    Ss   19:15   0:00 -bash
root      18667  0.1  0.2 116488  2940 pts/0    S    20:18   0:00 bash
root      18682  0.2  0.2 116488  2948 pts/0    S    20:18   0:00 bash

注意:
-开头的bash代表是在登录终端登录的顶级shell进程
非-开头的bash代表的是子shell进程
一旦退出了在终端登录的顶级shell,那么该终端下开启的所有子shell都会被回收,export设置的环境变量随即消失

所以说环境变量也是临时的,如果想设置成永久的,需要将变量写入shell配置文件中才可以,Shell 进程每次启动时都会执行配置文件中的代码做一些初始化工作,如果将变量放在配置文件中,那么每次启动进程都会定义这个变量。

登录shell与非登录shell

我们首先需要了解一下BASH的两种类型

  • 1、登录shell:就是通过输入用户名 密码后 或 su - 获得的shell
  • 2、非登录shell:则是通过bash命令和脚本开启的shell环境

我们知道在linux里一切皆为文件,同样,shell的属性加载也是写到文件里的。在登陆时就会加载对应文件的内容来初始化shell环境,非登录与登录区别就在于加载的文件不同,从而导致获得的shell环境不同。

我们看看登录shell都加载了那些文件

--> /etc/profile
--> /etc/profile.d/*.sh
--> $HOME/.bash_profile
--> $HOME/.bashrc
--> /etc/bashrc 

再看非登录shell加载的文件,非登录shell加载的文件要少很多

--> $HOME/.bashrc
--> /etc/bashrc
--> /etc/profile.d/*.sh

通常,我们会将环境变量设置在 $HOME/.bash_profile

但如果不管哪种登录shell都想使用的变量 可以考虑设置在$HOME/.bashrc或者/etc/bashrc中,因为它们都属于无论如何都会执行的文件,但是,如果我们真的在这类文件中添加了变量,那么意味着每次执行shell都会重新定义一遍该变量,而定义变量是要耗费内存资源的,这非常不可取,所以我们通常会结合export/etc/profile文件中声明一个全局变量,这样在每次登录用户时产生的顶级shell里会有一个全局变量,所有的子shell都可以看到了,无需重复定义。

[root@m01 ~]# vim /etc/profile
name="nana"

[root@m01 ~]# source /etc/profile		# 重载/etc/profile文件,可以在当前shell中立即生效,也可以退出后重新登录终端
[root@m01 ~]# echo $name
nana

案例

使用函数编写一个计算机

#!/bin/bash

read -p "输入第一个数字:" num1
read -p "输入运算符:" num2
read -p "输入第二个数字:" num3


function NUM() {
        NUM2=`echo "$1 $2 $3" | bc`
        echo "结果为:$NUM2"
}

NUM $num1 "$num2" $num3

编写一个Nginx脚本,脚本里面包含安装,启动,停止,监控等接口功能

#!/bin/bash
# 编写一个脚本,支持安装nginx,启动|停止nginx、监控nginx。

## 初始化一些函数
. /etc/init.d/functions

## 打印结果
function print_action() {
    if [ $2 -eq 0 ]; then
        action $1 /bin/true
    else
        action $1 /bin/false
    fi
}

# 判断nginx是否安装
function is_install() {
  rpm -q nginx
  return $?
}

# 测试nginx是否能够正常启动
function test_nginx() {
    is_install
    if [ $? -eq 0 ];then
      print_action "测试安装已通过" 0
    else
      print_action "测试安装未通过" 1
    exit;
    fi

    nginx -t
    if  [ $? -eq 0 ];then
      print_action "测试配置文件已通过" 0
    else
     print_action "测试文件未通过" 1
     exit;
    fi
}

## 安装nginx
function install() {
  is_install

  if [ $? -eq 0 ];then
    print_action "Nginx安装成功" 0
    exit;
  fi  

cat > /etc/yum.repos.d/nginx.repo<<EOF
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/\\$releasever/\\$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/\\$releasever/\\$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
EOF

  yum clean all 

  yum makecache

  yum -y install nginx

  if [ $? -eq 0 ];then
    print_action "nginx服务安装成功" 0
  else
    print_action "nginx服务安装失败" 1
    exit;
  fi
}

## 启动Nginx
function start_nginx() {
  test_nginx

  systemctl start nginx
  if [ $? -eq 0 ];then
    print_action "nginx启动成功" 0
  else
    print_action "nginx服务启动失败" 1
  fi
}

## 强制启动
function force_start() {
  test_nginx

  pkill nginx

  start_nginx
}

## 停止nginx
function stop_nginx() {
    systemctl stop nginx
    if [ $? -eq 0 ];then
        print_action "nginx服务停止成功" 0
    else
        print_action "nginx服务停止失败" 1
    fi
}

## 监控nginx
function read_nginx() {
  systemctl status nginx
  if [ $? -eq 0 ];then
        print_action "nginx服务监控成功" 0
  else
        print_action "nginx服务监控失败" 1
  exit;
  fi
}

## 入口函数,分发请求
function main() {
  PS3="请选择:"

  select i in install start fstart stop read
  do
  case $i in
  install)
    install
    ;;
  "start")
    start_nginx
    ;;
  fstart)
    force_start
    ;;
  "stop")
    stop_nginx
    ;;
  "read")
    read_nginx
    ;;
  *)
    print_action "选择操作模式错误" 1
    ;;
  esac
  done
}

main                       

以上是关于shell语法 函数的主要内容,如果未能解决你的问题,请参考以下文章

Shell之函数

shell函数

nodejs常用代码片段

Python如何调用别人写好的脚本

代码片段:Shell脚本实现重复执行和多进程

shell 函数编程