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 中至少任意一个的文件尾
原文地址:https://www.cnblogs.com/testeremma/p/12688151.html