zoukankan      html  css  js  c++  java
  • shell编程系列一:Shell脚本编程

    Shell脚本编程

    一、简介

    1、什么是shell

    shell是操作系统和应用程序之间的命令解释器;

    shell编程就是对一堆Linux命令的逻辑化处理 ;

    2、shell的分类

    • windows系统

    命令行cmd.exe

    • linux系统

    shell有很多种,如sh,ksh,csh,bash,zsh等

    在终端可输入more /etc/shells查看本机支持的shell

    二、Linux常用命令

    命令 备注
    head 默认获取前10行,常见选项:-n 指定行数;-c 指定显示字节数 打印前10行:head -n 10 test.txt;打印前2个字节:head -c 2 test.txt
    tail 默认获取末尾10行
    cut 获取某列内容,默认以规则的空格或tab键分割,常见选项:-d 指定分隔符;-f 指定某列
    uniq 去重重复内容,常见选项:-d 仅显示重复内容;-c 显示重复次数
    sort 对文本进行排序;默认以字符ASCII码数值从小到大排序,常见选项:-r 倒序;-n 以数值大小排序;-t 指定分隔符,默认空格;-knum 指定以某个字段排序 与uniq结合使用,先排序才能去重,因为uniq是相邻比较去重
    wc 计算文本数量,如wc-l test.txt

    示例A:head+tail

    示例B:cut+head

    1、获取以冒号为分隔符的第1列,第6列内容,并以逗号为分隔符输出;

    2、显示结果的前3行;

    示例C:sort+uniq

    三、变量

    1、变量分类

    • 自定义变量:局部变量和全局变量
    • 环境变量:Linux预定义的变量,如$PATH、$PWD$、HOME等

    2、定义变量

    利用等号“=”定义变量,如:变量名=变量值,等号两侧不能有空格;

    • 方式一:变量名=变量值

    变量值必须是一个整体,如需要存在空格等特殊字符,需要用方式二/三的双引号/单引号模式

    • 方式二:变量名=’变量值‘

    等号右侧为字符串,所见即所得(输出内容即为引号内容)

    • 方式三:变量名=“变量值”

    等号右侧为字符串,支持$var形式变量嵌套、 转义等!

    • 方式四:变量名=$(linux命令)或用反引号

    将命令执行后结果赋值给变量

    $()与反引号相似,但优先级更高,并支持嵌套

    • 方式五:变量名=(数组)变量名=(`linux命令`)

    数组中间使用空格隔开

    上述变量默认是局部变量

    示例A:将linux命令执行结果赋值给变量

    #!/bin/bash
    path=$(pwd)
    files=`ls -al`
    echo current path: $path
    echo files: $files
    

    以上2行和第3行分别演示了两种方式来将Linux命令执行结果保存到变量。

    第2行将pwd执行结果(当前所在目录)赋值给path变量。

    第3行将ls -al命令执行结果(列出当前目录下所有的文件及文件夹)赋值给变量

    定义变量不用$符号,使用变量要加$

    3、作用域

    Shell 变量的作用域可以分为三种:

    • 局部变量(local variable): 只能在函数内使用的变量 ;
    • 全局变量(global variable): 在当前shell中任何地方使用的变量 ;
    • 环境变量(environment variable): 可以在任何shell中使用的变量 。

    3.1、局部变量

    ** 在 Shell 函数中定义的变量默认也是全局变量,它和在函数外部定义变量拥有一样的效果, 要想变量的作用域仅限于函数内部,可以在定义时加上local命令,此时该变量就成了局部变量 **

    #!/bin/bash
    #定义函数
    function func(){
        a=99
    }
    #调用函数
    func
    #在函数外输出函数内部的变量
    echo $a
    

    输出结果:
    99

    #!/bin/bash
    #定义函数
    function func(){
        local a=99
    }
    #调用函数
    func
    #输出函数内部的变量
    echo $a
    

    输出结果为空,表明变量 a 在函数外部无效,是一个局部变量

    3.2、全局变量

    所谓全局变量,就是指变量在当前的整个 Shell 进程中都有效。每个 Shell 进程都有自己的作用域,彼此之间互不影响

    全局变量在不同 Shell 进程中有互不相关性,即在图形界面下同时打开两个 Shell,或使用两个终端远程连接到服务器(SSH) ,变量不通用; 就像小王家和小徐家都有一部电视机(变量名相同),但是同一时刻小王家和小徐家的电视中播放的节目可以是不同的(变量值不同);

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

    在命令行定义的变量和脚本中定义的变量,相互间默认不通用,需要对脚本文件执行source命令,命令行可引用;多命令行执行export,脚本文件可引用;

    3.3、环境变量

    默认情况下变量的作用域是当前shell,如果用export命令将其导出,那么此变量在其所有的子shell中也生效,这种变量就是环境变量。环境变量只能向下传递,即父shell可以传递给子shell,反过来则不行。注意这里的环境变量不是变量在所有shell中都有效,而是在export变量时的shell的所有子shell中有效。

    可以通过命令查看环境变量(只显示全局变量):env

    • 定义环境变量

    方法一:变量名=变量值;export 变量

    方法二:export 变量名=变量值

    [c.biancheng.net]$ a=22       #定义一个变量
    [c.biancheng.net]$ echo $a    #在当前Shell中输出a,成功
    22
    [c.biancheng.net]$ bash       #进入Shell子进程
    [c.biancheng.net]$ echo $a    #在子进程中输出a,失败
    
    [c.biancheng.net]$ exit       #退出Shell子进程,返回上一级Shell
    exit
    [c.biancheng.net]$ export a   #将a导出为环境变量
    [c.biancheng.net]$ bash       #重新进入Shell子进程
    [c.biancheng.net]$ echo $a    #在子进程中再次输出a,成功
    22
    [c.biancheng.net]$ exit       #退出Shell子进程
    exit
    [c.biancheng.net]$ exit       #退出父进程,结束整个Shell会话
    

    可以发现,默认情况下,a 在 Shell 子进程中是无效的;使用 export 将 a 导出为环境变量后,在子进程中就可以使用了 。

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

    通过 export 导出的环境变量只对当前 Shell 进程以及所有的子进程有效,如果最顶层的父进程被关闭了,那么环境变量也就随之消失了,其它的进程也就无法使用了,所以说环境变量也是临时的

    只有将变量写入 Shell 配置文件中才能达到这个目的!Shell 进程每次启动时都会执行配置文件中的代码做一些初始化工作,如果将变量放在配置文件中,那么每次启动进程都会定义这个变量 。

    环境变量可用于定义shell的运行环境,环境变量可以在配置文件中定义与修改,也可以在命令行中设置,但是命令行中的修改操作在终端重启时就会丢失,因此最好在配置文件中修改(用户家目录的“.bash_profile“文件或者全局配置“/etc/profile”、“/etc/bashrc”文件或者“/etc/profile.d”文件中定义。)将环境变量放在profile文件中,每次用户登录时这些变量值将被初始化。比如HOME、USER、SHELL、UID等再用户登录之前就已经被/bin/login程序设置好了。

    4、查看变量

    方式一:echo ${变量名}

    方式二:echo "$变量名",echo '$变量名'

    5、特殊符号

    符号 含义
    $0 获取当前执行的shell脚本名,包括脚本路径
    $n 获取当前执行的shell脚本第n个参数值,n=1-9,如果n大于9就要用大括号括起来${10}
    $# 获取当前shell命令行中参数的总个数
    $? 获取执行上一个指令的返回值(0为成功,非0为失败) 查看执行是否成功:echo $?
    $* 获取当前执行的shell的所有参数,将所有的命令行参数视为单个字符串
    $@ 个程序的所有参数"$1" "$2" "$3" "...",这是将参数传递给其它程序的最佳方式,因为它会保留所有内嵌在每个参数里的任何空白,将命令行的每个参数视为单个的字符串
    $$ 获取当前的shell进程号

    四、数值运算

    支持基本的数据类型运算(+、-、*、/、%、==、!=、>、>=、<、<=)

    在shell中,对于基本数据类型的运算主要分为两种,整数运算浮点数(小数)运算

    1、整数运算

    方式一:$((算术表达式))-------常用

    表达式中变量可不加$,前后要加空格

    方式二:expr 算术表达式

    注意:

    1. 在乘法(*)中,我们需用反斜线()来转义,不然会报错。
    2. 运算符前后必须还有空格,否则会被直接当作字符串返回。
    3. 如果要将计算结果保存到变量,就需要用到我们上篇文章讲到的那两种方式($() 或者 ``)来替换命令了。

    2、浮点数运算

    需要借组其他组件,后续扩展

    五、流程控制

    1、条件表达式

    1.1、返回值

    • 条件成立,返回0
    • 条件不处理,返回1

    1.2、逻辑表达式

    或(||)/且(&&)

    1.3、文件比较

    -f 判断输入内容是否是一个文件

    -d 判断输入内容是否是一个目录

    -x 判断输入内容是否可执行

    -e 判断文件是否存在

    1.4、数值比较

    比较 描述
    n1 -eq n2 判断n1是否等于n2,等价于(( n1==n2 ))
    n1 -ge n2 判断n1是否大于或等于n2,等价于(( n1>=n2 ))
    n1 -gt n2 判断n1是否大于n2,等价于(( n1>n2 ))
    n1 -le n2 判断n1是否小于或等于n2,等价于(( n1<=n2 ))
    n1 -lt n2 判断n1是否小于n2,等价于(( n1<n2 ))
    n1 -ne n2 判断n1是否不等于n2,等价于(( n1!=n2 ))

    1.5、字符串比较

    比较 描述
    str1 = str2 判断str1是否与str2相同
    str1 != str2 判断str1是否与str2不相同
    str1 < str2 判断str1是否比str2小(根据ASCII)
    str1 > str2 判断str1是否比str2大(根据ASCII)
    -n str1 判断str1的长度是否非0
    -z str1 判断str1的长度是否为0

    在使用大于(>)或小于(<)符号时,需要转义(>)(<),不然会把这两种符号时别为重定向,加双方括号可以不用转移,命令的格式如下:

    [[ expression ]]
    

    2、if

    2.1 if-then语句

    #写法一:
    if <command>
    then
    	<commands>
    fi
    #写法二:
    if command; then
    	commands
    fi
    

    if语句后面接的是命令,但我们其它编程语言中,这儿都是接返回布尔值(true,false)的表达式

    在shell脚本的if其实是根据紧跟后面的那个命令的退出状态码来判断是否执行then后面的语句的。

    关于退出状态码,你只需要记住:正常退出(命令执行正常)的状态码是0, 非正常退出的状态码不是0(有不少)。

    以上语句的语义为: 如果if后面的命令执行正常(状态码0),那么就执行then后面的语句。否则不执行。 fi代表if语句的结束。

    2.2 if-then-else语句

    #写法一:
    if command
    then
    	commands
    else
    	commands
    fi
    #写法二:
    if command1 
    then
    	commands 
    elif 
    	command2 
    then
    	command3
    fi
    

    2.3 test语句

    test命令用于if-then或者if-then-else语句中,主要用于判断列出的条件是否成立,如果成立,就会退出并返回退出状态码0,否则返回非0

    直接用:

    test condition
    #或
    [ expression ]
    

    结合if-then语句用

    if	test condition
    then
    	commands
    fi
    

    结合if-then-else语句用

    if	test condition
    then
    	commands
    else 
    	commands	
    fi
    

    条件成立就执行then语句,否则else语句

    test命令只能判断一下三类条件:

    • 数值比较
    • 字符串比较
    • 文件比较

    总结:test 命令比较奇葩,>、<、== 只能用来比较字符串,不能用来比较数字,比较数字需要使用 -eq、-gt 等选项;不管是比较字符串还是数字,test 都不支持 >= 和 <=

    3、for

    #格式一
    for 值 in 列表 
    do
    	执行命令
    done
    #格式二
    max=10
    for ((i=1;i<=max;i++))
    do
    	echo ${i}
    

    for...in循环默认是循环一组通过空格或制表符(tab键)或换行符(Enter键)分割的值。这个其实是由内部字段分隔符配置的,它是由系统环境变量IFS定义的

    4、while

    满足条件会一直循环

    while 条件 
    do
    	执行语句
    done
    

    5、until

    满足条件会一直循环

    until 条件 
    do
    	执行语句
    done
    

    6、case

    case 变量名 in
    	值1)
    		指令1
    			;;
    	值2)
    		指令2
    			;;		
    	值3)
    		指令3
    			;;
    		....
    	*)
    		指令4
    			;;
    esac
    

    示例:命令行可进行算术运算

    六、shell脚本格式

    • 脚本首行加“#!/bin/bash”
    • shell脚本文件后缀,建议命令为.sh,命名要简单;
    • 脚本执行失败时,使用exit返回非零值,来退出程序;
    • 默认缩进4个空格
    • 单行注释:#
    • 多行注释
    :<<!
    这是注释
    !
    

    七、函数

    1、格式

    #格式一:
    函数名()
    {
    	命令1
    	....
    }
    #格式二:
    function 函数名
    {
    	命令1
    	....
    }
    

    2、参数

    Shell 中的函数在定义时不能指明参数,但是在调用时却可以传递参数

    2.1 给脚本文件传递位置参数

    2.2 函数调用时传递位置参数

    可以理解成函数内部的$n为形参,函数调用时传递的参数为实参;

    3、用户输入----read命令

    read默认就是从键盘读取用户输入的数据;如果进行了重定向,那么可以从文件中读取数据

    read [-options] [variables]
    

    variables表示用来存储数据的变量,可以有一个,也可以有多个
    options和variables都是可选的,如果没有提供变量名,那么读取的数据将存放到环境变量 REPLY

    选项 说明
    -a array 把读取的数据赋值给数组 array,从下标 0 开始。
    -d delimiter 用字符串 delimiter 指定读取结束的位置,而不是一个换行符(读取到的数据不包括 delimiter),其实只有-d后的第一个字符被作为结束的标志
    -e 在获取用户输入的时候,对功能键进行编码转换,不会直接显式功能键对应的字符。比如read -e -p "输入文件名:" str 执行后,输入文件名开头的几个字符,使用tab键可以进行文件名补全(文件在当前目录存在)
    -n num 读取 num 个字符,而不是整行字符。空格也算是一个字符
    -p prompt 显示提示信息,提示内容为 prompt。
    -r 原样读取(Raw mode),不把反斜杠字符解释为转义字符。
    -s 静默模式(Silent mode),不会在屏幕上显示输入的字符。当输入密码和其它确认信息的时候,这是很有必要的。
    -t seconds 设置超时时间,单位为秒。如果用户没有在指定时间内输入完成,那么 read 将会返回一个非 0 的退出状态,表示读取失败。
    -u fd 使用文件描述符 fd 作为输入源,而不是标准输入,类似于重定向。
    功能 脚本 结果 注意点
    给多个变量赋值 #!/bin/bash
    read -p "please input name and age:" name age

    echo "名字为${name}"
    echo "年龄为${age}"
    运行结果:
    please input name and age:emma 17
    名字为emma
    年龄为17
    1.必须在一行内输入所有的值,不能换行,否则只能给第一个变量赋值,后续变量都会赋值失败
    2.使用了-p选项,该选项会用一段文本来提示用户输入
    3.脚本中-p后的提示字符与变量name之间必须有一个空格,否则变量name会被当成提示信息的一部分
    只读取一个字符 #!/bin/bash
    read -n 1 -p "Enter a char > " char
    printf " " #换行
    echo $char
    运行结果:
    Enter a char > 1
    1
    不使用printf " "时的运行结果为
    Enter a char > 11
    1.-n 1表示只读取一个字符。运行脚本后,只要用户输入一个字符,立即读取结束,不用等待用户按下回车键。 2.printf " "语句用来达到换行的效果,否则 echo 的输出结果会和用户输入的内容位于同一行,不容易区分。
    在指定时间内输入密码 #!/bin/bash
    if
    read -t 20 -sp "Enter password in 20 seconds(once) > " pass1 && printf " " && #第一次输入密码
    read -t 20 -sp "Enter password in 20 seconds(again)> " pass2 && printf " " && #第二次输入密码
    [ $pass1 == $pass2 ] #判断两次输入的密码是否相等
    then
    echo "Valid password"
    else
    echo "Invalid password"
    fi
    如果两次输入密码相同,运行结果为: Enter password in 20 seconds(once) > Enter password in 20 seconds(again)> Valid password
    如果两次输入密码不同,运行结果为: Enter password in 20 seconds(once) > Enter password in 20 seconds(again)> Invalid password
    如果第一次输入超时,运行结果为: Enter password in 20 seconds(once) > Invalid password
    如果第二次输入超时,运行结果为: Enter password in 20 seconds(once) > Enter password in 20 seconds(again)> Invalid password
    1.&&组合多个命令时,这些命令会依次执行,但只要一个命令失败,后续命令都不会执行
    把读取的数据赋值给一个数组 #!/bin/bash
    read -a test_arr -p 'please input some values:'
    echo 'the num of the input values:'${#test_arr[]}
    for item in ${test_arr[
    ]}
    do
    echo ${item}
    done
    please input some values:aa bb cc dd
    the num of the input values:4
    aa
    bb
    cc
    dd
    -d read -d delimiter -p 'please input data>' test_var
    输出:please input data>test432decho $test_var
    输出:test432
    1.使用-d指定分隔符后,只有-d后的第一个字符被作为结束标志
    等待输出q退出 read -dq -p "Input some words end with q:" word #输入,直到输入q,将自动退出
    -e read -e -p "please input filename:" str
    输出:please input filename:learn_ #按tab键
    learn_arr.sh learn_cmd.sh learn_read1.sh learn_read.sh learn_spe_var.sh learn_str.sh #显示出所有相关的文件
    继续输出:please input filename:learn_
    读文件中的内容 #!/bin/bash
    num=1
    cat learn_read1.sh|while read line
    do
    echo "current line:$num--$line"
    num=$[ $num+1 ]
    done
    echo 'finish'
    exit 0
    current line:1--#!/bin/bash
    current line:2--read -d 'rrr' -p 'pleass input some data>' name
    current line:3--echo ''
    current line:4--echo $name
    finish
    -u使用文件描述符 fd 作为输入源,而不是标准输入 #将afile文件中的前三行与bfile中的前四行拼接在一起
    while read -u3 i && read -u4 j;do
    echo $i $j
    done 3<afile 4<bfile
    read -u3 i 的意思是从 3 号 fd (file descriptor,文件描述符) 中读一行数据到 i 变量中,而 3<afile 的意思是重定向 afile 到 3 号 fd 中 所以,整个代码的意思是,不断从 afile 和 bfile 中分别读取内容到i , j 中,然后用echo 打印出来。 这个循环会一直执行直到遇到 afile 或 bfile 中至少任意一个的文件尾
  • 相关阅读:
    亚瑟阿伦36问
    Oracle动态SQL
    Oracle分页
    Oracle游标+动态SQL
    Oracle %type %rowtype
    Oracle游标
    Oracle存储过程和Java调用
    Oracle循环
    Oracle(if判断)
    Oracle视图
  • 原文地址:https://www.cnblogs.com/testeremma/p/12688151.html
Copyright © 2011-2022 走看看